@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.
- package/README.md +229 -0
- package/dist/cli/api.d.ts +6 -0
- package/dist/cli/api.d.ts.map +1 -0
- package/dist/cli/api.js +8 -0
- package/dist/cli/api.test.d.ts +2 -0
- package/dist/cli/api.test.d.ts.map +1 -0
- package/dist/cli/api.test.js +31 -0
- package/dist/cli/auth.d.ts +25 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +34 -0
- package/dist/cli/auth.test.d.ts +2 -0
- package/dist/cli/auth.test.d.ts.map +1 -0
- package/dist/cli/auth.test.js +89 -0
- package/dist/cli/commands/addons.d.ts +3 -0
- package/dist/cli/commands/addons.d.ts.map +1 -0
- package/dist/cli/commands/addons.js +54 -0
- package/dist/cli/commands/clone.d.ts +3 -0
- package/dist/cli/commands/clone.d.ts.map +1 -0
- package/dist/cli/commands/clone.js +18 -0
- package/dist/cli/commands/commands.test.d.ts +2 -0
- package/dist/cli/commands/commands.test.d.ts.map +1 -0
- package/dist/cli/commands/commands.test.js +439 -0
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +57 -0
- package/dist/cli/commands/create.d.ts +3 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +30 -0
- package/dist/cli/commands/destroy.d.ts +3 -0
- package/dist/cli/commands/destroy.d.ts.map +1 -0
- package/dist/cli/commands/destroy.js +16 -0
- package/dist/cli/commands/exec.d.ts +3 -0
- package/dist/cli/commands/exec.d.ts.map +1 -0
- package/dist/cli/commands/exec.js +22 -0
- package/dist/cli/commands/get.d.ts +3 -0
- package/dist/cli/commands/get.d.ts.map +1 -0
- package/dist/cli/commands/get.js +21 -0
- package/dist/cli/commands/help.d.ts +2 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/help.js +39 -0
- package/dist/cli/commands/keep.d.ts +3 -0
- package/dist/cli/commands/keep.d.ts.map +1 -0
- package/dist/cli/commands/keep.js +25 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +14 -0
- package/dist/cli/commands/login.d.ts +3 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +25 -0
- package/dist/cli/commands/snapshot.d.ts +3 -0
- package/dist/cli/commands/snapshot.d.ts.map +1 -0
- package/dist/cli/commands/snapshot.js +78 -0
- package/dist/cli/config.d.ts +9 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +27 -0
- package/dist/cli/config.test.d.ts +2 -0
- package/dist/cli/config.test.d.ts.map +1 -0
- package/dist/cli/config.test.js +60 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +78 -0
- package/dist/cli/index.test.d.ts +2 -0
- package/dist/cli/index.test.d.ts.map +1 -0
- package/dist/cli/index.test.js +126 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|