Experimental. API surface is still settling — expect breaking changes.
Sandboxed JavaScript execution and filesystem runtime for Cloudflare Workers agents.
Instead of parsing shell syntax, @cloudflare/shell runs JavaScript inside an isolated Worker and exposes a typed state object for operating on a filesystem backend. It is designed for agent workflows that need structured state operations, predictable semantics, and coarse host-side filesystem primitives.
- A runtime-neutral
StateBackendinterface for filesystem/state operations - A
FileSysteminterface with two implementations:InMemoryFs(ephemeral) andWorkspaceFileSystem(durable) FileSystemStateBackend— a single adapter wrapping anyFileSysteminto aStateBackendWorkspace— durable file storage backed by SQLite + optional R2stateTools(workspace)— aToolProviderfor@cloudflare/codemodethat exposesstate.*in sandboxed executionscreateGit(filesystem)— pure-JS git operations via isomorphic-git, backed by the virtual filesystemgitTools(workspace)— aToolProviderfor@cloudflare/codemodethat exposesgit.*in sandboxed executions with auto-injected auth- A prebuilt
statestdlib with type declarations for LLM prompts
This is not a bash interpreter. It does not parse shell syntax, expose pipes, or emulate POSIX shell behavior. It executes JavaScript.
import { createMemoryStateBackend } from "@cloudflare/shell";
import { stateToolsFromBackend } from "@cloudflare/shell/workers";
import { DynamicWorkerExecutor, resolveProvider } from "@cloudflare/codemode";
const backend = createMemoryStateBackend({
files: {
"/src/app.ts": 'export const answer = "foo";\n'
}
});
const executor = new DynamicWorkerExecutor({ loader: env.LOADER });
const result = await executor.execute(
`async () => {
const text = await state.readFile("/src/app.ts");
await state.writeFile("/src/app.ts", text.replace("foo", "bar"));
return await state.readFile("/src/app.ts");
}`,
[resolveProvider(stateToolsFromBackend(backend))]
);import { Agent } from "agents";
import { Workspace } from "@cloudflare/shell";
import { stateTools } from "@cloudflare/shell/workers";
import { DynamicWorkerExecutor, resolveProvider } from "@cloudflare/codemode";
class MyAgent extends Agent<Env> {
workspace = new Workspace({
sql: this.ctx.storage.sql,
r2: this.env.MY_BUCKET,
name: () => this.name
});
async run(code: string) {
const executor = new DynamicWorkerExecutor({ loader: this.env.LOADER });
return executor.execute(code, [
resolveProvider(stateTools(this.workspace))
]);
}
}import { Agent } from "agents";
import { Workspace } from "@cloudflare/shell";
import { WorkspaceFileSystem } from "@cloudflare/shell";
import { createGit } from "@cloudflare/shell/git";
class MyAgent extends Agent<Env> {
workspace = new Workspace({
sql: this.ctx.storage.sql,
name: () => this.name
});
async run() {
const git = createGit(new WorkspaceFileSystem(this.workspace));
await git.clone({ url: "https://github.com/org/repo", depth: 1 });
await this.workspace.writeFile("/README.md", "# Updated");
await git.add({ filepath: "." });
await git.commit({
message: "update readme",
author: { name: "Agent", email: "agent@example.com" }
});
await git.push({ token: this.env.GITHUB_TOKEN });
}
}init, clone, status, add, rm, commit, log, branch, checkout, fetch, pull, push, diff, remote
gitTools(workspace) exposes all git commands inside sandboxed executions. Default auth is auto-injected into clone/fetch/pull/push — the LLM never sees secrets.
import { stateTools } from "@cloudflare/shell/workers";
import { gitTools } from "@cloudflare/shell/git";
const providers = [
resolveProvider(stateTools(this.workspace)),
resolveProvider(gitTools(this.workspace, { token: this.env.GITHUB_TOKEN }))
];For Git servers that require Basic auth, pass hidden default credentials:
const providers = [
resolveProvider(stateTools(this.workspace)),
resolveProvider(
gitTools(this.workspace, {
auth: {
username: "git",
password: this.env.GIT_PASSWORD
}
})
)
];If both auth and token are configured, auth is used. Direct tool-call auth, if supplied, takes precedence over either default.
- Structured state operations instead of shell parsing
- Coarse host-side operations like
glob()anddiff()to avoid chatty RPC - Compatibility with both ephemeral in-memory state and durable
Workspace - Secure execution with isolate-level timeouts and outbound network blocking by default
The state object is available inside every isolate and exposes:
readFile, writeFile, appendFile, readFileBytes, writeFileBytes, mkdir, rm, cp, mv, symlink, readlink, realpath, readdir, glob, stat, lstat, exists, diff, diffContent
readJson(path), writeJson(path, value, { spaces? }), queryJson(path, query), updateJson(path, operations)
searchText(path, query, options?), searchFiles(glob, query, options?), replaceInFile(path, search, replacement, options?), replaceInFiles(glob, search, replacement, { dryRun?, rollbackOnError?, ...options })
find(path, options?), walkTree(path, { maxDepth? }), summarizeTree(path, { maxDepth? })
createArchive(path, sources), listArchive(path), extractArchive(path, destination), compressFile(path, destination?), decompressFile(path, destination?), hashFile(path, { algorithm? }), detectFile(path)
planEdits(instructions), applyEditPlan(plan, { dryRun?, rollbackOnError? }), applyEdits(edits, { dryRun?, rollbackOnError? })
// Preview changes without applying
const preview = await state.replaceInFiles("src/**/*.ts", "foo", "bar", {
dryRun: true
});
// Plan structured edits with intent
const plan = await state.planEdits([
{ kind: "replace", path: "/src/app.ts", search: "foo", replacement: "bar" },
{ kind: "writeJson", path: "/src/config.json", value: { enabled: true } }
]);
await state.applyEditPlan(plan);
// Apply raw edits transactionally
await state.applyEdits([
{ path: "/src/generated.ts", content: "export const generated = true;\n" }
]);Batch writes roll back by default if any write fails. Set rollbackOnError: false to allow partial progress.
| Shell command | state equivalent |
|---|---|
cat |
state.readFile() |
echo x > file |
state.writeFile() |
mkdir |
state.mkdir() |
ls / find |
state.readdir() / state.glob() |
find with filters |
state.find() |
tree / du |
state.walkTree() / state.summarizeTree() |
cp / mv / rm |
state.cp() / state.mv() / state.rm() |
diff |
state.diff() / state.diffContent() |
grep |
state.searchText() / state.searchFiles() |
sed |
state.replaceInFile() / state.replaceInFiles() |
jq |
state.readJson() / state.queryJson() / state.updateJson() |
tar |
state.createArchive() / state.listArchive() / state.extractArchive() |
gzip / gunzip |
state.compressFile() / state.decompressFile() |
sha256sum / file |
state.hashFile() / state.detectFile() |
git |
git.* via @cloudflare/shell/git |
- Stream-oriented transforms inspired by
sort,uniq,comm,cut,paste,tr - Full
rgCLI parity — file types, ignore controls, multiple roots - Richer JSON query semantics closer to
jqfilters - Structured patch helpers covering more of diff / codemod workflows
- A smaller "command" library built on top of
state.*
@cloudflare/codemode: executes sandboxed JavaScript that orchestrates tools@cloudflare/shell: provides filesystem backends andstateTools()ToolProvider for codemode