@agentuity/cli 0.0.105 → 0.0.106
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +93 -21
- package/dist/cli.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +130 -4
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +8 -2
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +8 -9
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/cloud/db/create.d.ts.map +1 -1
- package/dist/cmd/cloud/db/create.js +11 -2
- package/dist/cmd/cloud/db/create.js.map +1 -1
- package/dist/cmd/cloud/db/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/db/delete.js +13 -2
- package/dist/cmd/cloud/db/delete.js.map +1 -1
- package/dist/cmd/cloud/deploy.js +3 -3
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/env/delete.js +1 -1
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/import.js +4 -4
- package/dist/cmd/cloud/env/import.js.map +1 -1
- package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/env/pull.js +7 -9
- package/dist/cmd/cloud/env/pull.js.map +1 -1
- package/dist/cmd/cloud/env/push.js +2 -2
- package/dist/cmd/cloud/env/push.js.map +1 -1
- package/dist/cmd/cloud/env/set.js +3 -3
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +2 -0
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/cloud/sandbox/cp.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/cp.js +334 -0
- package/dist/cmd/cloud/sandbox/cp.js.map +1 -0
- package/dist/cmd/cloud/sandbox/create.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/create.js +105 -0
- package/dist/cmd/cloud/sandbox/create.js.map +1 -0
- package/dist/cmd/cloud/sandbox/delete.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/delete.js +72 -0
- package/dist/cmd/cloud/sandbox/delete.js.map +1 -0
- package/dist/cmd/cloud/sandbox/exec.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/exec.js +211 -0
- package/dist/cmd/cloud/sandbox/exec.js.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/get.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/execution/get.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/get.js +96 -0
- package/dist/cmd/cloud/sandbox/execution/get.js.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/index.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/execution/index.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/index.js +24 -0
- package/dist/cmd/cloud/sandbox/execution/index.js.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/list.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/execution/list.js +100 -0
- package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -0
- package/dist/cmd/cloud/sandbox/get.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/get.js +95 -0
- package/dist/cmd/cloud/sandbox/get.js.map +1 -0
- package/dist/cmd/cloud/sandbox/index.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/index.js +45 -0
- package/dist/cmd/cloud/sandbox/index.js.map +1 -0
- package/dist/cmd/cloud/sandbox/list.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/list.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/list.js +120 -0
- package/dist/cmd/cloud/sandbox/list.js.map +1 -0
- package/dist/cmd/cloud/sandbox/run.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/run.js +152 -0
- package/dist/cmd/cloud/sandbox/run.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.js +65 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.js +66 -0
- package/dist/cmd/cloud/sandbox/snapshot/delete.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.js +154 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.js +27 -0
- package/dist/cmd/cloud/sandbox/snapshot/index.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.js +83 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.js +63 -0
- package/dist/cmd/cloud/sandbox/snapshot/tag.js.map +1 -0
- package/dist/cmd/cloud/sandbox/util.d.ts +15 -0
- package/dist/cmd/cloud/sandbox/util.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/util.js +50 -0
- package/dist/cmd/cloud/sandbox/util.js.map +1 -0
- package/dist/cmd/cloud/secret/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/delete.js +3 -3
- package/dist/cmd/cloud/secret/delete.js.map +1 -1
- package/dist/cmd/cloud/secret/import.js +6 -6
- package/dist/cmd/cloud/secret/import.js.map +1 -1
- package/dist/cmd/cloud/secret/index.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/index.js +1 -0
- package/dist/cmd/cloud/secret/index.js.map +1 -1
- package/dist/cmd/cloud/secret/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/pull.js +7 -9
- package/dist/cmd/cloud/secret/pull.js.map +1 -1
- package/dist/cmd/cloud/secret/push.js +3 -3
- package/dist/cmd/cloud/secret/push.js.map +1 -1
- package/dist/cmd/cloud/secret/set.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/set.js +3 -3
- package/dist/cmd/cloud/secret/set.js.map +1 -1
- package/dist/cmd/cloud/storage/create.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/create.js +13 -2
- package/dist/cmd/cloud/storage/create.js.map +1 -1
- package/dist/cmd/cloud/storage/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/delete.js +13 -2
- package/dist/cmd/cloud/storage/delete.js.map +1 -1
- package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/list.js +2 -13
- package/dist/cmd/cloud/stream/list.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +1 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/profile/create.d.ts.map +1 -1
- package/dist/cmd/profile/create.js +1 -0
- package/dist/cmd/profile/create.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +27 -10
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/config.d.ts +0 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/env-util.d.ts +16 -8
- package/dist/env-util.d.ts.map +1 -1
- package/dist/env-util.js +46 -18
- package/dist/env-util.js.map +1 -1
- package/dist/tui.d.ts +20 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +82 -23
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +18 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
- package/src/cli.ts +99 -21
- package/src/cmd/build/ast.ts +163 -4
- package/src/cmd/build/entry-generator.ts +8 -2
- package/src/cmd/build/vite/registry-generator.ts +8 -11
- package/src/cmd/cloud/db/create.ts +13 -2
- package/src/cmd/cloud/db/delete.ts +15 -2
- package/src/cmd/cloud/deploy.ts +3 -3
- package/src/cmd/cloud/env/delete.ts +1 -1
- package/src/cmd/cloud/env/import.ts +4 -4
- package/src/cmd/cloud/env/pull.ts +7 -16
- package/src/cmd/cloud/env/push.ts +2 -2
- package/src/cmd/cloud/env/set.ts +3 -3
- package/src/cmd/cloud/index.ts +2 -0
- package/src/cmd/cloud/sandbox/cp.ts +531 -0
- package/src/cmd/cloud/sandbox/create.ts +114 -0
- package/src/cmd/cloud/sandbox/delete.ts +80 -0
- package/src/cmd/cloud/sandbox/exec.ts +254 -0
- package/src/cmd/cloud/sandbox/execution/get.ts +106 -0
- package/src/cmd/cloud/sandbox/execution/index.ts +25 -0
- package/src/cmd/cloud/sandbox/execution/list.ts +111 -0
- package/src/cmd/cloud/sandbox/get.ts +104 -0
- package/src/cmd/cloud/sandbox/index.ts +46 -0
- package/src/cmd/cloud/sandbox/list.ts +129 -0
- package/src/cmd/cloud/sandbox/run.ts +170 -0
- package/src/cmd/cloud/sandbox/snapshot/create.ts +71 -0
- package/src/cmd/cloud/sandbox/snapshot/delete.ts +74 -0
- package/src/cmd/cloud/sandbox/snapshot/get.ts +188 -0
- package/src/cmd/cloud/sandbox/snapshot/index.ts +28 -0
- package/src/cmd/cloud/sandbox/snapshot/list.ts +90 -0
- package/src/cmd/cloud/sandbox/snapshot/tag.ts +70 -0
- package/src/cmd/cloud/sandbox/util.ts +59 -0
- package/src/cmd/cloud/secret/delete.ts +8 -3
- package/src/cmd/cloud/secret/import.ts +6 -6
- package/src/cmd/cloud/secret/index.ts +1 -0
- package/src/cmd/cloud/secret/pull.ts +7 -16
- package/src/cmd/cloud/secret/push.ts +3 -3
- package/src/cmd/cloud/secret/set.ts +8 -3
- package/src/cmd/cloud/storage/create.ts +15 -2
- package/src/cmd/cloud/storage/delete.ts +15 -2
- package/src/cmd/cloud/stream/list.ts +2 -9
- package/src/cmd/dev/index.ts +1 -0
- package/src/cmd/profile/create.ts +1 -0
- package/src/cmd/project/template-flow.ts +29 -13
- package/src/config.ts +3 -0
- package/src/env-util.ts +52 -21
- package/src/tui.ts +131 -39
- package/src/types.ts +18 -16
|
@@ -3,13 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { createSubcommand } from '../../../types';
|
|
4
4
|
import * as tui from '../../../tui';
|
|
5
5
|
import { projectGet } from '@agentuity/server';
|
|
6
|
-
import {
|
|
7
|
-
findEnvFile,
|
|
8
|
-
findExistingEnvFile,
|
|
9
|
-
readEnvFile,
|
|
10
|
-
writeEnvFile,
|
|
11
|
-
mergeEnvVars,
|
|
12
|
-
} from '../../../env-util';
|
|
6
|
+
import { findExistingEnvFile, readEnvFile, writeEnvFile, mergeEnvVars } from '../../../env-util';
|
|
13
7
|
import { getCommand } from '../../../command-prefix';
|
|
14
8
|
|
|
15
9
|
const EnvPullResponseSchema = z.object({
|
|
@@ -21,7 +15,7 @@ const EnvPullResponseSchema = z.object({
|
|
|
21
15
|
|
|
22
16
|
export const pullSubcommand = createSubcommand({
|
|
23
17
|
name: 'pull',
|
|
24
|
-
description: 'Pull environment variables from cloud to local .env
|
|
18
|
+
description: 'Pull environment variables from cloud to local .env file',
|
|
25
19
|
tags: ['slow', 'requires-auth', 'requires-project'],
|
|
26
20
|
idempotent: true,
|
|
27
21
|
examples: [
|
|
@@ -45,14 +39,11 @@ export const pullSubcommand = createSubcommand({
|
|
|
45
39
|
return projectGet(apiClient, { id: project.projectId, mask: false });
|
|
46
40
|
});
|
|
47
41
|
|
|
48
|
-
const cloudEnv = projectData.env
|
|
42
|
+
const cloudEnv = { ...projectData.env, ...projectData.secrets }; // env pull with actually do both secrets and env since thats likely what the user would want
|
|
49
43
|
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
const localEnv = await readEnvFile(
|
|
53
|
-
|
|
54
|
-
// Target file is always .env.production
|
|
55
|
-
const targetEnvPath = await findEnvFile(projectDir);
|
|
44
|
+
// Target file is always .env
|
|
45
|
+
const targetEnvPath = await findExistingEnvFile(projectDir);
|
|
46
|
+
const localEnv = await readEnvFile(targetEnvPath);
|
|
56
47
|
|
|
57
48
|
// Merge: cloud values override local if force=true, otherwise keep local
|
|
58
49
|
let mergedEnv: Record<string, string>;
|
|
@@ -64,7 +55,7 @@ export const pullSubcommand = createSubcommand({
|
|
|
64
55
|
mergedEnv = mergeEnvVars(cloudEnv, localEnv);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
// Write to .env
|
|
58
|
+
// Write to .env (skip AGENTUITY_ keys)
|
|
68
59
|
await writeEnvFile(targetEnvPath, mergedEnv, {
|
|
69
60
|
skipKeys: Object.keys(mergedEnv).filter((k) => k.startsWith('AGENTUITY_')),
|
|
70
61
|
});
|
|
@@ -13,7 +13,7 @@ const EnvPushResponseSchema = z.object({
|
|
|
13
13
|
|
|
14
14
|
export const pushSubcommand = createSubcommand({
|
|
15
15
|
name: 'push',
|
|
16
|
-
description: 'Push environment variables from local .env
|
|
16
|
+
description: 'Push environment variables from local .env file to cloud',
|
|
17
17
|
tags: [
|
|
18
18
|
'mutating',
|
|
19
19
|
'updates-resource',
|
|
@@ -33,7 +33,7 @@ export const pushSubcommand = createSubcommand({
|
|
|
33
33
|
async handler(ctx) {
|
|
34
34
|
const { apiClient, project, projectDir } = ctx;
|
|
35
35
|
|
|
36
|
-
// Read local env file (prefer .env
|
|
36
|
+
// Read local env file (prefer .env)
|
|
37
37
|
const envFilePath = await findExistingEnvFile(projectDir);
|
|
38
38
|
const localEnv = await readEnvFile(envFilePath);
|
|
39
39
|
|
package/src/cmd/cloud/env/set.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createSubcommand } from '../../../types';
|
|
|
3
3
|
import * as tui from '../../../tui';
|
|
4
4
|
import { projectEnvUpdate } from '@agentuity/server';
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
findExistingEnvFile,
|
|
7
7
|
readEnvFile,
|
|
8
8
|
writeEnvFile,
|
|
9
9
|
filterAgentuitySdkKeys,
|
|
@@ -75,8 +75,8 @@ export const setSubcommand = createSubcommand({
|
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
// Update local .env
|
|
79
|
-
const envFilePath = await
|
|
78
|
+
// Update local .env file
|
|
79
|
+
const envFilePath = await findExistingEnvFile(projectDir);
|
|
80
80
|
const currentEnv = await readEnvFile(envFilePath);
|
|
81
81
|
currentEnv[args.key] = args.value;
|
|
82
82
|
|
package/src/cmd/cloud/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import secretCommand from './secret';
|
|
|
15
15
|
import apikeyCommand from './apikey';
|
|
16
16
|
import streamCommand from './stream';
|
|
17
17
|
import vectorCommand from './vector';
|
|
18
|
+
import sandboxCommand from './sandbox';
|
|
18
19
|
import { getCommand } from '../../command-prefix';
|
|
19
20
|
|
|
20
21
|
export const command = createCommand({
|
|
@@ -31,6 +32,7 @@ export const command = createCommand({
|
|
|
31
32
|
agentCommand,
|
|
32
33
|
streamCommand,
|
|
33
34
|
vectorCommand,
|
|
35
|
+
sandboxCommand,
|
|
34
36
|
envCommand,
|
|
35
37
|
secretCommand,
|
|
36
38
|
deploySubcommand,
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve, basename, join, relative } from 'node:path';
|
|
4
|
+
import { createCommand } from '../../../types';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { createSandboxClient } from './util';
|
|
7
|
+
import { getCommand } from '../../../command-prefix';
|
|
8
|
+
import {
|
|
9
|
+
sandboxWriteFiles,
|
|
10
|
+
sandboxReadFile,
|
|
11
|
+
sandboxExecute,
|
|
12
|
+
executionGet,
|
|
13
|
+
type APIClient,
|
|
14
|
+
} from '@agentuity/server';
|
|
15
|
+
import type { Logger, FileToWrite } from '@agentuity/core';
|
|
16
|
+
|
|
17
|
+
const POLL_INTERVAL_MS = 500;
|
|
18
|
+
const MAX_POLL_ATTEMPTS = 600;
|
|
19
|
+
|
|
20
|
+
interface ParsedPath {
|
|
21
|
+
sandboxId: string | null;
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parsePath(pathArg: string): ParsedPath {
|
|
26
|
+
const colonIndex = pathArg.indexOf(':');
|
|
27
|
+
if (colonIndex === -1) {
|
|
28
|
+
return { sandboxId: null, path: pathArg };
|
|
29
|
+
}
|
|
30
|
+
const prefix = pathArg.slice(0, colonIndex);
|
|
31
|
+
const path = pathArg.slice(colonIndex + 1);
|
|
32
|
+
if (prefix.startsWith('snbx_') || prefix.startsWith('sbx_')) {
|
|
33
|
+
return { sandboxId: prefix, path };
|
|
34
|
+
}
|
|
35
|
+
return { sandboxId: null, path: pathArg };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SandboxCpResponseSchema = z.object({
|
|
39
|
+
source: z.string().describe('Source path'),
|
|
40
|
+
destination: z.string().describe('Destination path'),
|
|
41
|
+
bytesTransferred: z.number().describe('Number of bytes transferred'),
|
|
42
|
+
filesTransferred: z.number().describe('Number of files transferred'),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const cpSubcommand = createCommand({
|
|
46
|
+
name: 'cp',
|
|
47
|
+
aliases: ['copy'],
|
|
48
|
+
description: 'Copy files or directories to or from a sandbox',
|
|
49
|
+
tags: ['slow', 'requires-auth'],
|
|
50
|
+
requires: { auth: true, region: true, org: true },
|
|
51
|
+
examples: [
|
|
52
|
+
{
|
|
53
|
+
command: getCommand('cloud sandbox cp ./local-file.txt snbx_abc123:/path/to/file.txt'),
|
|
54
|
+
description: 'Copy a local file to a sandbox',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
command: getCommand('cloud sandbox cp snbx_abc123:/path/to/file.txt ./local-file.txt'),
|
|
58
|
+
description: 'Copy a file from a sandbox to local',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
command: getCommand('cloud sandbox cp --recursive ./local-dir snbx_abc123:/path/to/dir'),
|
|
62
|
+
description: 'Copy a local directory to a sandbox recursively',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
command: getCommand('cloud sandbox cp -r snbx_abc123:/path/to/dir ./local-dir'),
|
|
66
|
+
description: 'Copy a directory from a sandbox to local recursively',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
schema: {
|
|
70
|
+
args: z.object({
|
|
71
|
+
source: z.string().describe('Source path (local path or sandboxId:/remote/path)'),
|
|
72
|
+
destination: z
|
|
73
|
+
.string()
|
|
74
|
+
.describe('Destination path (local path or sandboxId:/remote/path)'),
|
|
75
|
+
}),
|
|
76
|
+
options: z.object({
|
|
77
|
+
timeout: z.string().optional().describe('Operation timeout (e.g., "5m", "1h")'),
|
|
78
|
+
recursive: z.boolean().default(false).optional().describe('Copy directories recursively'),
|
|
79
|
+
}),
|
|
80
|
+
aliases: {
|
|
81
|
+
recursive: ['r'],
|
|
82
|
+
},
|
|
83
|
+
response: SandboxCpResponseSchema,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async handler(ctx) {
|
|
87
|
+
const { args, opts, options, auth, region, logger, orgId } = ctx;
|
|
88
|
+
|
|
89
|
+
const source = parsePath(args.source);
|
|
90
|
+
const destination = parsePath(args.destination);
|
|
91
|
+
|
|
92
|
+
if (source.sandboxId && destination.sandboxId) {
|
|
93
|
+
logger.fatal(
|
|
94
|
+
'Cannot copy between two sandboxes. Use a local path as source or destination.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!source.sandboxId && !destination.sandboxId) {
|
|
99
|
+
logger.fatal(
|
|
100
|
+
'At least one path must include a sandbox ID (e.g., snbx_abc123:/path/to/file)'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const client = createSandboxClient(logger, auth, region);
|
|
105
|
+
const recursive = opts.recursive ?? false;
|
|
106
|
+
|
|
107
|
+
if (source.sandboxId) {
|
|
108
|
+
return await downloadFromSandbox(
|
|
109
|
+
client,
|
|
110
|
+
logger,
|
|
111
|
+
orgId,
|
|
112
|
+
source.sandboxId,
|
|
113
|
+
source.path,
|
|
114
|
+
destination.path,
|
|
115
|
+
opts.timeout,
|
|
116
|
+
recursive,
|
|
117
|
+
options.json ?? false
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
return await uploadToSandbox(
|
|
121
|
+
client,
|
|
122
|
+
logger,
|
|
123
|
+
orgId,
|
|
124
|
+
destination.sandboxId!,
|
|
125
|
+
source.path,
|
|
126
|
+
destination.path,
|
|
127
|
+
opts.timeout,
|
|
128
|
+
recursive,
|
|
129
|
+
options.json ?? false
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
function getAllFiles(dirPath: string, basePath: string = dirPath): string[] {
|
|
136
|
+
const files: string[] = [];
|
|
137
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
138
|
+
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
const fullPath = join(dirPath, entry.name);
|
|
141
|
+
if (entry.isDirectory()) {
|
|
142
|
+
files.push(...getAllFiles(fullPath, basePath));
|
|
143
|
+
} else if (entry.isFile()) {
|
|
144
|
+
files.push(fullPath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return files;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function uploadToSandbox(
|
|
152
|
+
client: APIClient,
|
|
153
|
+
logger: Logger,
|
|
154
|
+
orgId: string,
|
|
155
|
+
sandboxId: string,
|
|
156
|
+
localPath: string,
|
|
157
|
+
remotePath: string,
|
|
158
|
+
timeout: string | undefined,
|
|
159
|
+
recursive: boolean,
|
|
160
|
+
jsonOutput: boolean
|
|
161
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
162
|
+
const resolvedPath = resolve(localPath);
|
|
163
|
+
|
|
164
|
+
if (!(await Bun.file(resolvedPath).exists())) {
|
|
165
|
+
const stat = statSync(resolvedPath, { throwIfNoEntry: false });
|
|
166
|
+
if (!stat) {
|
|
167
|
+
logger.fatal(`Local path not found: ${localPath}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const stat = statSync(resolvedPath);
|
|
172
|
+
|
|
173
|
+
if (stat.isDirectory()) {
|
|
174
|
+
if (!recursive) {
|
|
175
|
+
logger.fatal(`${localPath} is a directory. Use -r/--recursive to copy directories.`);
|
|
176
|
+
}
|
|
177
|
+
return await uploadDirectory(
|
|
178
|
+
client,
|
|
179
|
+
logger,
|
|
180
|
+
orgId,
|
|
181
|
+
sandboxId,
|
|
182
|
+
resolvedPath,
|
|
183
|
+
remotePath,
|
|
184
|
+
timeout,
|
|
185
|
+
jsonOutput
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return await uploadSingleFile(
|
|
190
|
+
client,
|
|
191
|
+
logger,
|
|
192
|
+
orgId,
|
|
193
|
+
sandboxId,
|
|
194
|
+
resolvedPath,
|
|
195
|
+
localPath,
|
|
196
|
+
remotePath,
|
|
197
|
+
timeout,
|
|
198
|
+
jsonOutput
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function uploadSingleFile(
|
|
203
|
+
client: APIClient,
|
|
204
|
+
logger: Logger,
|
|
205
|
+
orgId: string,
|
|
206
|
+
sandboxId: string,
|
|
207
|
+
resolvedPath: string,
|
|
208
|
+
displayPath: string,
|
|
209
|
+
remotePath: string,
|
|
210
|
+
_timeout: string | undefined,
|
|
211
|
+
jsonOutput: boolean
|
|
212
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
213
|
+
const buffer = readFileSync(resolvedPath);
|
|
214
|
+
|
|
215
|
+
let targetPath = remotePath;
|
|
216
|
+
if (!remotePath || remotePath === '' || remotePath.endsWith('/')) {
|
|
217
|
+
const baseDir = remotePath || '';
|
|
218
|
+
targetPath = baseDir ? baseDir + basename(resolvedPath) : basename(resolvedPath);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const files: FileToWrite[] = [{ path: targetPath, content: buffer }];
|
|
222
|
+
|
|
223
|
+
await sandboxWriteFiles(client, { sandboxId, files, orgId });
|
|
224
|
+
|
|
225
|
+
if (!jsonOutput) {
|
|
226
|
+
tui.success(`Copied ${displayPath} → ${sandboxId}:${targetPath} (${buffer.length} bytes)`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
source: displayPath,
|
|
231
|
+
destination: `${sandboxId}:${targetPath}`,
|
|
232
|
+
bytesTransferred: buffer.length,
|
|
233
|
+
filesTransferred: 1,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function uploadDirectory(
|
|
238
|
+
client: APIClient,
|
|
239
|
+
logger: Logger,
|
|
240
|
+
orgId: string,
|
|
241
|
+
sandboxId: string,
|
|
242
|
+
localDir: string,
|
|
243
|
+
remotePath: string,
|
|
244
|
+
_timeout: string | undefined,
|
|
245
|
+
jsonOutput: boolean
|
|
246
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
247
|
+
const allFiles = getAllFiles(localDir);
|
|
248
|
+
|
|
249
|
+
if (allFiles.length === 0) {
|
|
250
|
+
logger.fatal(`Directory is empty: ${localDir}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const files: FileToWrite[] = [];
|
|
254
|
+
let totalBytes = 0;
|
|
255
|
+
const effectiveRemotePath = remotePath || basename(localDir);
|
|
256
|
+
const baseRemotePath = effectiveRemotePath.endsWith('/')
|
|
257
|
+
? effectiveRemotePath.slice(0, -1)
|
|
258
|
+
: effectiveRemotePath;
|
|
259
|
+
|
|
260
|
+
for (const filePath of allFiles) {
|
|
261
|
+
const relativePath = relative(localDir, filePath);
|
|
262
|
+
const targetPath = `${baseRemotePath}/${relativePath}`;
|
|
263
|
+
const buffer = readFileSync(filePath);
|
|
264
|
+
files.push({ path: targetPath, content: buffer });
|
|
265
|
+
totalBytes += buffer.length;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await sandboxWriteFiles(client, { sandboxId, files, orgId });
|
|
269
|
+
|
|
270
|
+
if (!jsonOutput) {
|
|
271
|
+
tui.success(
|
|
272
|
+
`Copied ${localDir} → ${sandboxId}:${baseRemotePath} (${allFiles.length} files, ${totalBytes} bytes)`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
source: localDir,
|
|
278
|
+
destination: `${sandboxId}:${baseRemotePath}`,
|
|
279
|
+
bytesTransferred: totalBytes,
|
|
280
|
+
filesTransferred: allFiles.length,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function downloadFromSandbox(
|
|
285
|
+
client: APIClient,
|
|
286
|
+
logger: Logger,
|
|
287
|
+
orgId: string,
|
|
288
|
+
sandboxId: string,
|
|
289
|
+
remotePath: string,
|
|
290
|
+
localPath: string,
|
|
291
|
+
timeout: string | undefined,
|
|
292
|
+
recursive: boolean,
|
|
293
|
+
jsonOutput: boolean
|
|
294
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
295
|
+
if (recursive) {
|
|
296
|
+
return await downloadDirectory(
|
|
297
|
+
client,
|
|
298
|
+
logger,
|
|
299
|
+
orgId,
|
|
300
|
+
sandboxId,
|
|
301
|
+
remotePath,
|
|
302
|
+
localPath,
|
|
303
|
+
timeout,
|
|
304
|
+
jsonOutput
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return await downloadSingleFile(
|
|
309
|
+
client,
|
|
310
|
+
logger,
|
|
311
|
+
orgId,
|
|
312
|
+
sandboxId,
|
|
313
|
+
remotePath,
|
|
314
|
+
localPath,
|
|
315
|
+
timeout,
|
|
316
|
+
jsonOutput
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function downloadSingleFile(
|
|
321
|
+
client: APIClient,
|
|
322
|
+
logger: Logger,
|
|
323
|
+
orgId: string,
|
|
324
|
+
sandboxId: string,
|
|
325
|
+
remotePath: string,
|
|
326
|
+
localPath: string,
|
|
327
|
+
_timeout: string | undefined,
|
|
328
|
+
jsonOutput: boolean
|
|
329
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
330
|
+
const stream = await sandboxReadFile(client, { sandboxId, path: remotePath, orgId });
|
|
331
|
+
|
|
332
|
+
const chunks: Uint8Array[] = [];
|
|
333
|
+
const reader = stream.getReader();
|
|
334
|
+
while (true) {
|
|
335
|
+
const { done, value } = await reader.read();
|
|
336
|
+
if (done) break;
|
|
337
|
+
if (value) chunks.push(value);
|
|
338
|
+
}
|
|
339
|
+
const buffer = Buffer.concat(chunks);
|
|
340
|
+
|
|
341
|
+
let targetPath = localPath;
|
|
342
|
+
if (localPath.endsWith('/') || localPath === '.') {
|
|
343
|
+
targetPath = resolve(localPath, basename(remotePath));
|
|
344
|
+
} else {
|
|
345
|
+
targetPath = resolve(localPath);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const dir = dirname(targetPath);
|
|
349
|
+
mkdirSync(dir, { recursive: true });
|
|
350
|
+
|
|
351
|
+
writeFileSync(targetPath, buffer);
|
|
352
|
+
|
|
353
|
+
if (!jsonOutput) {
|
|
354
|
+
tui.success(`Copied ${sandboxId}:${remotePath} → ${targetPath} (${buffer.length} bytes)`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
source: `${sandboxId}:${remotePath}`,
|
|
359
|
+
destination: targetPath,
|
|
360
|
+
bytesTransferred: buffer.length,
|
|
361
|
+
filesTransferred: 1,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function downloadDirectory(
|
|
366
|
+
client: APIClient,
|
|
367
|
+
logger: Logger,
|
|
368
|
+
orgId: string,
|
|
369
|
+
sandboxId: string,
|
|
370
|
+
remotePath: string,
|
|
371
|
+
localPath: string,
|
|
372
|
+
timeout: string | undefined,
|
|
373
|
+
jsonOutput: boolean
|
|
374
|
+
): Promise<z.infer<typeof SandboxCpResponseSchema>> {
|
|
375
|
+
const listExecution = await sandboxExecute(client, {
|
|
376
|
+
sandboxId,
|
|
377
|
+
options: {
|
|
378
|
+
command: ['find', remotePath, '-type', 'f'],
|
|
379
|
+
timeout,
|
|
380
|
+
},
|
|
381
|
+
orgId,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const listChunks: Buffer[] = [];
|
|
385
|
+
if (listExecution.stdoutStreamUrl) {
|
|
386
|
+
await streamToBuffer(listExecution.stdoutStreamUrl, listChunks, logger);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
await waitForExecution(client, orgId, listExecution.executionId, logger);
|
|
390
|
+
|
|
391
|
+
const fileList = Buffer.concat(listChunks)
|
|
392
|
+
.toString('utf-8')
|
|
393
|
+
.trim()
|
|
394
|
+
.split('\n')
|
|
395
|
+
.filter((f) => f.length > 0);
|
|
396
|
+
|
|
397
|
+
if (fileList.length === 0) {
|
|
398
|
+
logger.fatal(`No files found in directory: ${remotePath}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const baseRemotePath = remotePath.endsWith('/') ? remotePath.slice(0, -1) : remotePath;
|
|
402
|
+
const baseLocalPath = resolve(localPath);
|
|
403
|
+
let totalBytes = 0;
|
|
404
|
+
|
|
405
|
+
for (const remoteFile of fileList) {
|
|
406
|
+
const relativePath = remoteFile.startsWith(baseRemotePath + '/')
|
|
407
|
+
? remoteFile.slice(baseRemotePath.length + 1)
|
|
408
|
+
: basename(remoteFile);
|
|
409
|
+
|
|
410
|
+
const localFilePath = join(baseLocalPath, relativePath);
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const stream = await sandboxReadFile(client, { sandboxId, path: remoteFile, orgId });
|
|
414
|
+
const chunks: Uint8Array[] = [];
|
|
415
|
+
const reader = stream.getReader();
|
|
416
|
+
while (true) {
|
|
417
|
+
const { done, value } = await reader.read();
|
|
418
|
+
if (done) break;
|
|
419
|
+
if (value) chunks.push(value);
|
|
420
|
+
}
|
|
421
|
+
const buffer = Buffer.concat(chunks);
|
|
422
|
+
totalBytes += buffer.length;
|
|
423
|
+
|
|
424
|
+
const dir = dirname(localFilePath);
|
|
425
|
+
mkdirSync(dir, { recursive: true });
|
|
426
|
+
writeFileSync(localFilePath, buffer);
|
|
427
|
+
|
|
428
|
+
if (!jsonOutput) {
|
|
429
|
+
logger.info(`Downloaded ${remoteFile} (${buffer.length} bytes)`);
|
|
430
|
+
}
|
|
431
|
+
} catch (err) {
|
|
432
|
+
logger.warn(`Failed to read file: ${remoteFile}, skipping: ${err}`);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!jsonOutput) {
|
|
438
|
+
tui.success(
|
|
439
|
+
`Copied ${sandboxId}:${baseRemotePath} → ${baseLocalPath} (${fileList.length} files, ${totalBytes} bytes)`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
source: `${sandboxId}:${baseRemotePath}`,
|
|
445
|
+
destination: baseLocalPath,
|
|
446
|
+
bytesTransferred: totalBytes,
|
|
447
|
+
filesTransferred: fileList.length,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function waitForExecution(
|
|
452
|
+
client: APIClient,
|
|
453
|
+
orgId: string,
|
|
454
|
+
executionId: string,
|
|
455
|
+
logger: Logger
|
|
456
|
+
): Promise<void> {
|
|
457
|
+
let attempts = 0;
|
|
458
|
+
|
|
459
|
+
while (attempts < MAX_POLL_ATTEMPTS) {
|
|
460
|
+
await sleep(POLL_INTERVAL_MS);
|
|
461
|
+
attempts++;
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const execInfo = await executionGet(client, { executionId, orgId });
|
|
465
|
+
|
|
466
|
+
if (
|
|
467
|
+
execInfo.status === 'completed' ||
|
|
468
|
+
execInfo.status === 'failed' ||
|
|
469
|
+
execInfo.status === 'timeout' ||
|
|
470
|
+
execInfo.status === 'cancelled'
|
|
471
|
+
) {
|
|
472
|
+
if (execInfo.status === 'failed' || execInfo.status === 'timeout') {
|
|
473
|
+
logger.fatal(`Execution ${execInfo.status}: ${executionId}`);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
} catch (err) {
|
|
478
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
479
|
+
throw err;
|
|
480
|
+
}
|
|
481
|
+
logger.debug('poll error: %s', err);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
logger.fatal('Execution timed out waiting for completion');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function streamToBuffer(url: string, chunks: Buffer[], logger: Logger): Promise<void> {
|
|
490
|
+
const maxRetries = 10;
|
|
491
|
+
const retryDelay = 200;
|
|
492
|
+
|
|
493
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
494
|
+
try {
|
|
495
|
+
if (attempt > 0) {
|
|
496
|
+
logger.debug('stream retry attempt %d', attempt + 1);
|
|
497
|
+
await sleep(retryDelay);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const response = await fetch(url);
|
|
501
|
+
|
|
502
|
+
if (!response.ok || !response.body) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const reader = response.body.getReader();
|
|
507
|
+
|
|
508
|
+
while (true) {
|
|
509
|
+
const { done, value } = await reader.read();
|
|
510
|
+
if (done) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (value) {
|
|
515
|
+
chunks.push(Buffer.from(value));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} catch (err) {
|
|
519
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
520
|
+
throw err;
|
|
521
|
+
}
|
|
522
|
+
logger.debug('stream error: %s', err);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function sleep(ms: number): Promise<void> {
|
|
528
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export default cpSubcommand;
|