diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c342e3f..fdf2a2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Run tests - working-directory: sandboxes/js + working-directory: wasm-sandboxes/js run: pnpm vitest run python-sandbox-tests: @@ -57,9 +57,9 @@ jobs: python-version: '3.x' - name: Install Python dependencies - working-directory: sandboxes/python + working-directory: wasm-sandboxes/python run: pip install -r requirements.txt -q - name: Run tests - working-directory: sandboxes/python + working-directory: wasm-sandboxes/python run: pnpm vitest run diff --git a/packages/bash-types/src/bash.ts b/packages/bash-types/src/bash.ts index 8b0d8ed..860b99b 100644 --- a/packages/bash-types/src/bash.ts +++ b/packages/bash-types/src/bash.ts @@ -6,6 +6,11 @@ export interface BashOptions { */ runtime: BaseRuntime; + /** + * The host workspace directory + */ + hostWorkspace?: string; + /** * The initial working directory */ diff --git a/packages/bash-types/src/runtime.ts b/packages/bash-types/src/runtime.ts index 098802b..97ad979 100644 --- a/packages/bash-types/src/runtime.ts +++ b/packages/bash-types/src/runtime.ts @@ -1,23 +1,26 @@ +import { State } from "./state"; + export interface BaseRuntime { + /** - * Execute a command + * The host workspace directory */ - executeCommand(code: string): Promise; + hostWorkspace?: string; /** * Execute a code */ - executeCode(language: string, code: string): Promise; + executeCode(state: State, code: string, language: string): Promise; /** * Execute a file */ - executeFile(language: string, filePath: string): Promise; + executeFile(state: State, filePath: string, language: string): Promise; /** * Resolve a directory path */ - resolveDirectoryPath(directoryPath: string): Promise; + resolveDirectoryPath(state: State, directoryPath: string): Promise; } export interface RuntimeResult { diff --git a/packages/bash-types/src/state.ts b/packages/bash-types/src/state.ts index 92aaa83..1b60f3a 100644 --- a/packages/bash-types/src/state.ts +++ b/packages/bash-types/src/state.ts @@ -16,5 +16,4 @@ export interface State { * The return code of the last executed command (ex: 0 for success, 1 for error). */ lastExitCode: number; - } diff --git a/packages/bash-wasm/package.json b/packages/bash-wasm/package.json index 79bb7c0..d3fdd58 100644 --- a/packages/bash-wasm/package.json +++ b/packages/bash-wasm/package.json @@ -12,6 +12,8 @@ "tsup": "^8.0.0" }, "dependencies": { + "@capsule-run/cli": "^0.8.5", + "@capsule-run/sdk": "^0.8.5", "@capsule-run/bash-types": "workspace:*" } } diff --git a/packages/bash-wasm/src/runtime.ts b/packages/bash-wasm/src/runtime.ts index 17e5a8a..d234444 100644 --- a/packages/bash-wasm/src/runtime.ts +++ b/packages/bash-wasm/src/runtime.ts @@ -1,21 +1,70 @@ -import type { BaseRuntime, RuntimeResult } from "@capsule-run/bash-types"; +import path from "path"; +import fs from "fs"; + +import { run } from '@capsule-run/sdk/runner'; + +import type { BaseRuntime, State } from "@capsule-run/bash-types"; + export class WasmRuntime implements BaseRuntime { - constructor() {} + private jsSandbox: string; + private pythonSandbox: string; - async executeCommand(code: string): Promise { - throw new Error("Method not implemented."); + public hostWorkspace: string = ""; + + constructor() { + const jsWasmPath = path.resolve(__dirname, "../dist/sandboxes/js/sandbox.wasm"); + const pyWasmPath = path.resolve(__dirname, "../dist/sandboxes/python/sandbox.wasm"); + + if (fs.existsSync(jsWasmPath) && fs.existsSync(pyWasmPath)) { + this.jsSandbox = jsWasmPath; + this.pythonSandbox = pyWasmPath; + } else { + this.jsSandbox = path.resolve(__dirname, "../../wasm-sandboxes/js/sandbox.ts"); + // We need to install the requirements.txt first to run the python sandbox + this.pythonSandbox = path.resolve(__dirname, "../../wasm-sandboxes/python/sandbox.py"); + } } - async executeCode(language: string, code: string): Promise { - throw new Error("Method not implemented."); + async executeCode(state: State, code: string, language: string = "js"): Promise { + const result = await run({ + file: language === "js" || language === "javascript" ? this.jsSandbox : this.pythonSandbox, + args: ["EXECUTE_CODE", JSON.stringify(state), code], + mounts: [`${this.hostWorkspace}::/`], + }) + + if (result.error) { + throw result.error.message; + } + + return result.result; } - async executeFile(language: string, filePath: string): Promise { - throw new Error("Method not implemented."); + async executeFile(state: State, filePath: string, language: string = "js"): Promise { + const result = await run({ + file: language === "js" || language === "javascript" ? this.jsSandbox : this.pythonSandbox, + args: ["EXECUTE_FILE", JSON.stringify(state), filePath], + mounts: [`${this.hostWorkspace}::/`], + }) + + if (result.error) { + throw result.error.message; + } + + return result.result as string; } - async resolveDirectoryPath(directoryPath: string): Promise { - throw new Error("Method not implemented."); + async resolveDirectoryPath(state: State, directoryPath: string): Promise { + const result = await run({ + file: this.jsSandbox, + args: ["RESOLVE_DIRECTORY_PATH", JSON.stringify(state), directoryPath], + mounts: [`${this.hostWorkspace}::/`], + }) + + if (result.error) { + throw result.error.message; + } + + return result.result as string; } } diff --git a/packages/bash-wasm/vitest.config.ts b/packages/bash-wasm/vitest.config.ts deleted file mode 100644 index 7628206..0000000 --- a/packages/bash-wasm/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig, mergeConfig } from 'vitest/config' - -export default defineConfig({ - test: { - name: 'bash-wasm', - environment: 'node', - // setupFiles: ['./test/setup.ts'], - }, -}) diff --git a/packages/bash/src/core/bash.ts b/packages/bash/src/core/bash.ts index 467abf0..cc1fc0b 100644 --- a/packages/bash/src/core/bash.ts +++ b/packages/bash/src/core/bash.ts @@ -6,23 +6,22 @@ export class Bash { private runtime: BaseRuntime; private filesystem: Filesystem; - public readonly state: StateManager; + public readonly stateManager: StateManager; - constructor({ runtime, initialCwd = "workspace" }: BashOptions) { + constructor({ runtime, hostWorkspace = ".capsule/session/workspace", initialCwd = "workspace" }: BashOptions) { this.runtime = runtime; - this.filesystem = new Filesystem(".capsule/session/workspace"); - this.state = new StateManager(runtime, initialCwd); + this.runtime.hostWorkspace = hostWorkspace; + this.stateManager = new StateManager(runtime, initialCwd); + this.filesystem = new Filesystem(hostWorkspace); this.filesystem.init(); } - run(command: string) { - this.runtime.executeCommand(command); - } + run(command: string) {} reset() { this.filesystem.reset(); - this.state.reset(); + this.stateManager.reset(); } } diff --git a/packages/bash/src/core/filesystem.ts b/packages/bash/src/core/filesystem.ts index b092276..d0bce0e 100644 --- a/packages/bash/src/core/filesystem.ts +++ b/packages/bash/src/core/filesystem.ts @@ -20,7 +20,7 @@ export class Filesystem { 'etc/os-release': 'NAME="Capsule OS"\nVERSION="1.0"\nID=capsule\n', 'etc/passwd': 'root:x:0:0:root:/root:/bin/bash\n', 'proc/cpuinfo': 'processor\t: 0\nvendor_id\t: CapsuleVirtualCPU\n', - 'workspace/README.md': '# Welcome to the Capsule Bash Environment\\n\\nYou are operating inside a secure and minimalist sandbox.\\n\\n## Environment Details:\\n- **OS:** Capsule Bash (Mocked Linux)\\n- **Capabilities:** Standard bash operations are supported, but advanced system calls may be restricted.\\n- **Working Directory:** `/workspace` is your designated safe zone.' + 'workspace/README.md': '# Welcome to the Capsule Bash Environment\\n\\nYou are operating inside a secure and minimalist sandboxed bash.' }; for (const [relativePath, content] of Object.entries(files)) { diff --git a/packages/bash/src/core/stateManager.ts b/packages/bash/src/core/stateManager.ts index e7b27ce..e4587a3 100644 --- a/packages/bash/src/core/stateManager.ts +++ b/packages/bash/src/core/stateManager.ts @@ -18,9 +18,14 @@ export class StateManager { return this.state.cwd.startsWith('/') ? this.state.cwd : `/${this.state.cwd}`; } - public async changeDirectory(targetPath: string): Promise { - const resolvedPath = await this.runtime.resolveDirectoryPath(targetPath); - this.state.cwd = resolvedPath; + public async changeDirectory(targetPath: string): Promise { + try { + const resolvedPath = await this.runtime.resolveDirectoryPath(this.state, targetPath); + this.state.cwd = resolvedPath; + return true; + } catch { + return false; + } } public setEnv(key: string, value: string): void { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 123fe87..7db91ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,12 +49,18 @@ importers: '@capsule-run/bash-types': specifier: workspace:* version: link:../bash-types + '@capsule-run/cli': + specifier: ^0.8.5 + version: 0.8.5 + '@capsule-run/sdk': + specifier: ^0.8.5 + version: 0.8.5(@types/node@25.6.0) devDependencies: tsup: specifier: ^8.0.0 version: 8.5.1(postcss@8.5.9)(typescript@6.0.2) - sandboxes/js: + wasm-sandboxes/js: dependencies: '@capsule-run/bash-types': specifier: workspace:* @@ -73,11 +79,11 @@ importers: specifier: '*' version: 4.1.4(@types/node@25.2.3)(vite@8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1)) - sandboxes/python: + wasm-sandboxes/python: devDependencies: '@capsule-run/sdk': specifier: ^0.8.4 - version: 0.8.4(@types/node@25.2.3) + version: 0.8.5(@types/node@25.2.3) '@types/node': specifier: 25.2.3 version: 25.2.3 @@ -168,14 +174,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@capsule-run/sdk@0.8.4': - resolution: {integrity: sha512-m+LgAycO6KiVY5ntegmhsue5GwWqZr6c9pSfQk2475+H3wf5Th8hP/mE6KcZIFcyJvQBER+l5QW1+qTTmT6Ijg==} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@capsule-run/sdk@0.8.5': resolution: {integrity: sha512-Z8RwcuL3EH5MldZ+3WXd8/0HEDHt758Mem6mB0TQbDF4psoETGtVAqDioyYrTjxtKzGuAN/yiIWcVYv8KyNt6A==} peerDependencies: @@ -1515,15 +1513,6 @@ snapshots: '@capsule-run/cli-linux-x64': 0.8.5 '@capsule-run/cli-win32-x64': 0.8.5 - '@capsule-run/sdk@0.8.4(@types/node@25.2.3)': - dependencies: - '@bytecodealliance/jco': 1.17.6 - esbuild: 0.27.7 - typescript: 5.9.3 - unenv: 2.0.0-rc.24 - optionalDependencies: - '@types/node': 25.2.3 - '@capsule-run/sdk@0.8.5(@types/node@25.2.3)': dependencies: '@bytecodealliance/jco': 1.17.6 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b2ee1b2..d44052b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,3 @@ packages: - packages/* - - sandboxes/* + - wasm-sandboxes/* diff --git a/sandboxes/js/__test__/sandbox.test.ts b/wasm-sandboxes/js/__test__/sandbox.test.ts similarity index 100% rename from sandboxes/js/__test__/sandbox.test.ts rename to wasm-sandboxes/js/__test__/sandbox.test.ts diff --git a/sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ b/wasm-sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ similarity index 100% rename from sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ rename to wasm-sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ diff --git a/sandboxes/js/__test__/workspace/imports/imported-file-test.js b/wasm-sandboxes/js/__test__/workspace/imports/imported-file-test.js similarity index 100% rename from sandboxes/js/__test__/workspace/imports/imported-file-test.js rename to wasm-sandboxes/js/__test__/workspace/imports/imported-file-test.js diff --git a/sandboxes/js/__test__/workspace/imports/imported-file-test2.js b/wasm-sandboxes/js/__test__/workspace/imports/imported-file-test2.js similarity index 100% rename from sandboxes/js/__test__/workspace/imports/imported-file-test2.js rename to wasm-sandboxes/js/__test__/workspace/imports/imported-file-test2.js diff --git a/sandboxes/js/__test__/workspace/test-file.js b/wasm-sandboxes/js/__test__/workspace/test-file.js similarity index 100% rename from sandboxes/js/__test__/workspace/test-file.js rename to wasm-sandboxes/js/__test__/workspace/test-file.js diff --git a/sandboxes/js/package.json b/wasm-sandboxes/js/package.json similarity index 100% rename from sandboxes/js/package.json rename to wasm-sandboxes/js/package.json diff --git a/sandboxes/js/sandbox.ts b/wasm-sandboxes/js/sandbox.ts similarity index 99% rename from sandboxes/js/sandbox.ts rename to wasm-sandboxes/js/sandbox.ts index 3210f83..6d7cb54 100644 --- a/sandboxes/js/sandbox.ts +++ b/wasm-sandboxes/js/sandbox.ts @@ -102,7 +102,6 @@ const executeCommand = task( process.chdir(state.cwd); const exports: { execute?: (args: string[]) => any } = {}; - console.log(args) const moduleWrapper = new Function('exports', scriptContent); moduleWrapper(exports); diff --git a/sandboxes/js/tsconfig.json b/wasm-sandboxes/js/tsconfig.json similarity index 100% rename from sandboxes/js/tsconfig.json rename to wasm-sandboxes/js/tsconfig.json diff --git a/sandboxes/js/vitest.config.ts b/wasm-sandboxes/js/vitest.config.ts similarity index 100% rename from sandboxes/js/vitest.config.ts rename to wasm-sandboxes/js/vitest.config.ts diff --git a/sandboxes/python/__test__/sandbox.test.ts b/wasm-sandboxes/python/__test__/sandbox.test.ts similarity index 100% rename from sandboxes/python/__test__/sandbox.test.ts rename to wasm-sandboxes/python/__test__/sandbox.test.ts diff --git a/sandboxes/python/__test__/workspace/hello.py b/wasm-sandboxes/python/__test__/workspace/hello.py similarity index 100% rename from sandboxes/python/__test__/workspace/hello.py rename to wasm-sandboxes/python/__test__/workspace/hello.py diff --git a/sandboxes/python/package.json b/wasm-sandboxes/python/package.json similarity index 100% rename from sandboxes/python/package.json rename to wasm-sandboxes/python/package.json diff --git a/sandboxes/python/requirements.txt b/wasm-sandboxes/python/requirements.txt similarity index 100% rename from sandboxes/python/requirements.txt rename to wasm-sandboxes/python/requirements.txt diff --git a/sandboxes/python/sandbox.py b/wasm-sandboxes/python/sandbox.py similarity index 100% rename from sandboxes/python/sandbox.py rename to wasm-sandboxes/python/sandbox.py diff --git a/sandboxes/python/vitest.config.ts b/wasm-sandboxes/python/vitest.config.ts similarity index 100% rename from sandboxes/python/vitest.config.ts rename to wasm-sandboxes/python/vitest.config.ts