@douglas-agent/sandbank-cli 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +229 -0
  2. package/dist/cli/api.d.ts +6 -0
  3. package/dist/cli/api.d.ts.map +1 -0
  4. package/dist/cli/api.js +8 -0
  5. package/dist/cli/api.test.d.ts +2 -0
  6. package/dist/cli/api.test.d.ts.map +1 -0
  7. package/dist/cli/api.test.js +31 -0
  8. package/dist/cli/auth.d.ts +25 -0
  9. package/dist/cli/auth.d.ts.map +1 -0
  10. package/dist/cli/auth.js +34 -0
  11. package/dist/cli/auth.test.d.ts +2 -0
  12. package/dist/cli/auth.test.d.ts.map +1 -0
  13. package/dist/cli/auth.test.js +89 -0
  14. package/dist/cli/commands/addons.d.ts +3 -0
  15. package/dist/cli/commands/addons.d.ts.map +1 -0
  16. package/dist/cli/commands/addons.js +54 -0
  17. package/dist/cli/commands/clone.d.ts +3 -0
  18. package/dist/cli/commands/clone.d.ts.map +1 -0
  19. package/dist/cli/commands/clone.js +18 -0
  20. package/dist/cli/commands/commands.test.d.ts +2 -0
  21. package/dist/cli/commands/commands.test.d.ts.map +1 -0
  22. package/dist/cli/commands/commands.test.js +439 -0
  23. package/dist/cli/commands/config.d.ts +3 -0
  24. package/dist/cli/commands/config.d.ts.map +1 -0
  25. package/dist/cli/commands/config.js +57 -0
  26. package/dist/cli/commands/create.d.ts +3 -0
  27. package/dist/cli/commands/create.d.ts.map +1 -0
  28. package/dist/cli/commands/create.js +30 -0
  29. package/dist/cli/commands/destroy.d.ts +3 -0
  30. package/dist/cli/commands/destroy.d.ts.map +1 -0
  31. package/dist/cli/commands/destroy.js +16 -0
  32. package/dist/cli/commands/exec.d.ts +3 -0
  33. package/dist/cli/commands/exec.d.ts.map +1 -0
  34. package/dist/cli/commands/exec.js +22 -0
  35. package/dist/cli/commands/get.d.ts +3 -0
  36. package/dist/cli/commands/get.d.ts.map +1 -0
  37. package/dist/cli/commands/get.js +21 -0
  38. package/dist/cli/commands/help.d.ts +2 -0
  39. package/dist/cli/commands/help.d.ts.map +1 -0
  40. package/dist/cli/commands/help.js +39 -0
  41. package/dist/cli/commands/keep.d.ts +3 -0
  42. package/dist/cli/commands/keep.d.ts.map +1 -0
  43. package/dist/cli/commands/keep.js +25 -0
  44. package/dist/cli/commands/list.d.ts +3 -0
  45. package/dist/cli/commands/list.d.ts.map +1 -0
  46. package/dist/cli/commands/list.js +14 -0
  47. package/dist/cli/commands/login.d.ts +3 -0
  48. package/dist/cli/commands/login.d.ts.map +1 -0
  49. package/dist/cli/commands/login.js +25 -0
  50. package/dist/cli/commands/snapshot.d.ts +3 -0
  51. package/dist/cli/commands/snapshot.d.ts.map +1 -0
  52. package/dist/cli/commands/snapshot.js +78 -0
  53. package/dist/cli/config.d.ts +9 -0
  54. package/dist/cli/config.d.ts.map +1 -0
  55. package/dist/cli/config.js +27 -0
  56. package/dist/cli/config.test.d.ts +2 -0
  57. package/dist/cli/config.test.d.ts.map +1 -0
  58. package/dist/cli/config.test.js +60 -0
  59. package/dist/cli/index.d.ts +8 -0
  60. package/dist/cli/index.d.ts.map +1 -0
  61. package/dist/cli/index.js +78 -0
  62. package/dist/cli/index.test.d.ts +2 -0
  63. package/dist/cli/index.test.d.ts.map +1 -0
  64. package/dist/cli/index.test.js +126 -0
  65. package/dist/index.d.ts +13 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +12 -0
  68. package/package.json +53 -0
@@ -0,0 +1,39 @@
1
+ export function helpCommand() {
2
+ console.log(`sandbank — Sandbox SDK and CLI for AI agents
3
+
4
+ Usage: sandbank <command> [options]
5
+
6
+ Commands:
7
+ login Save API key or wallet key
8
+ config Show or set configuration
9
+ create [--image <img>] Create a new sandbox
10
+ list List your sandboxes
11
+ get <id> Get sandbox details
12
+ destroy <id> Destroy a sandbox
13
+ exec <id> <command> Execute a command in a sandbox
14
+ clone [<id>] Clone a sandbox
15
+ keep <id> [--minutes <n>] Extend sandbox timeout
16
+ addons create <type> [--intent] Create an addon
17
+ addons list List addons
18
+ snapshot create <id> <name> Create a snapshot
19
+ snapshot list <id> List snapshots
20
+ snapshot restore <id> <name> Restore a snapshot
21
+ snapshot delete <id> <name> Delete a snapshot
22
+
23
+ Global options:
24
+ --api-key <key> API key for authentication
25
+ --wallet-key <0x..> EVM private key for x402 payment
26
+ --url <url> API URL (default: https://cloud.sandbank.dev)
27
+ --json Output as JSON
28
+ --version, -v Show version
29
+ --help, -h Show this help
30
+
31
+ Environment variables:
32
+ SANDBANK_API_KEY API key
33
+ SANDBANK_AGENT_TOKEN Box agent token (inside sandbox)
34
+ SANDBANK_WALLET_KEY EVM private key
35
+ SANDBANK_API_URL API URL
36
+ SANDBANK_BOX_ID Current box ID (inside sandbox)
37
+
38
+ https://sandbank.dev`);
39
+ }
@@ -0,0 +1,3 @@
1
+ import type { CliFlags } from '../auth.js';
2
+ export declare function keepCommand(args: string[], flags: CliFlags): Promise<void>;
3
+ //# sourceMappingURL=keep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keep.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/keep.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAW1C,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBhF"}
@@ -0,0 +1,25 @@
1
+ import { createApiClient, printJson } from '../api.js';
2
+ function takeOption(args, name) {
3
+ const idx = args.indexOf(name);
4
+ if (idx === -1)
5
+ return undefined;
6
+ const value = args[idx + 1];
7
+ args.splice(idx, 2);
8
+ return value;
9
+ }
10
+ export async function keepCommand(args, flags) {
11
+ const minutes = Number(takeOption(args, '--minutes') || 30);
12
+ const id = args[0];
13
+ if (!id) {
14
+ console.error('Usage: sandbank keep <id> [--minutes <n>]');
15
+ process.exit(1);
16
+ }
17
+ const api = createApiClient(flags);
18
+ const result = await api.x402Fetch(`/boxes/${id}/keep`, {
19
+ method: 'POST',
20
+ body: JSON.stringify({ timeout_minutes: minutes }),
21
+ });
22
+ if (flags.json)
23
+ return printJson(result);
24
+ console.log(`Extended ${id} by ${result.timeout_minutes} minutes`);
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { CliFlags } from '../auth.js';
2
+ export declare function listCommand(args: string[], flags: CliFlags): Promise<void>;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAI1C,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAahF"}
@@ -0,0 +1,14 @@
1
+ import { createApiClient, printJson } from '../api.js';
2
+ export async function listCommand(args, flags) {
3
+ const api = createApiClient(flags);
4
+ const boxes = await api.x402Fetch('/boxes');
5
+ if (flags.json)
6
+ return printJson(boxes);
7
+ if (boxes.length === 0) {
8
+ console.log('No sandboxes');
9
+ return;
10
+ }
11
+ for (const box of boxes) {
12
+ console.log(`${box.id} ${box.status.padEnd(8)} ${box.image} ${box.created_at}`);
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ import type { CliFlags } from '../auth.js';
2
+ export declare function loginCommand(args: string[], flags: CliFlags): void;
3
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAG1C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI,CAwBlE"}
@@ -0,0 +1,25 @@
1
+ import { loadCredentials, saveCredentials, maskSecret } from '../config.js';
2
+ export function loginCommand(args, flags) {
3
+ const creds = loadCredentials();
4
+ if (flags.apiKey) {
5
+ creds.apiKey = flags.apiKey;
6
+ }
7
+ if (flags.walletKey) {
8
+ creds.walletKey = flags.walletKey;
9
+ }
10
+ if (flags.url) {
11
+ creds.url = flags.url;
12
+ }
13
+ if (!flags.apiKey && !flags.walletKey && !flags.url) {
14
+ console.error('Usage: sandbank login --api-key <key> [--wallet-key <0x..>] [--url <url>]');
15
+ process.exit(1);
16
+ }
17
+ saveCredentials(creds);
18
+ console.log('Credentials saved to ~/.sandbank/credentials.json');
19
+ if (creds.apiKey)
20
+ console.log(` api-key: ${maskSecret(creds.apiKey)}`);
21
+ if (creds.walletKey)
22
+ console.log(` wallet-key: ${maskSecret(creds.walletKey)}`);
23
+ if (creds.url)
24
+ console.log(` url: ${creds.url}`);
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { CliFlags } from '../auth.js';
2
+ export declare function snapshotCommand(args: string[], flags: CliFlags): Promise<void>;
3
+ //# sourceMappingURL=snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAG1C,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEpF"}
@@ -0,0 +1,78 @@
1
+ import { createApiClient, printJson } from '../api.js';
2
+ export async function snapshotCommand(args, flags) {
3
+ const sub = args[0];
4
+ if (sub === 'create') {
5
+ const id = args[1];
6
+ const name = args[2];
7
+ if (!id || !name) {
8
+ console.error('Usage: sandbank snapshot create <box_id> <name>');
9
+ process.exit(1);
10
+ }
11
+ const api = createApiClient(flags);
12
+ await api.x402Fetch(`/boxes/${id}/snapshots`, {
13
+ method: 'POST',
14
+ body: JSON.stringify({ name }),
15
+ });
16
+ if (flags.json)
17
+ printJson({ id, name, created: true });
18
+ else
19
+ console.log(`Snapshot "${name}" created for ${id}`);
20
+ return;
21
+ }
22
+ if (sub === 'list' || sub === 'ls') {
23
+ const id = args[1];
24
+ if (!id) {
25
+ console.error('Usage: sandbank snapshot list <box_id>');
26
+ process.exit(1);
27
+ }
28
+ const api = createApiClient(flags);
29
+ const snapshots = await api.x402Fetch(`/boxes/${id}/snapshots`);
30
+ if (flags.json)
31
+ return printJson(snapshots);
32
+ if (snapshots.length === 0) {
33
+ console.log('No snapshots');
34
+ return;
35
+ }
36
+ for (const s of snapshots) {
37
+ console.log(`${s.name}${s.created_at ? ` ${s.created_at}` : ''}`);
38
+ }
39
+ return;
40
+ }
41
+ if (sub === 'restore') {
42
+ const id = args[1];
43
+ const name = args[2];
44
+ if (!id || !name) {
45
+ console.error('Usage: sandbank snapshot restore <box_id> <name>');
46
+ process.exit(1);
47
+ }
48
+ const api = createApiClient(flags);
49
+ await api.x402Fetch(`/boxes/${id}/snapshots/${encodeURIComponent(name)}/restore`, {
50
+ method: 'POST',
51
+ body: JSON.stringify({}),
52
+ });
53
+ if (flags.json)
54
+ printJson({ id, name, restored: true });
55
+ else
56
+ console.log(`Restored "${name}" on ${id}`);
57
+ return;
58
+ }
59
+ if (sub === 'delete' || sub === 'rm') {
60
+ const id = args[1];
61
+ const name = args[2];
62
+ if (!id || !name) {
63
+ console.error('Usage: sandbank snapshot delete <box_id> <name>');
64
+ process.exit(1);
65
+ }
66
+ const api = createApiClient(flags);
67
+ await api.x402Fetch(`/boxes/${id}/snapshots/${encodeURIComponent(name)}`, {
68
+ method: 'DELETE',
69
+ });
70
+ if (flags.json)
71
+ printJson({ id, name, deleted: true });
72
+ else
73
+ console.log(`Deleted "${name}" from ${id}`);
74
+ return;
75
+ }
76
+ console.error('Usage: sandbank snapshot <create|list|restore|delete> ...');
77
+ process.exit(1);
78
+ }
@@ -0,0 +1,9 @@
1
+ export interface SandbankCredentials {
2
+ url?: string;
3
+ apiKey?: string;
4
+ walletKey?: string;
5
+ }
6
+ export declare function loadCredentials(): SandbankCredentials;
7
+ export declare function saveCredentials(creds: SandbankCredentials): void;
8
+ export declare function maskSecret(value: string): string;
9
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,eAAe,IAAI,mBAAmB,CAQrD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAKhE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGhD"}
@@ -0,0 +1,27 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ function configDir() { return join(homedir(), '.sandbank'); }
5
+ function credentialsFile() { return join(configDir(), 'credentials.json'); }
6
+ export function loadCredentials() {
7
+ try {
8
+ const file = credentialsFile();
9
+ if (existsSync(file)) {
10
+ return JSON.parse(readFileSync(file, 'utf-8'));
11
+ }
12
+ }
13
+ catch { }
14
+ return {};
15
+ }
16
+ export function saveCredentials(creds) {
17
+ const dir = configDir();
18
+ const file = credentialsFile();
19
+ if (!existsSync(dir))
20
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
21
+ writeFileSync(file, JSON.stringify(creds, null, 2) + '\n', { mode: 0o600 });
22
+ }
23
+ export function maskSecret(value) {
24
+ if (value.length <= 8)
25
+ return '****';
26
+ return value.slice(0, 4) + '...' + value.slice(-4);
27
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/cli/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { loadCredentials, saveCredentials, maskSecret } from './config.js';
6
+ describe('maskSecret', () => {
7
+ it('masks long secrets preserving first/last 4 chars', () => {
8
+ expect(maskSecret('abcdefghijklmnop')).toBe('abcd...mnop');
9
+ });
10
+ it('masks short secrets completely', () => {
11
+ expect(maskSecret('short')).toBe('****');
12
+ expect(maskSecret('12345678')).toBe('****');
13
+ });
14
+ it('masks 9-char secrets', () => {
15
+ expect(maskSecret('123456789')).toBe('1234...6789');
16
+ });
17
+ });
18
+ describe('loadCredentials / saveCredentials', () => {
19
+ let origHome;
20
+ let tempDir;
21
+ beforeEach(() => {
22
+ origHome = process.env['HOME'];
23
+ tempDir = mkdtempSync(join(tmpdir(), 'sandbank-test-'));
24
+ process.env['HOME'] = tempDir;
25
+ });
26
+ afterEach(() => {
27
+ if (origHome !== undefined)
28
+ process.env['HOME'] = origHome;
29
+ else
30
+ delete process.env['HOME'];
31
+ rmSync(tempDir, { recursive: true, force: true });
32
+ });
33
+ it('returns empty object when no credentials file exists', () => {
34
+ expect(loadCredentials()).toEqual({});
35
+ });
36
+ it('saves and loads credentials', () => {
37
+ saveCredentials({ apiKey: 'test-key', url: 'https://example.com' });
38
+ const creds = loadCredentials();
39
+ expect(creds.apiKey).toBe('test-key');
40
+ expect(creds.url).toBe('https://example.com');
41
+ expect(creds.walletKey).toBeUndefined();
42
+ });
43
+ it('creates config dir', () => {
44
+ saveCredentials({ apiKey: 'key' });
45
+ const dir = join(tempDir, '.sandbank');
46
+ expect(statSync(dir).isDirectory()).toBe(true);
47
+ });
48
+ it('overwrites existing credentials', () => {
49
+ saveCredentials({ apiKey: 'old' });
50
+ saveCredentials({ apiKey: 'new', walletKey: '0xabc' });
51
+ const creds = loadCredentials();
52
+ expect(creds.apiKey).toBe('new');
53
+ expect(creds.walletKey).toBe('0xabc');
54
+ });
55
+ it('handles corrupted credentials file gracefully', () => {
56
+ mkdirSync(join(tempDir, '.sandbank'), { recursive: true });
57
+ writeFileSync(join(tempDir, '.sandbank', 'credentials.json'), 'not json');
58
+ expect(loadCredentials()).toEqual({});
59
+ });
60
+ });
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import type { CliFlags } from './auth.js';
3
+ export declare const VERSION = "0.5.4";
4
+ export declare function takeFlag(args: string[], name: string): boolean;
5
+ export declare function takeOption(args: string[], name: string): string | undefined;
6
+ export declare function parseGlobalFlags(args: string[]): CliFlags;
7
+ export declare function dispatch(args: string[]): Promise<void>;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEzC,eAAO,MAAM,OAAO,UAAU,CAAA;AAE9B,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAK9D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM3E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,CAOzD;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC5D"}
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import { loginCommand } from './commands/login.js';
3
+ import { configCommand } from './commands/config.js';
4
+ import { createCommand } from './commands/create.js';
5
+ import { listCommand } from './commands/list.js';
6
+ import { getCommand } from './commands/get.js';
7
+ import { destroyCommand } from './commands/destroy.js';
8
+ import { execCommand } from './commands/exec.js';
9
+ import { cloneCommand } from './commands/clone.js';
10
+ import { keepCommand } from './commands/keep.js';
11
+ import { addonsCommand } from './commands/addons.js';
12
+ import { snapshotCommand } from './commands/snapshot.js';
13
+ import { helpCommand } from './commands/help.js';
14
+ export const VERSION = '0.5.4';
15
+ export function takeFlag(args, name) {
16
+ const idx = args.indexOf(name);
17
+ if (idx === -1)
18
+ return false;
19
+ args.splice(idx, 1);
20
+ return true;
21
+ }
22
+ export function takeOption(args, name) {
23
+ const idx = args.indexOf(name);
24
+ if (idx === -1)
25
+ return undefined;
26
+ const value = args[idx + 1];
27
+ args.splice(idx, 2);
28
+ return value;
29
+ }
30
+ export function parseGlobalFlags(args) {
31
+ return {
32
+ apiKey: takeOption(args, '--api-key'),
33
+ walletKey: takeOption(args, '--wallet-key'),
34
+ url: takeOption(args, '--url'),
35
+ json: takeFlag(args, '--json'),
36
+ };
37
+ }
38
+ export async function dispatch(args) {
39
+ if (takeFlag(args, '--version') || takeFlag(args, '-v')) {
40
+ console.log(VERSION);
41
+ return;
42
+ }
43
+ if (takeFlag(args, '--help') || takeFlag(args, '-h') || args.length === 0) {
44
+ helpCommand();
45
+ return;
46
+ }
47
+ const command = args.shift();
48
+ const flags = parseGlobalFlags(args);
49
+ switch (command) {
50
+ case 'login': return loginCommand(args, flags);
51
+ case 'config': return configCommand(args, flags);
52
+ case 'create': return createCommand(args, flags);
53
+ case 'list': return listCommand(args, flags);
54
+ case 'ls': return listCommand(args, flags);
55
+ case 'get': return getCommand(args, flags);
56
+ case 'destroy': return destroyCommand(args, flags);
57
+ case 'rm': return destroyCommand(args, flags);
58
+ case 'exec': return execCommand(args, flags);
59
+ case 'clone': return cloneCommand(args, flags);
60
+ case 'keep': return keepCommand(args, flags);
61
+ case 'addons': return addonsCommand(args, flags);
62
+ case 'snapshot': return snapshotCommand(args, flags);
63
+ case 'help': return helpCommand();
64
+ default:
65
+ console.error(`Unknown command: ${command}`);
66
+ helpCommand();
67
+ process.exit(1);
68
+ }
69
+ }
70
+ // Only skip when imported for vitest (test runner sets special argv)
71
+ const isTest = typeof process !== 'undefined' && process.env['VITEST'];
72
+ if (!isTest) {
73
+ dispatch(process.argv.slice(2)).catch(err => {
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ console.error(msg);
76
+ process.exit(1);
77
+ });
78
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/cli/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,126 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // Mock all command modules — vi.hoisted ensures mocks are available before imports
3
+ const mocks = vi.hoisted(() => ({
4
+ loginCommand: vi.fn(),
5
+ configCommand: vi.fn(),
6
+ createCommand: vi.fn(),
7
+ listCommand: vi.fn(),
8
+ getCommand: vi.fn(),
9
+ destroyCommand: vi.fn(),
10
+ execCommand: vi.fn(),
11
+ cloneCommand: vi.fn(),
12
+ keepCommand: vi.fn(),
13
+ addonsCommand: vi.fn(),
14
+ snapshotCommand: vi.fn(),
15
+ helpCommand: vi.fn(),
16
+ }));
17
+ vi.mock('./commands/login.js', () => ({ loginCommand: mocks.loginCommand }));
18
+ vi.mock('./commands/config.js', () => ({ configCommand: mocks.configCommand }));
19
+ vi.mock('./commands/create.js', () => ({ createCommand: mocks.createCommand }));
20
+ vi.mock('./commands/list.js', () => ({ listCommand: mocks.listCommand }));
21
+ vi.mock('./commands/get.js', () => ({ getCommand: mocks.getCommand }));
22
+ vi.mock('./commands/destroy.js', () => ({ destroyCommand: mocks.destroyCommand }));
23
+ vi.mock('./commands/exec.js', () => ({ execCommand: mocks.execCommand }));
24
+ vi.mock('./commands/clone.js', () => ({ cloneCommand: mocks.cloneCommand }));
25
+ vi.mock('./commands/keep.js', () => ({ keepCommand: mocks.keepCommand }));
26
+ vi.mock('./commands/addons.js', () => ({ addonsCommand: mocks.addonsCommand }));
27
+ vi.mock('./commands/snapshot.js', () => ({ snapshotCommand: mocks.snapshotCommand }));
28
+ vi.mock('./commands/help.js', () => ({ helpCommand: mocks.helpCommand }));
29
+ import { takeFlag, takeOption, parseGlobalFlags, dispatch, VERSION } from './index.js';
30
+ vi.spyOn(process, 'exit').mockImplementation((code) => {
31
+ throw new Error(`process.exit(${code})`);
32
+ });
33
+ describe('takeFlag', () => {
34
+ it('removes flag and returns true', () => {
35
+ const args = ['--json', 'create'];
36
+ expect(takeFlag(args, '--json')).toBe(true);
37
+ expect(args).toEqual(['create']);
38
+ });
39
+ it('returns false when flag not present', () => {
40
+ const args = ['create'];
41
+ expect(takeFlag(args, '--json')).toBe(false);
42
+ expect(args).toEqual(['create']);
43
+ });
44
+ });
45
+ describe('takeOption', () => {
46
+ it('removes option and value, returns value', () => {
47
+ const args = ['--api-key', 'mykey', 'create'];
48
+ expect(takeOption(args, '--api-key')).toBe('mykey');
49
+ expect(args).toEqual(['create']);
50
+ });
51
+ it('returns undefined when option not present', () => {
52
+ const args = ['create'];
53
+ expect(takeOption(args, '--api-key')).toBeUndefined();
54
+ });
55
+ });
56
+ describe('parseGlobalFlags', () => {
57
+ it('extracts all global flags', () => {
58
+ const args = ['--api-key', 'k', '--wallet-key', '0x1', '--url', 'http://x', '--json', 'create'];
59
+ const flags = parseGlobalFlags(args);
60
+ expect(flags).toEqual({ apiKey: 'k', walletKey: '0x1', url: 'http://x', json: true });
61
+ expect(args).toEqual(['create']);
62
+ });
63
+ it('returns empty flags when none present', () => {
64
+ const args = ['create'];
65
+ const flags = parseGlobalFlags(args);
66
+ expect(flags).toEqual({ apiKey: undefined, walletKey: undefined, url: undefined, json: false });
67
+ });
68
+ });
69
+ describe('dispatch', () => {
70
+ beforeEach(() => { vi.clearAllMocks(); });
71
+ it('shows version with --version', async () => {
72
+ const spy = vi.spyOn(console, 'log').mockImplementation(() => { });
73
+ await dispatch(['--version']);
74
+ expect(spy).toHaveBeenCalledWith(VERSION);
75
+ spy.mockRestore();
76
+ });
77
+ it('shows version with -v', async () => {
78
+ const spy = vi.spyOn(console, 'log').mockImplementation(() => { });
79
+ await dispatch(['-v']);
80
+ expect(spy).toHaveBeenCalledWith(VERSION);
81
+ spy.mockRestore();
82
+ });
83
+ it('shows help with --help', async () => {
84
+ await dispatch(['--help']);
85
+ expect(mocks.helpCommand).toHaveBeenCalled();
86
+ });
87
+ it('shows help with -h', async () => {
88
+ await dispatch(['-h']);
89
+ expect(mocks.helpCommand).toHaveBeenCalled();
90
+ });
91
+ it('shows help with no args', async () => {
92
+ await dispatch([]);
93
+ expect(mocks.helpCommand).toHaveBeenCalled();
94
+ });
95
+ const commandMap = [
96
+ ['login', 'loginCommand', ['login', '--api-key', 'x']],
97
+ ['config', 'configCommand', ['config']],
98
+ ['create', 'createCommand', ['create']],
99
+ ['list', 'listCommand', ['list']],
100
+ ['ls', 'listCommand', ['ls']],
101
+ ['get', 'getCommand', ['get', 'abc']],
102
+ ['destroy', 'destroyCommand', ['destroy', 'abc']],
103
+ ['rm', 'destroyCommand', ['rm', 'abc']],
104
+ ['exec', 'execCommand', ['exec', 'abc', 'echo']],
105
+ ['clone', 'cloneCommand', ['clone', 'abc']],
106
+ ['keep', 'keepCommand', ['keep', 'abc']],
107
+ ['addons', 'addonsCommand', ['addons', 'list']],
108
+ ['snapshot', 'snapshotCommand', ['snapshot', 'list', 'abc']],
109
+ ['help', 'helpCommand', ['help']],
110
+ ];
111
+ for (const [name, fn, args] of commandMap) {
112
+ it(`dispatches ${name}`, async () => {
113
+ await dispatch([...args]);
114
+ expect(mocks[fn]).toHaveBeenCalled();
115
+ });
116
+ }
117
+ it('exits on unknown command', async () => {
118
+ await expect(dispatch(['unknown'])).rejects.toThrow('process.exit(1)');
119
+ });
120
+ it('passes global flags to commands', async () => {
121
+ await dispatch(['create', '--api-key', 'mykey', '--json']);
122
+ const [, flags] = mocks.createCommand.mock.calls[0];
123
+ expect(flags.apiKey).toBe('mykey');
124
+ expect(flags.json).toBe(true);
125
+ });
126
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * sandbank — Unified sandbox SDK for AI agents
3
+ *
4
+ * This package re-exports everything from @douglas-agent/sandbank-core.
5
+ * For provider adapters, install them separately:
6
+ *
7
+ * pnpm add sandbank @douglas-agent/sandbank-daytona
8
+ * pnpm add sandbank @douglas-agent/sandbank-flyio
9
+ * pnpm add sandbank @douglas-agent/sandbank-cloudflare
10
+ * pnpm add sandbank @douglas-agent/sandbank-boxlite
11
+ */
12
+ export * from '@douglas-agent/sandbank-core';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,cAAc,8BAA8B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * sandbank — Unified sandbox SDK for AI agents
3
+ *
4
+ * This package re-exports everything from @douglas-agent/sandbank-core.
5
+ * For provider adapters, install them separately:
6
+ *
7
+ * pnpm add sandbank @douglas-agent/sandbank-daytona
8
+ * pnpm add sandbank @douglas-agent/sandbank-flyio
9
+ * pnpm add sandbank @douglas-agent/sandbank-cloudflare
10
+ * pnpm add sandbank @douglas-agent/sandbank-boxlite
11
+ */
12
+ export * from '@douglas-agent/sandbank-core';
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@douglas-agent/sandbank-cli",
3
+ "version": "0.5.4",
4
+ "description": "Unified sandbox SDK and CLI for AI agents — write once, run on any cloud",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Xeonice/sandbank-douglas-agent.git",
10
+ "directory": "packages/sandbank"
11
+ },
12
+ "homepage": "https://sandbank.dev",
13
+ "keywords": [
14
+ "sandbox",
15
+ "ai-agent",
16
+ "ai",
17
+ "agent",
18
+ "cloud",
19
+ "sdk",
20
+ "cli",
21
+ "daytona",
22
+ "flyio",
23
+ "cloudflare",
24
+ "multi-agent",
25
+ "orchestration"
26
+ ],
27
+ "bin": {
28
+ "sandbank": "dist/cli/index.js"
29
+ },
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "typecheck": "tsc --noEmit",
43
+ "clean": "rm -rf dist"
44
+ },
45
+ "dependencies": {
46
+ "@douglas-agent/sandbank-cloud": "^0.2.0",
47
+ "@douglas-agent/sandbank-core": "^0.3.6"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^25.5.0",
51
+ "typescript": "^5.7.3"
52
+ }
53
+ }