{"id":50372132,"url":"https://github.com/wantpinow/convex-sandbox","last_synced_at":"2026-06-15T22:00:49.033Z","repository":{"id":351138216,"uuid":"1146272834","full_name":"wantpinow/convex-sandbox","owner":"wantpinow","description":"Persistent bash sandboxes using just-bash and Convex file storage. No VMs.","archived":false,"fork":false,"pushed_at":"2026-04-13T18:09:12.000Z","size":212,"stargazers_count":41,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-13T20:10:01.611Z","etag":null,"topics":["bash","convex","just-bash","sandbox","serverless"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wantpinow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-30T21:12:02.000Z","updated_at":"2026-04-13T19:48:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/wantpinow/convex-sandbox","commit_stats":null,"previous_names":["wantpinow/convex-sandbox"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/wantpinow/convex-sandbox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantpinow%2Fconvex-sandbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantpinow%2Fconvex-sandbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantpinow%2Fconvex-sandbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantpinow%2Fconvex-sandbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wantpinow","download_url":"https://codeload.github.com/wantpinow/convex-sandbox/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wantpinow%2Fconvex-sandbox/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34381762,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","convex","just-bash","sandbox","serverless"],"created_at":"2026-05-30T08:00:19.050Z","updated_at":"2026-06-15T22:00:49.024Z","avatar_url":"https://github.com/wantpinow.png","language":"TypeScript","funding_links":[],"categories":["Integrations"],"sub_categories":[],"readme":"\u003e This repo is WIP. Do not use directly in production.\n\n# Convex Sandbox\n\nPersistent bash sandboxes backed by Convex, using Vercel's [`just-bash`](https://github.com/vercel-labs/just-bash) for in-memory command execution and Convex file storage for filesystem persistence. No VMs or containers involved.\n\nhttps://github.com/user-attachments/assets/15027d2d-e41e-4bbb-84cc-04163ea38207\n\n## Overview\n\n`just-bash` provides a bash interpreter that runs in-process on Node.js with a virtual in-memory filesystem. This project wraps it in a Convex action that loads the filesystem from storage before execution and diffs changes back afterward. The result is stateful, isolated bash environments that persist across invocations using only Convex primitives (database, file storage, and actions).\n\nAn optional AI agent (`@convex-dev/agent`) provides a chat interface that can execute commands in the sandbox via tool calling.\n\n## The execution engine\n\nThe core logic lives in `convex/exec.ts`. Here's what happens on each command:\n\n### 1. Lazy filesystem hydration\n\n```typescript\nconst initialFiles: InitialFiles = {};\nfor (const file of files) {\n  const { storageId } = file;\n  initialFiles[file.path] = async () =\u003e {\n    const blob = await ctx.storage.get(storageId);\n    if (!blob) return \"\";\n    return await blob.text();\n  };\n}\n```\n\nFiles are registered as async callbacks rather than loaded eagerly. `just-bash` only calls the callback when a command actually reads the file, so commands that touch a small number of files don't pay the cost of loading the entire filesystem.\n\n### 2. Filesystem mutation tracking\n\nWe intercept the virtual filesystem's mutating methods (`writeFile`, `appendFile`, `rm`, `mv`, `cp`) to build a diff of what changed during execution:\n\n```typescript\nconst writtenPaths = new Set\u003cstring\u003e();\nconst deletedPaths = new Set\u003cstring\u003e();\n\nconst origWriteFile = fs.writeFile.bind(fs);\nfs.writeFile = async (...fsArgs) =\u003e {\n  const path = fs.resolvePath(bash.getCwd(), fsArgs[0]);\n  writtenPaths.add(path);\n  deletedPaths.delete(path);\n  return origWriteFile(...fsArgs);\n};\n```\n\nThe two sets interact correctly for compound operations: a file that is written then deleted in the same command won't be persisted; a file that is deleted then re-created will be.\n\n### 3. Working directory persistence\n\n`just-bash` resets its internal cwd after each `exec()` call, so `cd` would not persist between commands. We work around this by appending a hidden `pwd` to every command and extracting the result from stdout:\n\n```typescript\nconst CWD_MARKER = \"\\x00__CWD__\\x00\";\nconst result = await bash.exec(\n  `${command}\\n__exit_code=$?\\necho \"${CWD_MARKER}$(pwd)\"\\nexit $__exit_code`\n);\n```\n\nThe marker is stripped from the output, and the extracted path is saved to the session for the next invocation.\n\n### 4. Change persistence\n\nAfter execution, changes are written back to Convex:\n\n- **Written paths**: content is read from the virtual FS, stored as a blob via `ctx.storage.store()`, and the file record is created or updated.\n- **Deleted paths**: the corresponding file record is removed from the database.\n- **CWD changes**: the session record is patched with the new working directory.\n\nEach file version produces a new storage blob. Previous blobs are not deleted during execution (the sandbox delete mutation handles cleanup).\n\n## Data model\n\n| Table | Fields | Purpose |\n|-------|--------|---------|\n| `sandboxes` | `name` | Top-level isolation boundary |\n| `sessions` | `sandboxId`, `cwd` | Persistent working directory per terminal |\n| `files` | `sandboxId`, `path`, `storageId` | Virtual filesystem entries backed by Convex storage |\n| `agentThreads` | `threadId`, `sandboxId`, `sessionId` | Maps AI agent threads to sandboxes |\n\nIndexes enforce path uniqueness per sandbox (`bySandboxIdAndPath`) and support efficient lookups by parent (`bySandboxId`).\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│  Next.js Frontend                                   │\n│  ┌──────────┬──────────────────────┬──────────────┐ │\n│  │ File     │ File Viewer          │ Agent Panel  │ │\n│  │ Tree     │                      │              │ │\n│  │          ├──────────────────────┤              │ │\n│  │          │ Terminal             │              │ │\n│  └──────────┴──────────────────────┴──────────────┘ │\n└───────────────────────┬─────────────────────────────┘\n                        │\n┌───────────────────────┴─────────────────────────────┐\n│  Convex Backend                                     │\n│                                                     │\n│  run.ts ──► exec.ts                                 │\n│              ├─ Hydrate virtual FS from storage      │\n│              ├─ Intercept FS mutations               │\n│              ├─ Execute via just-bash                 │\n│              ├─ Extract cwd from output               │\n│              └─ Persist diff back to storage          │\n│                                                     │\n│  agent.ts ──► exec tool ──► run.ts                  │\n└─────────────────────────────────────────────────────┘\n```\n\n## AI agent\n\nThe agent (`convex/agent.ts`) uses `@convex-dev/agent` with the Vercel AI Gateway (`anthropic/claude-sonnet-4-20250514`). It exposes a single `exec` tool that calls `run.ts`. The agent maintains a persistent session per thread so that directory changes and file modifications carry across tool calls within a conversation.\n\n## Setup\n\n```bash\npnpm install\n\n# Start Convex (deploys functions and generates types)\nnpx convex dev\n\n# Set the AI Gateway API key (required for the agent)\nnpx convex env set AI_GATEWAY_API_KEY \u003cyour-key\u003e\n\n# Start the frontend\npnpm dev\n```\n\n## Scripts\n\n| Command | Description |\n|---------|-------------|\n| `pnpm dev` | Next.js dev server (Turbopack) |\n| `pnpm dev:convex` | Convex dev server |\n| `pnpm build` | Production build |\n| `pnpm test:once` | Run test suite (72 tests) |\n| `pnpm lint` | ESLint |\n| `pnpm typecheck` | TypeScript (frontend + Convex) |\n\n## Project structure\n\n```\nconvex/\n  exec.ts            Core execution engine\n  run.ts             Orchestration action (creates sandbox/session if needed)\n  sandbox.ts         Sandbox CRUD (cascading deletes)\n  session.ts         Session CRUD\n  file.ts            File CRUD with path uniqueness + content URL query\n  agent.ts           AI agent definition and exec tool\n  agentQueries.ts    Thread/message queries for the chat UI\n  schema.ts          Database schema\n  convex.config.ts   Component registration (@convex-dev/agent)\n  *.test.ts          Tests\n\napp/\n  page.tsx                   Sandbox list (create, delete)\n  [sandboxId]/page.tsx       IDE layout (file tree, viewer, terminal, agent)\n\ncomponents/\n  file-tree.tsx       Nested file tree with selection state\n  file-viewer.tsx     Text file display with line numbers\n  terminal.tsx        Terminal with session management\n  agent-panel.tsx     Chat panel with streaming and tool call rendering\n```\n\n## Dependencies\n\n| Package | Role |\n|---------|------|\n| [`just-bash`](https://github.com/vercel-labs/just-bash) | In-memory bash interpreter with virtual filesystem |\n| [`convex`](https://convex.dev) | Database, file storage, serverless functions |\n| [`@convex-dev/agent`](https://github.com/get-convex/agent) | AI agent with tool calling |\n| [`@ai-sdk/gateway`](https://sdk.vercel.ai) | Vercel AI Gateway provider |\n| [`react-resizable-panels`](https://github.com/bvaughn/react-resizable-panels) | Resizable panel layout |\n| [`convex-test`](https://docs.convex.dev/testing) | Convex function testing |\n\n## License\n\nApache 2.0. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwantpinow%2Fconvex-sandbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwantpinow%2Fconvex-sandbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwantpinow%2Fconvex-sandbox/lists"}