{"_id":"@cloudflare/computer","_rev":"5-070ada9fff4314d0f70f4655a643574e","name":"@cloudflare/computer","dist-tags":{"alpha":"0.1.0-alpha.1","latest":"0.1.1"},"versions":{"0.0.0":{"name":"@cloudflare/computer","version":"0.0.0","author":"","license":"ISC","_id":"@cloudflare/computer@0.0.0","maintainers":[{"name":"cf-ci-write","email":"npm-readonly@cloudflare.com"},{"name":"dash_service_account","email":"service-account@cloudflare-ui.com"},{"name":"wrangler-publisher","email":"workers-devprod@cloudflare.com"},{"name":"cfpriveng","email":"msang@cloudflare.com"},{"name":"cf-npm-publish","email":"ui-platform-production@cloudflare.com"}],"dist":{"shasum":"1a9f26ed32f034ed7f92b533ec744ab788167d3a","tarball":"https://registry.npmjs.org/@cloudflare/computer/-/computer-0.0.0.tgz","fileCount":1,"integrity":"sha512-TS8IsT6OhpilE77/RpPFpoxQFxjXXv2vkM/qZRqeTY5cuMf2Yr1L49N4UfHs57Q9TPzWzraRJ0KuG58eaQSjMg==","signatures":[{"sig":"MEQCIFx8WABBXSpVSo82nxafVIQs/NVAeLHGkNSJZ0yFyeE5AiAnwdb3jlaivgsVLe1eeShk10PFzNcmQGrbU2EojCWV5g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":238},"main":"index.js","type":"commonjs","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"_npmUser":{"name":"wrangler-publisher","email":"workers-devprod@cloudflare.com"},"_npmVersion":"11.9.0","description":"","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/computer_0.0.0_1785332949636_0.2046386785285108","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.1":{"name":"@cloudflare/computer","version":"0.1.0-alpha.1","license":"MIT","_id":"@cloudflare/computer@0.1.0-alpha.1","maintainers":[{"name":"cf-ci-write","email":"npm-readonly@cloudflare.com"},{"name":"dash_service_account","email":"service-account@cloudflare-ui.com"},{"name":"wrangler-publisher","email":"workers-devprod@cloudflare.com"},{"name":"cfpriveng","email":"msang@cloudflare.com"},{"name":"cf-npm-publish","email":"ui-platform-production@cloudflare.com"}],"homepage":"https://github.com/cloudflare/computer#readme","bugs":{"url":"https://github.com/cloudflare/computer/issues"},"dist":{"shasum":"7289b606a6a7f54d5946714d588167a92a01c8e5","tarball":"https://registry.npmjs.org/@cloudflare/computer/-/computer-0.1.0-alpha.1.tgz","fileCount":40,"integrity":"sha512-oZ0SvnYeNqs5nt2Qe8CkakuPHjpEQUtY165iIQhaaBig4OR8Fvk7jKjLFZt7pf9D2evuu9stnIQdSTUvC/yB4w==","signatures":[{"sig":"MEQCIGeO6/WZoHq75plFvHaXX/bf7KAXgwhbUlCt8rpKoehkAiBQ8Cp45MooM+iiwHlpeO5s9W2I1QoVeIE9QHic5k+Byw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cloudflare%2fcomputer@0.1.0-alpha.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":135991475},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./git":{"types":"./dist/git.d.ts","import":"./dist/git.js"},"./tools":{"types":"./dist/tools/index.d.ts","import":"./dist/tools/index.js"},"./assets":{"types":"./dist/assets/index.d.ts","import":"./dist/assets/index.js"},"./artifacts":{"types":"./dist/artifacts/index.d.ts","import":"./dist/artifacts/index.js"},"./backends/worker":{"types":"./dist/backends/worker/index.d.ts","import":"./dist/backends/worker/index.js"},"./backends/container":{"types":"./dist/backends/container/index.d.ts","import":"./dist/backends/container/index.js"},"./observe/cloudflare":{"types":"./dist/observe/cloudflare.d.ts","import":"./dist/observe/cloudflare.js"}},"gitHead":"48aa155e2141f4f1349d9393affd8e9fca991d0e","private":false,"scripts":{"test":"vitest run && vitest run --config vitest.config.proxy.ts && vitest run --config vitest.config.worker-backend.ts && vitest run --config vitest.config.stub-soak.ts","build":"rolldown -c","prepare":"npm run build:shell-bundle","pretest":"npm run build:shell-bundle","prebuild":"npm run build:deps && npm run build:shell-bundle","typecheck":"tsc -p tsconfig.build.json --noEmit","build:deps":"npm run build --workspace @cloudflare/computer-rpc","pretypecheck":"npm run build:deps && npm run build:shell-bundle","test:harness":"./test-harness/run-harness.sh","bench:harness":"./test-harness/run-harness.sh bench","prepublishOnly":"npm run build && npm test","test:stub-soak":"vitest run --config vitest.config.stub-soak.ts","build:shell-bundle":"node ./src/backends/worker/script/build-bundle.mjs","test:worker-backend":"vitest run --config vitest.config.worker-backend.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:e09e2ed7-4455-453f-9ac2-2d4ef45e0e39"}},"repository":{"url":"git+https://github.com/cloudflare/computer.git","type":"git","directory":"packages/computer"},"_npmVersion":"11.16.0","description":"Cloudflare Computer — a SQLite-backed virtual filesystem with sync to a container-side daemon (computerd).","directories":{},"_nodeVersion":"24.18.0","dependencies":{"capnweb":"^0.8.0","just-bash":"^3.0.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"ai":"^7.0.0","zod":"^4.4.3","diff":"^9.0.0","memfs":"^4.57.6","vitest":"^4.1.7","esbuild":"^0.28.1","rolldown":"^1.0.2","wrangler":"^4.107.1","typescript":"^6.0.3","isomorphic-git":"^1.38.3","@cloudflare/dofs":"*","@platformatic/vfs":"^0.4.0","rolldown-plugin-dts":"^0.25.2","@cloudflare/computer-rpc":"*","@cloudflare/workers-types":"^4.20260616.1","@cloudflare/vitest-pool-workers":"^0.16.10"},"peerDependencies":{"ai":"^6.0.196 || ^7.0.0","zod":"^4.4.3","isomorphic-git":"^1.27.0","@platformatic/vfs":"*"},"peerDependenciesMeta":{"ai":{"optional":true},"zod":{"optional":true},"isomorphic-git":{"optional":true},"@platformatic/vfs":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/computer_0.1.0-alpha.1_1785418280297_0.43836057210871426","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@cloudflare/computer","version":"0.1.0","license":"MIT","_id":"@cloudflare/computer@0.1.0","maintainers":[{"name":"cf-ci-write","email":"npm-readonly@cloudflare.com"},{"name":"dash_service_account","email":"service-account@cloudflare-ui.com"},{"name":"wrangler-publisher","email":"workers-devprod@cloudflare.com"},{"name":"cfpriveng","email":"msang@cloudflare.com"},{"name":"cf-npm-publish","email":"ui-platform-production@cloudflare.com"}],"homepage":"https://github.com/cloudflare/computer#readme","bugs":{"url":"https://github.com/cloudflare/computer/issues"},"dist":{"shasum":"7612f4b2967c37ee63434f7754a1c1402fbf661b","tarball":"https://registry.npmjs.org/@cloudflare/computer/-/computer-0.1.0.tgz","fileCount":46,"integrity":"sha512-2aNIhgdD4TXq92CbM9ciW7TZleivXAB7le5nEymjtnt5kl9JpiC7iADw/bNPfwpsuvNfNj+TgFQdQKs2CP35vQ==","signatures":[{"sig":"MEQCIHnmxit1VIS7RItDx7unt/g5Fuxc7h5CRxAHCQYQ+89uAiAmtrzCznwyfSw+PmONrxZCgtThJvoX8e5Vrcih1Iw6eQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cloudflare%2fcomputer@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":138430165},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./git":{"types":"./dist/git.d.ts","import":"./dist/git.js"},"./tools":{"types":"./dist/tools/index.d.ts","import":"./dist/tools/index.js"},"./assets":{"types":"./dist/assets/index.d.ts","import":"./dist/assets/index.js"},"./artifacts":{"types":"./dist/artifacts/index.d.ts","import":"./dist/artifacts/index.js"},"./backends/container":{"types":"./dist/backends/container/index.d.ts","import":"./dist/backends/container/index.js"},"./observe/cloudflare":{"types":"./dist/observe/cloudflare.d.ts","import":"./dist/observe/cloudflare.js"},"./backends/worker-shell":{"types":"./dist/backends/worker-shell/index.d.ts","import":"./dist/backends/worker-shell/index.js"},"./backends/worker-javascript":{"types":"./dist/backends/worker-javascript/index.d.ts","import":"./dist/backends/worker-javascript/index.js"}},"gitHead":"6f96d50c382ca4447e3587a02358fb03136fd9ff","private":false,"scripts":{"test":"vitest run && vitest run --config vitest.config.proxy.ts && vitest run --config vitest.config.worker-backend.ts && vitest run --config vitest.config.script-runner.ts && vitest run --config vitest.config.stub-soak.ts","build":"rolldown -c","prepare":"npm run build:shell-bundle","pretest":"npm run build:shell-bundle","prebuild":"npm run build:deps && npm run build:shell-bundle","test:code":"vitest run --config vitest.config.script-runner.ts","typecheck":"tsc -p tsconfig.build.json --noEmit","build:deps":"npm run build --workspace @cloudflare/computer-rpc","pretypecheck":"npm run build:deps && npm run build:shell-bundle","test:harness":"./test-harness/run-harness.sh","bench:harness":"./test-harness/run-harness.sh bench","prepublishOnly":"npm run build && npm test","test:stub-soak":"vitest run --config vitest.config.stub-soak.ts","build:shell-bundle":"node ./src/backends/worker-shell/script/build-bundle.mjs","test:worker-backend":"vitest run --config vitest.config.worker-backend.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:e09e2ed7-4455-453f-9ac2-2d4ef45e0e39"}},"repository":{"url":"git+https://github.com/cloudflare/computer.git","type":"git","directory":"packages/computer"},"_npmVersion":"11.16.0","description":"Cloudflare Computer — a SQLite-backed virtual filesystem with sync to a container-side daemon (computerd).","directories":{},"_nodeVersion":"24.18.0","dependencies":{"acorn":"^8.17.0","capnweb":"^0.8.0","just-bash":"^3.0.1"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^7.0.0","zod":"^4.4.3","diff":"^9.0.0","memfs":"^4.57.6","vitest":"^4.1.7","esbuild":"^0.28.1","rolldown":"^1.0.2","wrangler":"^4.107.1","typescript":"^6.0.3","isomorphic-git":"^1.38.3","@cloudflare/dofs":"*","@platformatic/vfs":"^0.4.0","rolldown-plugin-dts":"^0.25.2","@cloudflare/computer-rpc":"*","@cloudflare/workers-types":"^4.20260616.1","@cloudflare/vitest-pool-workers":"^0.16.10"},"peerDependencies":{"ai":"^6.0.196 || ^7.0.0","zod":"^4.4.3","@platformatic/vfs":"*"},"peerDependenciesMeta":{"ai":{"optional":true},"zod":{"optional":true},"@platformatic/vfs":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/computer_0.1.0_1785759560164_0.02444141357903984","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@cloudflare/computer","version":"0.1.1","description":"Cloudflare Computer — a SQLite-backed virtual filesystem with sync to a container-side daemon (computerd).","license":"MIT","repository":{"directory":"packages/computer","type":"git","url":"git+https://github.com/cloudflare/computer.git"},"private":false,"type":"module","publishConfig":{"tag":"unreleased"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./git":{"types":"./dist/git.d.ts","import":"./dist/git.js"},"./assets":{"types":"./dist/assets/index.d.ts","import":"./dist/assets/index.js"},"./artifacts":{"types":"./dist/artifacts/index.d.ts","import":"./dist/artifacts/index.js"},"./tools":{"types":"./dist/tools/index.d.ts","import":"./dist/tools/index.js"},"./backends/container":{"types":"./dist/backends/container/index.d.ts","import":"./dist/backends/container/index.js"},"./backends/worker-javascript":{"types":"./dist/backends/worker-javascript/index.d.ts","import":"./dist/backends/worker-javascript/index.js"},"./backends/worker-shell":{"types":"./dist/backends/worker-shell/index.d.ts","import":"./dist/backends/worker-shell/index.js"},"./observe/cloudflare":{"types":"./dist/observe/cloudflare.d.ts","import":"./dist/observe/cloudflare.js"}},"main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"build:deps":"npm run build --workspace @cloudflare/computer-rpc","build:shell-bundle":"node ./src/backends/worker-shell/script/build-bundle.mjs","prebuild":"npm run build:deps && npm run build:shell-bundle","pretest":"npm run build:shell-bundle","pretypecheck":"npm run build:deps && npm run build:shell-bundle","prepare":"npm run build:shell-bundle","build":"rolldown -c","typecheck":"tsc -p tsconfig.build.json --noEmit","test":"vitest run && vitest run --config vitest.config.proxy.ts && vitest run --config vitest.config.worker-backend.ts && vitest run --config vitest.config.script-runner.ts && vitest run --config vitest.config.stub-soak.ts","test:stub-soak":"vitest run --config vitest.config.stub-soak.ts","test:worker-backend":"vitest run --config vitest.config.worker-backend.ts","test:code":"vitest run --config vitest.config.script-runner.ts","test:harness":"./test-harness/run-harness.sh","bench:harness":"./test-harness/run-harness.sh bench","prepublishOnly":"npm run build && npm test"},"dependencies":{"acorn":"^8.17.0","capnweb":"^0.8.0","just-bash":"^3.0.1"},"peerDependencies":{"@platformatic/vfs":"*","ai":"^6.0.196 || ^7.0.0","zod":"^4.4.3"},"peerDependenciesMeta":{"@platformatic/vfs":{"optional":true},"ai":{"optional":true},"zod":{"optional":true}},"devDependencies":{"@cloudflare/computer-rpc":"*","@cloudflare/dofs":"*","@cloudflare/vitest-pool-workers":"^0.16.10","@cloudflare/workers-types":"^4.20260616.1","@platformatic/vfs":"^0.4.0","ai":"^7.0.0","diff":"^9.0.0","esbuild":"^0.28.1","isomorphic-git":"^1.38.3","memfs":"^4.57.6","rolldown":"^1.0.2","rolldown-plugin-dts":"^0.25.2","typescript":"^6.0.3","vitest":"^4.1.7","wrangler":"^4.107.1","zod":"^4.4.3"},"gitHead":"63d363632e558f7e077794988d36ed75017c2a62","_id":"@cloudflare/computer@0.1.1","bugs":{"url":"https://github.com/cloudflare/computer/issues"},"homepage":"https://github.com/cloudflare/computer#readme","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-4xWx5yX+y5MyNhtIK9N6LcyBsbzGdaxGbhfZdh51zRWRfdkI8OhL2ftBCReqaR6HD7we+Yg0rEihwBETrmkbmg==","shasum":"cb32c2395ed091e8e8185404a3d5a80177601060","tarball":"https://registry.npmjs.org/@cloudflare/computer/-/computer-0.1.1.tgz","fileCount":45,"unpackedSize":11879888,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cloudflare%2fcomputer@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFWXHQ1vKrjAZWyP5bo2aj/9K3iERSCAUvS0LKfqH2TEAiEAx9VsQ7L3G9vcZBqEXDtjyoT9kIwm67IPca5E6e6xoFc="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:e09e2ed7-4455-453f-9ac2-2d4ef45e0e39"}},"directories":{},"maintainers":[{"name":"cf-ci-write","email":"npm-readonly@cloudflare.com"},{"name":"dash_service_account","email":"service-account@cloudflare-ui.com"},{"name":"wrangler-publisher","email":"workers-devprod@cloudflare.com"},{"name":"cfpriveng","email":"msang@cloudflare.com"},{"name":"cf-npm-publish","email":"ui-platform-production@cloudflare.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/computer_0.1.1_1785762082269_0.5529557590603031"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-29T13:49:09.592Z","modified":"2026-08-03T13:01:22.943Z","0.0.0":"2026-07-29T13:49:09.780Z","0.1.0-alpha.1":"2026-07-30T13:31:20.995Z","0.1.0":"2026-08-03T12:19:20.865Z","0.1.1":"2026-08-03T13:01:22.482Z"},"bugs":{"url":"https://github.com/cloudflare/computer/issues"},"license":"MIT","homepage":"https://github.com/cloudflare/computer#readme","repository":{"directory":"packages/computer","type":"git","url":"git+https://github.com/cloudflare/computer.git"},"description":"Cloudflare Computer — a SQLite-backed virtual filesystem with sync to a container-side daemon (computerd).","maintainers":[{"name":"cf-ci-write","email":"npm-readonly@cloudflare.com"},{"name":"dash_service_account","email":"service-account@cloudflare-ui.com"},{"name":"wrangler-publisher","email":"workers-devprod@cloudflare.com"},{"name":"cfpriveng","email":"msang@cloudflare.com"},{"name":"cf-npm-publish","email":"ui-platform-production@cloudflare.com"}],"readme":"# `@cloudflare/computer`\n\nA persistent, SQLite-backed virtual filesystem for Durable Objects,\nwith pluggable command and code execution. Built for agents that need a\nsmall, portable working directory and the tools to read, write, and run\nthings in it.\n\n> [!IMPORTANT]\n> **PREVIEW ONLY.** This package is provided as a preview for feedback\n> only. APIs are unstable and the design is subject to change. Suitable\n> for experiments, exploration, and prototypes. It is NOT suitable for\n> production use at this time.\n\n## What you get\n\n- **A filesystem in your Durable Object.** `workspace.fs` looks like\n  `node:fs/promises` — `readFile`, `writeFile`, `mkdir`, `readdir`,\n  `rm`, `grep` — and is durable across DO restarts, backed by the DO's\n  own SQLite storage.\n- **One execution surface, several backends.**\n  `workspace.runtime.exec()` runs a shell command or an ECMAScript\n  module. Pick a full Linux container, a fast in-Worker shell, or an\n  isolated JavaScript runtime, all against the same files.\n- **Batteries for agents.** Ready-made [AI SDK](https://github.com/vercel/ai)\n  tools (`read`, `write`, `edit`, `ls`, `exec`), a git client, R2-backed\n  read-only mounts, and helpers for publishing files.\n\nThe Workspace can also run with no execution backend at all, giving you\njust the filesystem.\n\n### Limits\n\n- ~10 GB per workspace (it shares storage with the DO).\n- The container-side filesystem is held in memory. Aim for agent-scale\n  workspaces, not full monorepos.\n- Container access goes through FUSE, so heavy I/O (large `node_modules`\n  installs, big tarball extractions) is slower than a native disk. See\n  [`docs/19_performance.md`](../../docs/19_performance.md).\n\n## Installation\n\n```sh\nnpm install @cloudflare/computer\n```\n\nYour Worker needs the `nodejs_compat` compatibility flag. The\nworker-shell and worker-javascript backends additionally need the\n`experimental` flag and a Worker Loader binding. Each backend has its\nown binding requirements — see [Choosing a backend](#choosing-a-backend).\n\nOptional peer dependencies, installed only if you use the matching\nfeature: `ai` and `zod` (for `@cloudflare/computer/tools`),\n`@platformatic/vfs` (for the Node-side VFS provider).\n\n## Quick start\n\nThe smallest useful thing is a filesystem with no execution backend. Add\n`withWorkspace` to a Durable Object and you have durable files:\n\n```ts\nimport { withWorkspace, getWorkspace } from \"@cloudflare/computer\";\nimport { DurableObject } from \"cloudflare:workers\";\n\nexport class Agent extends withWorkspace(\n  class extends DurableObject<Env> {},\n  (self) => ({ storage: self.ctx.storage }),\n) {}\n\nexport default {\n  async fetch(request: Request, env: Env): Promise<Response> {\n    const id = env.Agent.idFromName(\"user-123\");\n    using ws = await getWorkspace(env.Agent.get(id));\n\n    await ws.fs.writeFile(\"/notes.md\", \"- [ ] ship it\\n\");\n    const notes = await ws.fs.readFile(\"/notes.md\", \"utf8\");\n\n    return new Response(notes);\n  },\n} satisfies ExportedHandler<Env>;\n```\n\n`wrangler.jsonc`:\n\n```jsonc\n{\n  \"compatibility_flags\": [\"nodejs_compat\"],\n  \"durable_objects\": {\n    \"bindings\": [{ \"name\": \"Agent\", \"class_name\": \"Agent\" }]\n  },\n  \"migrations\": [\n    { \"tag\": \"v1\", \"new_sqlite_classes\": [\"Agent\"] }\n  ]\n}\n```\n\nTo run commands against those files, add an execution backend. The\nworker-shell backend needs no container and no Docker, so it's the\nquickest way to get `exec` working:\n\n```ts\nimport { withWorkspace, getWorkspace } from \"@cloudflare/computer\";\nimport { WorkerShellBackend } from \"@cloudflare/computer/backends/worker-shell\";\nimport { DurableObject } from \"cloudflare:workers\";\n\nexport class Agent extends withWorkspace(\n  class extends DurableObject<Env> {},\n  (self) => ({\n    storage: self.ctx.storage,\n    backends: [\n      new WorkerShellBackend({\n        loader: self.env.LOADER,\n        workspace: { binding: \"Agent\", id: self.ctx.id.toString() },\n        ctx: self.ctx,\n      }),\n    ],\n  }),\n) {}\n```\n\nAdd the loader binding and the `experimental` flag to `wrangler.jsonc`:\n\n```jsonc\n{\n  \"compatibility_flags\": [\"nodejs_compat\", \"experimental\"],\n  \"worker_loaders\": [{ \"binding\": \"LOADER\" }]\n}\n```\n\nNow `exec` runs against the same files your `fs` calls wrote:\n\n```ts\nusing ws = await getWorkspace(env.Agent.get(id));\nawait ws.fs.writeFile(\"/hello.txt\", \"world\");\nusing run = await ws.runtime.exec(\"cat /hello.txt\");\nconst { stdout, exitCode } = await run.result();\n```\n\nFor a full, runnable version of this see\n[`examples/worker-shell`](../../examples/worker-shell).\n\n## The filesystem\n\n`workspace.fs` is async, uses absolute paths, and is durable across DO\nrestarts. Strings default to UTF-8; pass a `Uint8Array` or a\n`ReadableStream` for binary content.\n\n```ts\n// Write a string, bytes, or a stream straight to disk.\nawait ws.fs.writeFile(\"/notes/todo.md\", \"- [ ] ship it\\n\");\nawait ws.fs.writeFile(\"/data/blob.bin\", new Uint8Array([1, 2, 3]));\nawait ws.fs.writeFile(\"/uploads/big.csv\", request.body!);\n\n// Read back as a string or as a stream.\nconst todo = await ws.fs.readFile(\"/notes/todo.md\", \"utf8\");\nconst stream = await ws.fs.readFile(\"/uploads/big.csv\");\nreturn new Response(stream);\n\n// Directories.\nawait ws.fs.mkdir(\"/notes/daily\", { recursive: true });\nfor (const entry of await ws.fs.readdir(\"/notes\")) {\n  console.log(entry.isDirectory ? `d ${entry.name}` : `f ${entry.name}`);\n}\n\n// Remove and search.\nawait ws.fs.rm(\"/notes/daily\", { recursive: true });\nconst hits = await ws.fs.grep(\"TODO\", \"/\", { ignoreCase: true });\n```\n\nSee [`docs/04_filesystem_interface.md`](../../docs/04_filesystem_interface.md)\nfor the full surface.\n\n### Read-only mounts\n\nPre-fill part of the tree from an R2 bucket. Files under the mount point\nare read-only; writes reject with `EROFS`.\n\n```ts\nimport { R2Bucket } from \"@cloudflare/computer\";\n\nnew Workspace({\n  storage: ctx.storage,\n  mounts: { \"/workspace/r2\": R2Bucket(env.Bucket) },\n});\n```\n\n## Running commands and code\n\n`workspace.runtime.exec(source, options)` is the single execution entry\npoint. What `source` means depends on the backend: a shell command for\nthe container and worker-shell backends, an ECMAScript module for the\nworker-javascript backend.\n\n```ts\nusing run = await ws.runtime.exec(\"ls -la /workspace\", { encoding: \"utf8\" });\nconst { stdout, stderr, exitCode } = await run.result();\n```\n\nThe handle is also a `ReadableStream` of live events, so you can forward\noutput as it happens — for example, as Server-Sent Events:\n\n```ts\nasync fetch(request: Request) {\n  const run = await ws.runtime.exec(\"npm test\", { encoding: \"utf8\" });\n\n  const sse = run.pipeThrough(\n    new TransformStream({\n      transform(event, controller) {\n        const frame = `event: ${event.name}\\ndata: ${JSON.stringify(event.value)}\\n\\n`;\n        controller.enqueue(new TextEncoder().encode(frame));\n      },\n    }),\n  );\n\n  return new Response(sse, {\n    headers: { \"content-type\": \"text/event-stream\", \"cache-control\": \"no-cache\" },\n  });\n}\n```\n\nAlongside `exec`, the runtime exposes `getExec`, `killExec`, and\n`disposeExec`. See\n[`docs/05_runtime_interface.md`](../../docs/05_runtime_interface.md).\n\n### Choosing a backend\n\n| Backend | Import | Runs | Needs |\n| --- | --- | --- | --- |\n| **Container** | `@cloudflare/computer/backends/container` | Shell commands in full Linux userland (real binaries, `npm`, `node`, network) | A Cloudflare Container running `computerd` |\n| **Worker shell** | `@cloudflare/computer/backends/worker-shell` | Shell commands via [just-bash](https://github.com/vercel-labs/just-bash) in a Dynamic Worker | A Worker Loader binding; `experimental` flag |\n| **Worker JavaScript** | `@cloudflare/computer/backends/worker-javascript` | ECMAScript modules in a fresh Dynamic Worker | A Worker Loader binding; `experimental` flag |\n\n- **Container** cold-starts more slowly but gives you a real Linux\n  environment. The container owns its own SQLite-backed VFS and this\n  package syncs the two stores across a capnweb WebSocket. See\n  [`docs/07_injected_service.md`](../../docs/07_injected_service.md) for\n  the container image, and [`examples/container`](../../examples/container).\n- **Worker shell** is fast and needs no container. Every filesystem\n  operation forwards back to the same Durable Object, so there's no\n  second store and no sync round trip. See\n  [`docs/12_worker_backend.md`](../../docs/12_worker_backend.md) and\n  [`examples/worker-shell`](../../examples/worker-shell).\n- **Worker JavaScript** evaluates a module with structured\n  input/results, durable relative imports, configured libraries,\n  Workspace-backed `node:fs/promises`, and trusted `ws:git` /\n  `ws:artifacts` modules. It runs after `runtime.exec()` returns, so\n  pass `waitUntil: ctx.waitUntil.bind(ctx)` to `Workspace`; the backend\n  refuses to connect without it. See\n  [`docs/17_isolate_javascript.md`](../../docs/17_isolate_javascript.md)\n  and [`examples/worker-javascript`](../../examples/worker-javascript).\n\nYou can register several backends on one Workspace and route each call\nto a named one — see [Multiple backends](#multiple-backends).\n\n## Tools for agents\n\n`@cloudflare/computer/tools` ships AI SDK tools that wrap the Workspace\nsurfaces, ready to hand to `generateText`, `streamText`, or an agent\nframework's `getTools()`. The default set is `read`, `write`, `edit`,\nand `ls`; `exec` and `publish` are added when you configure them.\n\n```ts\nimport { createAITools } from \"@cloudflare/computer/tools\";\n\nconst tools = createAITools({\n  workspace,\n  read: { maxBytes: 32 * 1024, maxLines: 800 },\n  shell: {\n    defaultBackend: \"shell\",\n    backends: {\n      shell: { description: \"Fast Worker shell with built-in text commands.\" },\n      container: { description: \"Full Linux userland in a Cloudflare Container.\" },\n    },\n  },\n});\n```\n\nThe model reads each backend's `description` when deciding where a\ncommand should run, so write them in plain language. See\n[`docs/09_tool_interface.md`](../../docs/09_tool_interface.md).\n\n## Git\n\n`workspace.git` is an opt-in typed git client backed by\n[`isomorphic-git`](https://github.com/isomorphic-git/isomorphic-git),\noperating directly on the local SQLite VFS — no backend or shell\nrequired. Enable it by passing `createGitClient()` from\n`@cloudflare/computer/git`:\n\n```ts\nimport { Workspace } from \"@cloudflare/computer\";\nimport { createGitClient } from \"@cloudflare/computer/git\";\n\nconst ws = new Workspace({\n  storage: ctx.storage,\n  git: createGitClient(),\n  defaultGitIdentity: { name: \"Agent\", email: \"agent@example.test\" },\n});\n\nawait ws.git.clone({ url: \"https://github.com/example/repo.git\" });\nawait ws.fs.writeFile(\"/notes.md\", \"hello\");\nawait ws.git.add({ paths: [\"notes.md\"] });\nawait ws.git.commit({ message: \"add notes\" });\n```\n\nThe git subpath bundles `isomorphic-git` lazily and swaps its `pako`\ndependency for the Workers `node:zlib` implementation, so the default\npackage graph stays free of git. There's also an argv entry point\n(`workspace.git.cli({ argv })`), and when git is configured the\nworker-shell backend exposes a built-in `git` command. See\n[`docs/13_git_interface.md`](../../docs/13_git_interface.md).\n\n## Sharing files\n\nTwo ways to get a file out of the workspace and into the world:\n\n- **Assets** (`@cloudflare/computer/assets`): `createAssets(...).share`\n  uploads a workspace file to R2 and returns a presigned URL. Attach it\n  through `WorkspaceOptions.assets` to expose an in-shell\n  `assets publish <path> [<expiry>]` command. See\n  [`docs/14_assets_interface.md`](../../docs/14_assets_interface.md).\n- **Artifacts** (`@cloudflare/computer/artifacts`):\n  `createArtifact(binding, sessionId)` is a session-scoped facade over\n  the [Cloudflare Artifacts](https://developers.cloudflare.com/artifacts/)\n  binding. Every repository name is implicitly prefixed with the session\n  id, so one namespace hosts many isolated sessions.\n\n```ts\nimport { createArtifact } from \"@cloudflare/computer/artifacts\";\n\nconst artifacts = createArtifact(env.ARTIFACTS, agentId);\nconst repo = await artifacts.create(\"build-cache\", { description: \"CI artifacts\" });\nconst token = await artifacts.createToken(\"build-cache\", \"read\", 3600);\nconst mine = await artifacts.list(); // only this session's repos\n```\n\nArtifacts also offers an argv CLI (`artifacts.cli({ argv })`), and when\n`Workspace` is configured with an Artifacts binding the worker-shell\nbackend exposes an `artifacts` command. See\n[`docs/15_artifacts_interface.md`](../../docs/15_artifacts_interface.md).\n\n## Crossing the Worker → DO boundary\n\nThe Durable Object owns the Workspace; a Worker reaches it through a\nstub. `withWorkspace` installs the plumbing, and `getWorkspace(stub)`\nreturns a client:\n\n```ts\nexport default {\n  async fetch(request: Request, env: Env): Promise<Response> {\n    const id = env.Agent.idFromName(\"user-123\");\n    using ws = await getWorkspace(env.Agent.get(id));\n\n    await ws.fs.writeFile(\"/notes.md\", \"hello\");\n    using run = await ws.runtime.exec(\"ls /workspace\");\n    const { exitCode, stdout } = await run.result();\n\n    return new Response(stdout, { status: exitCode === 0 ? 200 : 500 });\n  },\n} satisfies ExportedHandler<Env>;\n```\n\n### Dispose your stubs\n\nThe one gotcha worth internalizing: the RPC layer does not\ngarbage-collect remote stubs. On long-lived sessions undisposed stubs\naccumulate on the peer until the session ends. The rules are short:\n\n- `using` the value from `getWorkspace(...)`.\n- `using` the handle from `ws.runtime.exec(...)`.\n- Don't worry about `ws.fs`, `ws.runtime`, or `ws.git` — they ride with\n  the parent.\n- Pure-value returns (`readFile` as a string, `stat`, `readdir`,\n  `git.cli({...})`) carry no stubs; nothing to dispose.\n\nShort-lived single-shot Workers tear the session down with the request,\nso this matters most on long-lived isolates that keep grabbing fresh\nstubs or on busy `exec` workloads. The full contract is in\n[`docs/11_lifecycle.md`](../../docs/11_lifecycle.md#stub-disposal-contract).\n\nTo hunt leaks, set `CAPNWEB_TRACK_STUBS=1` and read `stubSnapshot()`\nfrom `@cloudflare/computer-rpc/debug`, or hit `GET /__computerd/stubs`\non a computerd instance.\n\n## Package entrypoints\n\n| Entrypoint | Purpose |\n| --- | --- |\n| `@cloudflare/computer` | The `Workspace` facade, `workspace.runtime`, stub types, the R2 mount, and proxy classes. |\n| `@cloudflare/computer/backends/container` | `CloudflareContainerBackend` and `withWorkspaceContainer`. Pulls in the computerd / capnweb sync plumbing. |\n| `@cloudflare/computer/backends/worker-shell` | `WorkerShellBackend` and the bundled just-bash runtime. |\n| `@cloudflare/computer/backends/worker-javascript` | `WorkerJavaScriptBackend`, configured libraries, durable imports, `node:fs/promises`, and trusted `ws:git` / `ws:artifacts`. |\n| `@cloudflare/computer/tools` | AI SDK tools for agents: `read`, `write`, `edit`, `ls`, optional `exec` and `publish`. |\n| `@cloudflare/computer/git` | Opt-in `isomorphic-git` glue for checkouts inside the workspace. |\n| `@cloudflare/computer/assets` | `createAssets` — share a workspace file to R2 as a presigned URL. |\n| `@cloudflare/computer/artifacts` | `createArtifact` and its CLI, a session-scoped facade over the Cloudflare Artifacts binding. |\n| `@cloudflare/computer/observe/cloudflare` | Cloudflare-runtime adapter for the observability hook. |\n\nA consumer that only uses the container backend never imports the worker\nsubpaths, so unused backend payloads tree-shake away. Wire types shared\nwith the in-container service live in the sibling package\n`@cloudflare/computer-rpc`.\n\n## Advanced\n\n### Multiple backends\n\nA Workspace can carry more than one backend, each registered under a\nstable selector `id` (defaulting to `\"worker-shell\"`,\n`\"container-shell\"`, or `\"worker-javascript\"`). `runtime.exec` uses the\nfirst backend unless the caller names one:\n\n```ts\nconst ws = new Workspace({\n  storage: ctx.storage,\n  backends: [\n    new WorkerShellBackend({ id: \"shell\", loader: env.LOADER, /* ... */ }),\n    new CloudflareContainerBackend({ id: \"sandbox\", container: () => this, /* ... */ }),\n  ],\n});\n\nconst grep = await ws.runtime.exec(\"grep -r TODO /workspace\");  // default: \"shell\"\nconst build = await ws.runtime.exec(\"npm test\", { backend: \"sandbox\" });\n```\n\nBackends connect lazily — the first `exec`, `push`, `pull`, or\n`ready(id)` for an id dials it. `ready({ all: true })` pre-warms every\nbackend, which is handy from an agent's `onStart`. Per-backend sync\ncursors are independent, so activity on one never disturbs the other.\n\nA backend that accepts a structured `input` and returns a structured\n`value` declares `callable: true` (the worker-javascript backend does).\nPassing `input` to a non-callable backend is a clear error rather than a\nsilent drop.\n\n### Constructing without the mixin\n\n`withWorkspace` is the shortcut. You can also construct a `Workspace`\ndirectly and expose it through a stub yourself:\n\n```ts\nconst ws = new Workspace({ storage: ctx.storage, backends: [/* ... */] });\nawait ws.ready();\nconst stub = ws.stub();  // crosses the Workers-RPC boundary\n```\n\nWhen assigning a workspace to a Think agent's `workspace`, pass\n`useThink: true` so Think's compatibility methods are added alongside\n`workspace.fs` and `workspace.runtime`.\n\n### Durable pending-sync retries\n\nA command can change backend files and then have its post-command pull\nfail; the result exposes `sync: { status: \"pending\", ... }`. Configure a\n`SyncRetryScheduler` on `Workspace` to persist one coalesced retry per\nbackend, then call `workspace.retryPendingSync(backend)` from your DO's\nalarm. Retries use bounded exponential backoff and return `\"exhausted\"`\nafter the configured maximum. The library does not own your DO's alarm.\nSee `SyncRetryScheduler`, `SyncRetryIntent`, and `SyncRetryOptions` in\nthe package exports.\n\n### Observability\n\nPass an `observer` to the `Workspace` constructor to receive one span\nper documented operation (`workspace.connect`, `workspace.sync.push`,\n`workspace.sync.pull`, `workspace.runtime.exec.spawn`,\n`workspace.fs.<op>`). The `span(name, attributes, run)` shape fits the\nCloudflare runtime's `ctx.tracing`, OpenTelemetry, or a test recorder;\nattribute values are restricted to `boolean | number | string`. The\ndefault is a zero-cost no-op, so there's no overhead unless you opt in.\nAn adapter for the Cloudflare runtime lives at\n`@cloudflare/computer/observe/cloudflare`.\n\n## Examples\n\n- [`examples/worker-shell`](../../examples/worker-shell) — the\n  worker-shell backend behind a `write` / `read` / `exec` HTTP surface.\n  No container.\n- [`examples/worker-javascript`](../../examples/worker-javascript) — the\n  same shape, running ECMAScript modules instead of shell commands.\n- [`examples/container`](../../examples/container) — the container\n  backend running `computerd`.\n- [`examples/think`](../../examples/think) — a chat agent that uses the\n  workspace as its working directory.\n- [`examples/tutorial`](../../examples/tutorial) — a step-by-step build:\n  write a markdown card on the host, render it to PDF with `pandoc` in\n  the container.\n\n## Documentation\n\nThe design specification lives under [`docs/`](../../docs/README.md).\nStart with [01. VFS](../../docs/01_vfs.md),\n[04. Filesystem Interface](../../docs/04_filesystem_interface.md), and\n[05. Runtime Interface](../../docs/05_runtime_interface.md). It is\nforward-looking — read it for intent, not as a description of the code\ntoday.\n\n## License\n\nMIT. See [`LICENSE`](../../LICENSE).\n","readmeFilename":"README.md"}