From 167fd85daa369b61b77210869f3e06f0bb0556a6 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sat, 11 Apr 2026 22:10:11 +0200 Subject: [PATCH 1/4] feat: add RESOLVE_DIRECTORY_PATH sandbox action --- sandboxes/js/__test__/sandbox.test.ts | 49 ++++++++++++++++++++++++++- sandboxes/js/sandbox.ts | 15 ++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/sandboxes/js/__test__/sandbox.test.ts b/sandboxes/js/__test__/sandbox.test.ts index e9b3d29..c90d055 100644 --- a/sandboxes/js/__test__/sandbox.test.ts +++ b/sandboxes/js/__test__/sandbox.test.ts @@ -7,7 +7,7 @@ const SANDBOX = path.resolve(__dirname, '../sandbox.ts'); const WORKSPACE = '__test__/workspace'; const baseState = JSON.stringify({ - cwd: '/', + cwd: '.', env: {}, lastExitCode: 0, }); @@ -147,3 +147,50 @@ describe('sandbox.ts – invalid action', () => { assertFailure(result); }); }); + + +describe('sandbox.ts – RESOLVE_DIRECTORY_PATH', () => { + it('resolves a directory path', async () => { + const result = await run({ + file: SANDBOX, + args: ['RESOLVE_DIRECTORY_PATH', baseState, 'imports'], + mounts: [`${WORKSPACE}::/`], + }); + + const value = assertSuccess(result); + expect(value).toBe('/imports'); + }); + + it('Should return an error because the directory path does not exist', async () => { + const result = await run({ + file: SANDBOX, + args: ['RESOLVE_DIRECTORY_PATH', baseState, '../non-existent-directory'], + mounts: [`${WORKSPACE}::/`], + }); + + const value = assertFailure(result); + expect(value.message).toContain("Directory path ../non-existent-directory does not exist"); + }); + + it('resolves a directory path', async () => { + const result = await run({ + file: SANDBOX, + args: ['RESOLVE_DIRECTORY_PATH', baseState, 'imports/../imports/complex-path-testing'], + mounts: [`${WORKSPACE}::/`], + }); + + const value = assertSuccess(result); + expect(value).toBe('/imports/complex-path-testing'); + }); + + it('Should works with a different initial cwd', async () => { + const result = await run({ + file: SANDBOX, + args: ['RESOLVE_DIRECTORY_PATH', JSON.stringify({ cwd: 'imports', env: {}, lastExitCode: 0 }), 'complex-path-testing'], + mounts: [`${WORKSPACE}::/`], + }); + + const value = assertSuccess(result); + expect(value).toBe('/imports/complex-path-testing'); + }); +}); diff --git a/sandboxes/js/sandbox.ts b/sandboxes/js/sandbox.ts index ba45764..3210f83 100644 --- a/sandboxes/js/sandbox.ts +++ b/sandboxes/js/sandbox.ts @@ -115,6 +115,19 @@ const executeCommand = task( } ) +export const resolveDirectoryPath = task( + { name: "resolveDirectoryPath", compute: "LOW", ram: "32MB" }, + async (state: State, targetPath: string) => { + process.chdir(state.cwd); + + if (!fs.existsSync(targetPath)) { + throw new Error(`Directory path ${targetPath} does not exist`); + } + + return '/' + path.resolve(targetPath); + } +) + export const main = task( { name: "main", compute: "HIGH" }, async (action: string, state: string, ...args: string[]): Promise => { @@ -131,6 +144,8 @@ export const main = task( response = await executeCode(parsedState, parsedArgs[0]); } else if (action === "EXECUTE_FILE") { response = await executeFile(parsedState, parsedArgs[0], parsedArgs.slice(1)); + } else if (action === "RESOLVE_DIRECTORY_PATH") { + response = await resolveDirectoryPath(parsedState, parsedArgs[0]); } else { throw new Error(`Invalid action: ${action}`); } From d3b026ca1b7dd677e915224ea101209746d02d43 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sun, 12 Apr 2026 01:01:57 +0200 Subject: [PATCH 2/4] feat: implement core Bash runtime, filesystem management, and state tracking interfaces --- packages/bash/package.json | 3 ++ packages/bash/src/core/bash.ts | 28 ++++++++++++++++++ packages/bash/src/core/filesystem.ts | 35 +++++++++++++++++++++++ packages/bash/src/core/stateManager.ts | 39 ++++++++++++++++++++++++++ packages/bash/src/index.ts | 1 + 5 files changed, 106 insertions(+) create mode 100644 packages/bash/src/core/bash.ts create mode 100644 packages/bash/src/core/filesystem.ts create mode 100644 packages/bash/src/core/stateManager.ts diff --git a/packages/bash/package.json b/packages/bash/package.json index f4a98d1..ddf59d6 100644 --- a/packages/bash/package.json +++ b/packages/bash/package.json @@ -10,5 +10,8 @@ "packageManager": "pnpm@10.21.0", "devDependencies": { "tsup": "^8.0.0" + }, + "dependencies": { + "@capsule-run/bash-types": "workspace:*" } } diff --git a/packages/bash/src/core/bash.ts b/packages/bash/src/core/bash.ts new file mode 100644 index 0000000..467abf0 --- /dev/null +++ b/packages/bash/src/core/bash.ts @@ -0,0 +1,28 @@ +import type { BaseRuntime, BashOptions } from "@capsule-run/bash-types"; +import { StateManager } from "./stateManager"; +import { Filesystem } from "./filesystem"; + +export class Bash { + private runtime: BaseRuntime; + private filesystem: Filesystem; + + public readonly state: StateManager; + + constructor({ runtime, initialCwd = "workspace" }: BashOptions) { + this.runtime = runtime; + this.filesystem = new Filesystem(".capsule/session/workspace"); + this.state = new StateManager(runtime, initialCwd); + + this.filesystem.init(); + } + + run(command: string) { + this.runtime.executeCommand(command); + } + + reset() { + this.filesystem.reset(); + this.state.reset(); + } + +} diff --git a/packages/bash/src/core/filesystem.ts b/packages/bash/src/core/filesystem.ts new file mode 100644 index 0000000..b092276 --- /dev/null +++ b/packages/bash/src/core/filesystem.ts @@ -0,0 +1,35 @@ +import fs from 'fs'; +import path from 'path'; + +export class Filesystem { + private workspace: string; + + constructor(workspace: string) { + this.workspace = workspace; + } + + init() { + const directories = ['bin', 'dev', 'etc', 'proc', 'root', 'sys', 'tmp', 'workspace']; + + for (const dir of directories) { + fs.mkdirSync(path.join(this.workspace, dir), { recursive: true }); + } + + const files: Record = { + 'etc/resolv.conf': 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n', + '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.' + }; + + for (const [relativePath, content] of Object.entries(files)) { + fs.writeFileSync(path.join(this.workspace, relativePath), content); + } + } + + reset() { + fs.rmSync(this.workspace, { recursive: true, force: true }); + this.init(); + } +} diff --git a/packages/bash/src/core/stateManager.ts b/packages/bash/src/core/stateManager.ts new file mode 100644 index 0000000..e7b27ce --- /dev/null +++ b/packages/bash/src/core/stateManager.ts @@ -0,0 +1,39 @@ +import path from 'node:path'; +import type { BaseRuntime, State } from '@capsule-run/bash-types'; + +export class StateManager { + public readonly state: State; + private runtime: BaseRuntime; + + constructor(runtime: BaseRuntime, initialCwd: string = 'workspace') { + this.runtime = runtime; + this.state = { + cwd: initialCwd, + env: {}, + lastExitCode: 0 + }; + } + + get displayCwd(): string { + 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 setEnv(key: string, value: string): void { + this.state.env[key] = value; + } + + public setExitCode(code: number): void { + this.state.lastExitCode = code; + } + + public reset() { + this.state.cwd = 'workspace'; + this.state.env = {}; + this.state.lastExitCode = 0; + } +} diff --git a/packages/bash/src/index.ts b/packages/bash/src/index.ts index e69de29..8767807 100644 --- a/packages/bash/src/index.ts +++ b/packages/bash/src/index.ts @@ -0,0 +1 @@ +export { Bash } from "./core/bash"; From 7a8e13b499b823dc838d51b0523a09f6354bcc15 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sun, 12 Apr 2026 01:02:45 +0200 Subject: [PATCH 3/4] feat: define BaseRuntime interface and implement WasmRuntime scaffold --- packages/bash-types/src/bash.ts | 13 ++++++++++ packages/bash-types/src/index.ts | 2 ++ packages/bash-types/src/runtime.ts | 38 ++++++++++++++++++++++++++++++ packages/bash-wasm/package.json | 3 +++ packages/bash-wasm/src/index.ts | 2 +- packages/bash-wasm/src/runtime.ts | 21 +++++++++++++++++ pnpm-lock.yaml | 8 +++++++ 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 packages/bash-types/src/bash.ts create mode 100644 packages/bash-types/src/runtime.ts create mode 100644 packages/bash-wasm/src/runtime.ts diff --git a/packages/bash-types/src/bash.ts b/packages/bash-types/src/bash.ts new file mode 100644 index 0000000..8b0d8ed --- /dev/null +++ b/packages/bash-types/src/bash.ts @@ -0,0 +1,13 @@ +import type { BaseRuntime } from "./runtime"; + +export interface BashOptions { + /** + * The runtime to use for executing commands + */ + runtime: BaseRuntime; + + /** + * The initial working directory + */ + initialCwd?: string; +} diff --git a/packages/bash-types/src/index.ts b/packages/bash-types/src/index.ts index a259013..cc3a742 100644 --- a/packages/bash-types/src/index.ts +++ b/packages/bash-types/src/index.ts @@ -1 +1,3 @@ export { State } from "./state"; +export { BaseRuntime, RuntimeResult } from "./runtime"; +export { BashOptions } from "./bash"; diff --git a/packages/bash-types/src/runtime.ts b/packages/bash-types/src/runtime.ts new file mode 100644 index 0000000..098802b --- /dev/null +++ b/packages/bash-types/src/runtime.ts @@ -0,0 +1,38 @@ +export interface BaseRuntime { + /** + * Execute a command + */ + executeCommand(code: string): Promise; + + /** + * Execute a code + */ + executeCode(language: string, code: string): Promise; + + /** + * Execute a file + */ + executeFile(language: string, filePath: string): Promise; + + /** + * Resolve a directory path + */ + resolveDirectoryPath(directoryPath: string): Promise; +} + +export interface RuntimeResult { + /** + * Standard output + */ + stdout: string; + + /** + * Standard error + */ + stderr: string; + + /** + * Exit code + */ + exitCode: number; +} diff --git a/packages/bash-wasm/package.json b/packages/bash-wasm/package.json index 32a8522..79bb7c0 100644 --- a/packages/bash-wasm/package.json +++ b/packages/bash-wasm/package.json @@ -10,5 +10,8 @@ "packageManager": "pnpm@10.21.0", "devDependencies": { "tsup": "^8.0.0" + }, + "dependencies": { + "@capsule-run/bash-types": "workspace:*" } } diff --git a/packages/bash-wasm/src/index.ts b/packages/bash-wasm/src/index.ts index 8b13789..f5757ba 100644 --- a/packages/bash-wasm/src/index.ts +++ b/packages/bash-wasm/src/index.ts @@ -1 +1 @@ - +export { WasmRuntime } from "./runtime"; diff --git a/packages/bash-wasm/src/runtime.ts b/packages/bash-wasm/src/runtime.ts new file mode 100644 index 0000000..17e5a8a --- /dev/null +++ b/packages/bash-wasm/src/runtime.ts @@ -0,0 +1,21 @@ +import type { BaseRuntime, RuntimeResult } from "@capsule-run/bash-types"; + +export class WasmRuntime implements BaseRuntime { + constructor() {} + + async executeCommand(code: string): Promise { + throw new Error("Method not implemented."); + } + + async executeCode(language: string, code: string): Promise { + throw new Error("Method not implemented."); + } + + async executeFile(language: string, filePath: string): Promise { + throw new Error("Method not implemented."); + } + + async resolveDirectoryPath(directoryPath: string): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d584d6..123fe87 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,10 @@ importers: version: 4.1.4(@types/node@25.6.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)) packages/bash: + dependencies: + '@capsule-run/bash-types': + specifier: workspace:* + version: link:../bash-types devDependencies: tsup: specifier: ^8.0.0 @@ -41,6 +45,10 @@ importers: version: 8.5.1(postcss@8.5.9)(typescript@6.0.2) packages/bash-wasm: + dependencies: + '@capsule-run/bash-types': + specifier: workspace:* + version: link:../bash-types devDependencies: tsup: specifier: ^8.0.0 From 0dca4f4f8553e0d1d1bf7adaec37456b661d86e4 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sun, 12 Apr 2026 01:13:03 +0200 Subject: [PATCH 4/4] test: add directory for complex import path testing --- .../js/__test__/workspace/imports/complex-path-testing/__test__ | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ diff --git a/sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ b/sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ new file mode 100644 index 0000000..e69de29