run_code API reference
run_code({ slug, code }) evaluates an async arrow function in a sandbox
attached to the space's Workspace, and JSON-serializes whatever you return:
run_code({ slug, code: `async () => {
await state.writeFile("/index.html", "<h1>hi</h1>");
return await state.glob("**/*");
}` })
Six namespaces are in scope: state.* (files), git.* (repo), host.*
(build/deploy/previews), config.* (asset config + variables/secrets/egress),
media.* (durable assets), introspect.* (read deployed SQLite). Arguments are
positional, exactly as shown below.
Escaping:
codeis a JS expression, so a literal${…}in source you author is interpolated at parse time. Escape it as\${…}, useString.raw, or — simpler — drop verbatim source with thewrite_filestool instead.
state.* — Workspace files
Paths are absolute (/src/index.ts). readFile/stat/lstat throw ENOENT
if missing; exists never throws; glob returns sorted absolute paths.
Read & inspect
await state.readFile("/src/index.ts") // → string
await state.readFileBytes("/logo.png") // → Uint8Array
await state.exists("/package.json") // → boolean
await state.stat("/src") // → { type, size, mtime, … } (follows symlinks)
await state.lstat("/link") // → stat without following the final symlink
await state.readdir("/src") // → string[] (names)
await state.readdirWithFileTypes("/src") // → [{ name, isDirectory, … }]
await state.glob("src/**/*.{ts,tsx}") // → string[] absolute paths
await state.walkTree("/", { maxDepth: 3 }) // → nested tree node
await state.summarizeTree("/") // → { files, dirs, bytes, … } aggregate
await state.find("/", { type: "file" }) // → [{ path, … }] flat listing
await state.diff("/a.txt", "/b.txt") // → unified diff string
await state.hashFile("/dist/app.js") // → content hash string
Write & mutate
await state.writeFile("/index.html", "<h1>hi</h1>")
// Advisory: writeFileBytes is for text you happen to hold as bytes — NEVER media.
// Images/fonts/video/audio go to media (media.write / get_urls dest=media),
// not the git workspace.
await state.writeFileBytes("/data/seed.csv", bytes) // bytes: Uint8Array
await state.appendFile("/log.txt", "line\n")
await state.mkdir("/assets", { recursive: true })
await state.rm("/tmp", { recursive: true })
await state.cp("/a.txt", "/b.txt")
await state.mv("/old.txt", "/new.txt")
Search & bulk edit
await state.searchText("/", "TODO") // → [{ path, line, text }]
await state.searchFiles("src/**/*.ts", "useState") // → matches grouped by file
await state.replaceInFile("/src/app.ts", "v1", "v2") // single file
await state.replaceInFiles("src/**/*.ts", "v1", "v2") // glob-wide
await state.planEdits([{ /* instruction */ }]) // → an edit plan (preview)
await state.applyEdits([{ /* edit */ }]) // apply edits atomically
JSON helpers
await state.readJson("/data.json") // → parsed value
await state.writeJson("/data.json", { a: 1 })
await state.queryJson("/data.json", "$.items[0].name") // JSONPath query
await state.updateJson("/data.json", [{ /* op */ }]) // structured update
Archives
await state.createArchive("/out.zip", ["/src", "/public"])
await state.extractArchive("/in.zip", "/unpacked")
git.* — the Workspace as a repo
Options objects; dir defaults to the repo root / (omit it). git.add
stages by filepath (singular).
await git.status({ dir: "/" }) // → working-tree status
await git.add({ filepath: ".", dir: "/" }) // stage everything
await git.commit({ message: "feat: x",
author: { name: "Agent", email: "agent@unfolder.space" }, dir: "/" })
await git.log({ dir: "/" }) // → commit history
await git.branch({ list: true }) // → { branches, current } ← deploy target
await git.branch({ name: "feat-x" }) // create (does NOT switch)
await git.checkout({ ref: "feat-x" }) // switch HEAD
await git.deleteBranch({ ref: "feat-x" }) // delete a branch (never main / the current branch)
await git.merge({ theirs: "feat-x", // LOCAL merge (ff + clean 3-way)
author: { name: "Agent", email: "agent@unfolder.space" } })
await git.rm({ filepath: "old.ts", dir: "/" })
await git.diff({ dir: "/" })
init/clone/fetch/pull/push/remote exist but are for real remotes
(e.g. GitHub) — a space has no remote by default, so use git.merge (not
fetch/pull/push) to move commits between local branches. Full promote flow:
workflow.
host.* — build, deploy, previews
await host.build() // bundle HEAD → { commitSha, mainModule, moduleCount, assetCount, warnings }; refuses dirty / no-commits
await host.deploy() // build + deploy the CHECKED-OUT branch → { target, branch, facetName, url, … }; main → production, else a stable preview
await host.listPreviews() // → [{ branch, previewId, facetName, commitSha, url }]
await host.stopPreview({ branch: "feat-x" }) // tear down a preview facet (the git branch is left intact)
deploy targets whichever branch is checked out (git.branch({ list: true })'s
current). Commit and ship in one call:
async () => {
await git.add({ filepath: "." });
await git.commit({ message: "feat: x", author: { name: "Agent", email: "agent@unfolder.space" } });
return await host.deploy();
}
Custom domains are separate top-level tools (set_custom_domain /
get_custom_domain / remove_custom_domain), not part of host.*.
config.* — asset config, variables, secrets, egress
await config.getAssetConfig() // → AssetConfig | null
await config.setAssetConfig({ /* headers, redirects */ }) // see bundling
await config.setVariable({ name: "API_BASE_URL", value: "https://api.example.com" })
await config.declareSecret({ name: "STRIPE_KEY" }) // owner sets the value in settings
await config.allowEgress({ host: "api.github.com" }) // owner approves in settings
await config.list() // → { variables, egress } — never secret VALUES
await config.remove({ name: "API_BASE_URL" }) // by variable/secret NAME, or rule by HOST
Secret values and host approvals are owner-only, done on the settings page
(get_urls returns the settings URL). Secrets and egress are independent — a
secret is injected into env (owner sets the value); outbound hosts are a separate
allow/deny list. Full model: secrets and
bundling.
media.* — durable assets served by URL
await media.list() // → [{ path, size, uploaded, contentType }]
await media.list("photos/") // filter by prefix
await media.read("notes.txt") // → text, or null
await media.write("data/site.json", "{}", "application/json")
await media.remove("old.png")
await media.url("photos/hero.jpg") // public → https://<your-space-slug>.<domain>/photos/hero.jpg
await media.url("_private/deck.pdf", { expiresIn: 600 }) // private (`_*`) → signed, time-limited URL
For binary uploads, don't pass bytes here — call get_urls for a
sessionUpload URL and PUT out-of-band. Precedence and private (_*) media:
media.
introspect.* — probe the deployed facet
Read deployed SQLite
await introspect.query({ sql: "SELECT id, body FROM notes ORDER BY id" })
await introspect.query({ sql: "SELECT * FROM notes WHERE id = ?", params: [1] })
await introspect.query({ sql: "PRAGMA table_list", facet: "production" })
Read-only (a single SELECT/PRAGMA/EXPLAIN) against the deployed facet's
runtime DB — this is the only way to read it from run_code, since state.* is
the source Workspace, not the live worker. Writes go through your worker's HTTP
API. Requires the deployed App to keep the scaffold's __query export.
Background: dynamic-worker.
Self-test custom admin views
await introspect.adminFetch("/forms") // GET your __adminFetch view
await introspect.adminFetch("/settings", { method: "POST", // POST a form (new Request init)
headers: { "content-type": "application/x-www-form-urlencoded" }, body: "name=hi" })
await introspect.adminFetch("/forms", undefined, "preview:ab12") // probe a preview facet
Probes your facet's custom admin view hook (__adminFetch) the same way the
platform admin does — but from the sandbox, with no interactive owner login —
so you can verify a view renders before handing it off. The arguments are the
standard new Request(input, init) pair plus an optional third facet (defaults
to production); a bare path like /forms is fine — the host is ignored, only the
path + query reach your hook. To probe a preview, pass the facetName that
host.deploy() / host.listPreviews() hand back (e.g. preview:ab12) — no need
to build the preview: name yourself. The probe runs as the space owner.
Returns the response serialized to JSON — { ok, facet, status, statusText, headers, body, truncated } (body is text, capped) — not a Response. A facet
with no __adminFetch hook → { ok: false, code: "UNSUPPORTED" }; an
unknown/not-live facet → NOT_DEPLOYED. Add the hook per
admin-prep.