@bctrl/cli 0.1.2 → 0.1.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 +95 -97
- package/dist/api/auth.d.ts +5 -6
- package/dist/api/auth.js +4 -5
- package/dist/api/client.d.ts +1 -0
- package/dist/api/client.js +3 -0
- package/dist/api/errors.js +2 -2
- package/dist/commands/ai/index.js +59 -102
- package/dist/commands/{browser → api-key}/index.d.ts +1 -1
- package/dist/commands/api-key/index.js +47 -0
- package/dist/commands/auth/login.js +2 -1
- package/dist/commands/auth/status.js +5 -0
- package/dist/commands/browser-extension/index.d.ts +3 -0
- package/dist/commands/browser-extension/index.js +86 -0
- package/dist/commands/file/index.js +9 -3
- package/dist/commands/{invocation → help}/index.d.ts +1 -1
- package/dist/commands/help/index.js +17 -0
- package/dist/commands/proxy/index.d.ts +3 -0
- package/dist/commands/proxy/index.js +66 -0
- package/dist/commands/run/index.js +205 -155
- package/dist/commands/runtime/index.js +265 -116
- package/dist/commands/shared/help.js +1 -1
- package/dist/commands/shared/rest.d.ts +1 -0
- package/dist/commands/shared/rest.js +14 -6
- package/dist/commands/space/index.js +17 -32
- package/dist/commands/space/list.d.ts +1 -11
- package/dist/commands/space/list.js +14 -23
- package/dist/commands/subaccount/index.js +29 -80
- package/dist/commands/tool/index.js +15 -7
- package/dist/commands/tool-call/index.js +6 -1
- package/dist/commands/toolset/index.js +6 -3
- package/dist/commands/usage/index.d.ts +3 -0
- package/dist/commands/usage/index.js +13 -0
- package/dist/commands/vault/index.js +104 -25
- package/dist/config/auth-store.d.ts +10 -12
- package/dist/config/auth-store.js +4 -5
- package/dist/factory.js +1 -1
- package/dist/generated/help.d.ts +125 -110
- package/dist/generated/help.js +175 -141
- package/dist/root.js +10 -4
- package/package.json +47 -47
- package/dist/commands/browser/index.js +0 -53
- package/dist/commands/invocation/index.js +0 -36
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
3
|
-
import { addCliHelp } from '../shared/help.js';
|
|
4
|
-
import { readBlob, readJsonFile, writeBinary } from '../shared/io.js';
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import { readBlob, readJsonFile } from '../shared/io.js';
|
|
5
3
|
import { parsePositiveInteger } from '../shared/options.js';
|
|
6
4
|
import { addOutputFlags, outputData } from '../shared/output.js';
|
|
7
|
-
import { addPaginationFlags, createJsonBodyCommand, createListCommand, createViewCommand,
|
|
5
|
+
import { addPaginationFlags, createDeleteCommand, createJsonBodyCommand, createListCommand, createViewCommand, requestAndPrint, } from '../shared/rest.js';
|
|
8
6
|
export function createRuntimeCommand(factory) {
|
|
9
|
-
const command = new Command('runtime').description('
|
|
7
|
+
const command = new Command('runtime').description('Manage runtimes');
|
|
10
8
|
command.addCommand(createListCommand(factory, {
|
|
11
9
|
description: 'List runtimes',
|
|
12
10
|
path: '/runtimes',
|
|
@@ -21,188 +19,339 @@ export function createRuntimeCommand(factory) {
|
|
|
21
19
|
}),
|
|
22
20
|
}));
|
|
23
21
|
command.addCommand(createViewCommand(factory, {
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
name: 'get',
|
|
23
|
+
description: 'Get a runtime',
|
|
24
|
+
path: '/runtimes/{runtimeId}',
|
|
25
|
+
argName: 'runtimeId',
|
|
26
26
|
}));
|
|
27
|
-
command.addCommand(
|
|
27
|
+
command.addCommand(createRuntimeCreateCommand(factory));
|
|
28
|
+
command.addCommand(createRuntimePatchCommand(factory));
|
|
29
|
+
command.addCommand(createDeleteCommand(factory, {
|
|
30
|
+
description: 'Delete a runtime and its browser state',
|
|
31
|
+
path: '/runtimes/{runtimeId}',
|
|
32
|
+
argNames: ['runtimeId'],
|
|
33
|
+
configure: (cmd) => cmd.option('--force', 'Stop the runtime first if it is still active'),
|
|
34
|
+
query: (_args, options) => ({ force: options.force === true ? true : undefined }),
|
|
35
|
+
}));
|
|
36
|
+
command.addCommand(createRuntimeStartCommand(factory));
|
|
37
|
+
command.addCommand(addOutputFlags(new Command('stop').description('Stop a runtime').argument('<runtimeId>')).action(async (runtimeId, options) => {
|
|
38
|
+
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(runtimeId)}/stop`, {
|
|
39
|
+
output: options,
|
|
40
|
+
});
|
|
41
|
+
}));
|
|
42
|
+
command.addCommand(createRuntimeFileCommand(factory));
|
|
43
|
+
command.addCommand(createRuntimeInvocationCommand(factory));
|
|
44
|
+
command.addCommand(createRuntimeTargetCommand(factory));
|
|
45
|
+
return command;
|
|
46
|
+
}
|
|
47
|
+
function createRuntimeTargetCommand(factory) {
|
|
48
|
+
const command = new Command('target').description('Manage runtime targets (tabs)');
|
|
49
|
+
command.addCommand(addOutputFlags(new Command('list').description('List targets').argument('<runtimeId>')).action(async (runtimeId, options) => {
|
|
50
|
+
await requestAndPrint(factory, 'get', `/runtimes/${encodeURIComponent(runtimeId)}/targets`, {
|
|
51
|
+
output: options,
|
|
52
|
+
});
|
|
53
|
+
}));
|
|
54
|
+
command.addCommand(addOutputFlags(new Command('create')
|
|
55
|
+
.description('Open a new target')
|
|
56
|
+
.argument('<runtimeId>')
|
|
57
|
+
.option('--uri <uri>', 'Navigate the new target to this URI')
|
|
58
|
+
.option('--activate', 'Focus the new target')).action(async (runtimeId, options) => {
|
|
59
|
+
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(runtimeId)}/targets`, {
|
|
60
|
+
body: {
|
|
61
|
+
...(options.uri ? { uri: options.uri } : {}),
|
|
62
|
+
...(options.activate ? { activate: true } : {}),
|
|
63
|
+
},
|
|
64
|
+
output: options,
|
|
65
|
+
});
|
|
66
|
+
}));
|
|
67
|
+
command.addCommand(addOutputFlags(new Command('get').description('Get a target').argument('<runtimeId>').argument('<targetId>')).action(async (runtimeId, targetId, options) => {
|
|
68
|
+
await requestAndPrint(factory, 'get', `/runtimes/${encodeURIComponent(runtimeId)}/targets/${encodeURIComponent(targetId)}`, { output: options });
|
|
69
|
+
}));
|
|
70
|
+
command.addCommand(addOutputFlags(new Command('activate')
|
|
71
|
+
.description('Focus a target')
|
|
72
|
+
.argument('<runtimeId>')
|
|
73
|
+
.argument('<targetId>')).action(async (runtimeId, targetId, options) => {
|
|
74
|
+
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(runtimeId)}/targets/${encodeURIComponent(targetId)}/activate`, { output: options });
|
|
75
|
+
}));
|
|
76
|
+
command.addCommand(addOutputFlags(new Command('delete')
|
|
77
|
+
.description('Close a target')
|
|
78
|
+
.argument('<runtimeId>')
|
|
79
|
+
.argument('<targetId>')).action(async (runtimeId, targetId, options) => {
|
|
80
|
+
await requestAndPrint(factory, 'delete', `/runtimes/${encodeURIComponent(runtimeId)}/targets/${encodeURIComponent(targetId)}`, { output: options });
|
|
81
|
+
}));
|
|
82
|
+
return command;
|
|
83
|
+
}
|
|
84
|
+
function createRuntimeCreateCommand(factory) {
|
|
85
|
+
return createJsonBodyCommand(factory, {
|
|
28
86
|
name: 'create',
|
|
29
87
|
description: 'Create a runtime',
|
|
30
88
|
method: 'post',
|
|
31
89
|
path: '/runtimes',
|
|
32
90
|
configure: (cmd) => cmd
|
|
33
|
-
.option('--space <id>', 'Space id')
|
|
91
|
+
.option('--space <id>', 'Space id; omitted uses caller default space')
|
|
34
92
|
.option('--name <name>', 'Runtime name')
|
|
93
|
+
.option('--profile', 'Persist browser state across runtime starts')
|
|
94
|
+
.option('--proxy <id-or-url>', 'Saved proxy id or inline custom proxy URL')
|
|
95
|
+
.addOption(new Option('--device <device>', 'Fingerprint device filter').choices([
|
|
96
|
+
'desktop',
|
|
97
|
+
'mobile',
|
|
98
|
+
]))
|
|
99
|
+
.addOption(new Option('--os <os>', 'Fingerprint OS filter').choices([
|
|
100
|
+
'windows',
|
|
101
|
+
'macos',
|
|
102
|
+
'android',
|
|
103
|
+
'ios',
|
|
104
|
+
]))
|
|
105
|
+
.addOption(new Option('--browser <browser>', 'Fingerprint browser filter').choices([
|
|
106
|
+
'chrome',
|
|
107
|
+
'edge',
|
|
108
|
+
'safari',
|
|
109
|
+
]))
|
|
110
|
+
.option('--browser-version <version>', 'Fingerprint browser version filter')
|
|
111
|
+
.option('--locale <locale>', 'Fingerprint locale/language filter; repeat to set an ordered language stack', collectLocale, [])
|
|
35
112
|
.option('--config-file <path>', 'Runtime config JSON file')
|
|
36
113
|
.option('--metadata-file <path>', 'Runtime metadata JSON file')
|
|
37
114
|
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
38
115
|
body: async (_args, options) => {
|
|
39
116
|
if (typeof options.input === 'string')
|
|
40
117
|
return readJsonFile(options.input);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
118
|
+
const config = typeof options.configFile === 'string'
|
|
119
|
+
? await readJsonFile(options.configFile, '--config-file')
|
|
120
|
+
: undefined;
|
|
44
121
|
return {
|
|
45
122
|
type: 'browser',
|
|
46
123
|
spaceId: options.space,
|
|
47
124
|
name: options.name,
|
|
48
|
-
config:
|
|
125
|
+
config: buildRuntimeCreateConfig(config, options),
|
|
49
126
|
metadata: typeof options.metadataFile === 'string'
|
|
50
127
|
? await readJsonFile(options.metadataFile, '--metadata-file')
|
|
51
128
|
: undefined,
|
|
52
129
|
};
|
|
53
130
|
},
|
|
54
|
-
})
|
|
55
|
-
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function collectLocale(value, previous) {
|
|
134
|
+
return [...previous, value];
|
|
135
|
+
}
|
|
136
|
+
function isRecord(value) {
|
|
137
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
138
|
+
}
|
|
139
|
+
function buildRuntimeCreateConfig(config, options) {
|
|
140
|
+
const fingerprint = {
|
|
141
|
+
...(options.device ? { device: options.device } : {}),
|
|
142
|
+
...(options.os ? { os: options.os } : {}),
|
|
143
|
+
...(options.browser ? { browser: options.browser } : {}),
|
|
144
|
+
...(options.browserVersion ? { browserVersion: options.browserVersion } : {}),
|
|
145
|
+
...(options.locale && options.locale.length === 1 ? { locale: options.locale[0] } : {}),
|
|
146
|
+
...(options.locale && options.locale.length > 1 ? { locale: options.locale } : {}),
|
|
147
|
+
};
|
|
148
|
+
const overrides = {
|
|
149
|
+
...(options.profile === true ? { profile: true } : {}),
|
|
150
|
+
...(options.proxy ? { proxy: options.proxy } : {}),
|
|
151
|
+
...(Object.keys(fingerprint).length > 0 ? { fingerprint } : {}),
|
|
152
|
+
};
|
|
153
|
+
if (Object.keys(overrides).length === 0) {
|
|
154
|
+
return config;
|
|
155
|
+
}
|
|
156
|
+
if (config !== undefined && !isRecord(config)) {
|
|
157
|
+
throw new Error('--config-file must contain a JSON object when combined with runtime flags');
|
|
158
|
+
}
|
|
159
|
+
if (!isRecord(config)) {
|
|
160
|
+
return overrides;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
...config,
|
|
164
|
+
...overrides,
|
|
165
|
+
...(isRecord(config.fingerprint) || 'fingerprint' in overrides
|
|
166
|
+
? {
|
|
167
|
+
fingerprint: {
|
|
168
|
+
...(isRecord(config.fingerprint) ? config.fingerprint : {}),
|
|
169
|
+
...(isRecord(overrides.fingerprint) ? overrides.fingerprint : {}),
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
: {}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function createRuntimePatchCommand(factory) {
|
|
176
|
+
return createJsonBodyCommand(factory, {
|
|
177
|
+
name: 'patch',
|
|
178
|
+
description: 'Update a runtime',
|
|
179
|
+
method: 'patch',
|
|
180
|
+
path: '/runtimes/{runtimeId}',
|
|
181
|
+
argNames: ['runtimeId'],
|
|
182
|
+
configure: (cmd) => cmd
|
|
183
|
+
.option('--name <name>', 'Runtime name')
|
|
184
|
+
.option('--idle-timeout-minutes <minutes>', 'Idle timeout in minutes', parsePositiveInteger)
|
|
185
|
+
.option('--config-file <path>', 'Runtime config JSON file')
|
|
186
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
187
|
+
body: async (_args, options) => {
|
|
188
|
+
if (typeof options.input === 'string')
|
|
189
|
+
return readJsonFile(options.input);
|
|
190
|
+
return {
|
|
191
|
+
name: options.name,
|
|
192
|
+
idleTimeoutMinutes: options.idleTimeoutMinutes,
|
|
193
|
+
config: typeof options.configFile === 'string'
|
|
194
|
+
? await readJsonFile(options.configFile, '--config-file')
|
|
195
|
+
: undefined,
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function createRuntimeStartCommand(factory) {
|
|
201
|
+
return addOutputFlags(new Command('start')
|
|
56
202
|
.description('Start a runtime')
|
|
57
|
-
.argument('<
|
|
58
|
-
.action(async (
|
|
59
|
-
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(
|
|
60
|
-
|
|
61
|
-
});
|
|
62
|
-
}), 'runtime.start'));
|
|
63
|
-
command.addCommand(addOutputFlags(new Command('stop')
|
|
64
|
-
.description('Stop a runtime')
|
|
65
|
-
.argument('<id>'))
|
|
66
|
-
.action(async (id, options) => {
|
|
67
|
-
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(id)}/stop`, {
|
|
68
|
-
output: options,
|
|
69
|
-
});
|
|
70
|
-
}));
|
|
71
|
-
command.addCommand(createRuntimeConnectionCommand(factory));
|
|
72
|
-
command.addCommand(createRuntimeFileCommand(factory));
|
|
73
|
-
command.addCommand(addOutputFlags(new Command('runs')
|
|
74
|
-
.description('List runs for a runtime')
|
|
75
|
-
.argument('<id>')
|
|
76
|
-
.option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
|
|
77
|
-
.option('--cursor <cursor>', 'Pagination cursor'))
|
|
78
|
-
.action(async (id, options) => {
|
|
79
|
-
await requestAndPrint(factory, 'get', `/runtimes/${encodeURIComponent(id)}/runs`, {
|
|
80
|
-
query: { limit: options.limit, cursor: options.cursor },
|
|
203
|
+
.argument('<runtimeId>')
|
|
204
|
+
.option('--idempotency-key <key>', 'Idempotency key for retry-safe start')).action(async (runtimeId, options) => {
|
|
205
|
+
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(runtimeId)}/start`, {
|
|
206
|
+
idempotencyKey: options.idempotencyKey,
|
|
81
207
|
output: options,
|
|
82
208
|
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function createRuntimeInvocationCommand(factory) {
|
|
212
|
+
const command = new Command('invocation').description('Create, wait for, and cancel invocations');
|
|
213
|
+
command.addCommand(createRuntimeInvocationCreateCommand(factory));
|
|
214
|
+
command.addCommand(createRuntimeInvocationWaitCommand(factory));
|
|
215
|
+
command.addCommand(addOutputFlags(new Command('cancel')
|
|
216
|
+
.description('Cancel an invocation')
|
|
217
|
+
.argument('<runtimeId>')
|
|
218
|
+
.argument('<invocationId>')).action(async (runtimeId, invocationId, options) => {
|
|
219
|
+
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(runtimeId)}/invocations/${encodeURIComponent(invocationId)}/cancel`, { output: options });
|
|
83
220
|
}));
|
|
84
221
|
return command;
|
|
85
222
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
.
|
|
91
|
-
.option('--
|
|
92
|
-
.option('
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
223
|
+
function createRuntimeInvocationCreateCommand(factory) {
|
|
224
|
+
return addOutputFlags(new Command('create')
|
|
225
|
+
.description('Create a runtime invocation')
|
|
226
|
+
.argument('<runtimeId>')
|
|
227
|
+
.option('--action <action>', 'Invocation action')
|
|
228
|
+
.option('--instruction <text>', 'Instruction for action/agent invocations')
|
|
229
|
+
.option('--target <target>', "Target to act on: 'active' (default), 'new', or a target id")
|
|
230
|
+
.option('--model <model>', 'Model id')
|
|
231
|
+
.option('--tool <id...>', 'Inline tool id')
|
|
232
|
+
.option('--toolset <id>', 'Toolset id')
|
|
233
|
+
.option('--timeout-ms <ms>', 'Invocation timeout in milliseconds', parsePositiveInteger)
|
|
234
|
+
.option('--idempotency-key <key>', 'Idempotency key for retry-safe create')
|
|
235
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin')).action(async (runtimeId, options) => {
|
|
236
|
+
const body = typeof options.input === 'string'
|
|
237
|
+
? await readJsonFile(options.input)
|
|
238
|
+
: {
|
|
239
|
+
action: options.action,
|
|
240
|
+
instruction: options.instruction,
|
|
241
|
+
target: options.target === undefined
|
|
242
|
+
? undefined
|
|
243
|
+
: options.target === 'active' || options.target === 'new'
|
|
244
|
+
? options.target
|
|
245
|
+
: { id: options.target },
|
|
246
|
+
model: options.model,
|
|
247
|
+
toolIds: options.tool,
|
|
248
|
+
toolsetId: options.toolset,
|
|
249
|
+
timeoutMs: options.timeoutMs,
|
|
250
|
+
};
|
|
251
|
+
await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(runtimeId)}/invocations`, {
|
|
252
|
+
body,
|
|
253
|
+
idempotencyKey: options.idempotencyKey,
|
|
96
254
|
output: options,
|
|
97
255
|
});
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function createRuntimeInvocationWaitCommand(factory) {
|
|
259
|
+
return createJsonBodyCommand(factory, {
|
|
260
|
+
name: 'wait',
|
|
261
|
+
description: 'Wait for an invocation',
|
|
102
262
|
method: 'post',
|
|
103
|
-
path: '/runtimes/{
|
|
104
|
-
argNames: ['
|
|
263
|
+
path: '/runtimes/{runtimeId}/invocations/{invocationId}/wait',
|
|
264
|
+
argNames: ['runtimeId', 'invocationId'],
|
|
105
265
|
configure: (cmd) => cmd
|
|
106
|
-
.option('--
|
|
266
|
+
.option('--timeout-ms <ms>', 'Wait timeout in milliseconds', parsePositiveInteger)
|
|
107
267
|
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
108
268
|
body: async (_args, options) => {
|
|
109
269
|
if (typeof options.input === 'string')
|
|
110
270
|
return readJsonFile(options.input);
|
|
111
|
-
return {
|
|
112
|
-
protocol: options.protocol,
|
|
113
|
-
};
|
|
271
|
+
return { timeoutMs: options.timeoutMs };
|
|
114
272
|
},
|
|
115
|
-
})
|
|
116
|
-
command.addCommand(addOutputFlags(new Command('delete')
|
|
117
|
-
.description('Revoke a runtime connection')
|
|
118
|
-
.argument('<runtime>')
|
|
119
|
-
.argument('<connection>'))
|
|
120
|
-
.action(async (runtime, connection, options) => {
|
|
121
|
-
await requestAndPrint(factory, 'delete', pathTemplate('/runtimes/{runtime}/connections/{connection}', { runtime, connection }), { output: options });
|
|
122
|
-
}));
|
|
123
|
-
return command;
|
|
273
|
+
});
|
|
124
274
|
}
|
|
125
275
|
function createRuntimeFileCommand(factory) {
|
|
126
276
|
const command = new Command('file').description('Move files into and out of runtimes');
|
|
127
|
-
command.addCommand(addOutputFlags(new Command('
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
277
|
+
command.addCommand(addOutputFlags(addPaginationFlags(new Command('list').description('List runtime files').argument('<runtimeId>'))
|
|
278
|
+
.option('--type <type>', 'Filter by file type')).action(async (runtimeId, options) => {
|
|
279
|
+
await requestAndPrint(factory, 'get', `/runtimes/${encodeURIComponent(runtimeId)}/files`, {
|
|
280
|
+
query: { type: options.type, limit: options.limit, cursor: options.cursor },
|
|
281
|
+
output: options,
|
|
282
|
+
});
|
|
283
|
+
}));
|
|
284
|
+
command.addCommand(createRuntimeFileUploadCommand(factory));
|
|
285
|
+
command.addCommand(createRuntimeFileStageCommand(factory));
|
|
286
|
+
command.addCommand(createRuntimeFileCollectCommand(factory));
|
|
287
|
+
return command;
|
|
288
|
+
}
|
|
289
|
+
function createRuntimeFileUploadCommand(factory) {
|
|
290
|
+
return addOutputFlags(new Command('upload')
|
|
291
|
+
.description('Upload a local file into a runtime workspace')
|
|
292
|
+
.argument('<runtimeId>')
|
|
293
|
+
.argument('<localPath>')
|
|
294
|
+
.option('--destination-path <path>', 'Durable BCTRL storage destination')
|
|
295
|
+
.option('--runtime-path <path>', 'Runtime workspace destination')
|
|
132
296
|
.option('--name <name>', 'Display name')
|
|
133
|
-
.option('--metadata-file <path>', 'Metadata JSON file'))
|
|
134
|
-
.action(async (runtime, localPath, options) => {
|
|
297
|
+
.option('--metadata-file <path>', 'Metadata JSON file')).action(async (runtimeId, localPath, options) => {
|
|
135
298
|
const client = await factory.apiClient();
|
|
136
299
|
const file = await readBlob(localPath);
|
|
137
300
|
const metadata = typeof options.metadataFile === 'string'
|
|
138
301
|
? JSON.stringify(await readJsonFile(options.metadataFile, '--metadata-file'))
|
|
139
302
|
: undefined;
|
|
140
|
-
const result = await client.uploadFile(`/runtimes/${encodeURIComponent(
|
|
303
|
+
const result = await client.uploadFile(`/runtimes/${encodeURIComponent(runtimeId)}/files/upload`, {
|
|
141
304
|
file: file.blob,
|
|
142
305
|
fileName: options.name ?? file.fileName,
|
|
143
306
|
fields: {
|
|
144
|
-
...(options.
|
|
307
|
+
...(options.destinationPath ? { destinationPath: options.destinationPath } : {}),
|
|
308
|
+
...(options.runtimePath ? { runtimePath: options.runtimePath } : {}),
|
|
145
309
|
...(options.name ? { name: options.name } : {}),
|
|
146
310
|
...(metadata ? { metadata } : {}),
|
|
147
311
|
},
|
|
148
312
|
});
|
|
149
313
|
await outputData(factory.io, result, options);
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.argument('<runtime-path>')
|
|
155
|
-
.requiredOption('--to <path>', 'Output path, or - for stdout')
|
|
156
|
-
.option('--name <name>', 'Durable file name')
|
|
157
|
-
.option('--storage-path <path>', 'Advanced: durable BCTRL storage path'))
|
|
158
|
-
.action(async (runtime, runtimePath, options) => {
|
|
159
|
-
const client = await factory.apiClient();
|
|
160
|
-
const collected = await client.post(`/runtimes/${encodeURIComponent(runtime)}/files/collect`, {
|
|
161
|
-
body: {
|
|
162
|
-
path: runtimePath,
|
|
163
|
-
name: options.name,
|
|
164
|
-
storagePath: options.storagePath,
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
if (!collected.id) {
|
|
168
|
-
throw new CliError('Expected runtime file collect response to include id');
|
|
169
|
-
}
|
|
170
|
-
const data = await client.download(`/files/${encodeURIComponent(collected.id)}/content`);
|
|
171
|
-
await writeBinary(options.to, data);
|
|
172
|
-
}));
|
|
173
|
-
command.addCommand(createJsonBodyCommand(factory, {
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
function createRuntimeFileStageCommand(factory) {
|
|
317
|
+
return createJsonBodyCommand(factory, {
|
|
174
318
|
name: 'stage',
|
|
175
319
|
description: 'Stage a durable file into a runtime',
|
|
176
320
|
method: 'post',
|
|
177
|
-
path: '/runtimes/{
|
|
178
|
-
argNames: ['
|
|
321
|
+
path: '/runtimes/{runtimeId}/files/stage',
|
|
322
|
+
argNames: ['runtimeId'],
|
|
179
323
|
configure: (cmd) => cmd
|
|
180
324
|
.option('--file <id>', 'Durable file id')
|
|
181
|
-
.option('--
|
|
182
|
-
.option('--name <name>', 'Runtime-local name')
|
|
325
|
+
.option('--runtime-path <path>', 'Runtime workspace destination')
|
|
326
|
+
.option('--name <name>', 'Runtime-local display name')
|
|
183
327
|
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
184
328
|
body: async (_args, options) => {
|
|
185
329
|
if (typeof options.input === 'string')
|
|
186
330
|
return readJsonFile(options.input);
|
|
187
|
-
return { fileId: options.file,
|
|
331
|
+
return { fileId: options.file, runtimePath: options.runtimePath, name: options.name };
|
|
188
332
|
},
|
|
189
|
-
})
|
|
190
|
-
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function createRuntimeFileCollectCommand(factory) {
|
|
336
|
+
return createJsonBodyCommand(factory, {
|
|
191
337
|
name: 'collect',
|
|
192
338
|
description: 'Collect a runtime-local file into durable storage',
|
|
193
339
|
method: 'post',
|
|
194
|
-
path: '/runtimes/{
|
|
195
|
-
argNames: ['
|
|
340
|
+
path: '/runtimes/{runtimeId}/files/collect',
|
|
341
|
+
argNames: ['runtimeId'],
|
|
196
342
|
configure: (cmd) => cmd
|
|
197
|
-
.requiredOption('--path <path>', 'Runtime
|
|
343
|
+
.requiredOption('--runtime-path <path>', 'Runtime workspace source')
|
|
198
344
|
.option('--name <name>', 'Durable file name')
|
|
199
|
-
.option('--
|
|
345
|
+
.option('--destination-path <path>', 'Durable BCTRL storage destination')
|
|
200
346
|
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
201
347
|
body: async (_args, options) => {
|
|
202
348
|
if (typeof options.input === 'string')
|
|
203
349
|
return readJsonFile(options.input);
|
|
204
|
-
return {
|
|
350
|
+
return {
|
|
351
|
+
runtimePath: options.runtimePath,
|
|
352
|
+
name: options.name,
|
|
353
|
+
destinationPath: options.destinationPath,
|
|
354
|
+
};
|
|
205
355
|
},
|
|
206
|
-
})
|
|
207
|
-
return command;
|
|
356
|
+
});
|
|
208
357
|
}
|
|
@@ -10,7 +10,7 @@ function toCommandHelpSpec(help) {
|
|
|
10
10
|
const raw = help;
|
|
11
11
|
return {
|
|
12
12
|
purpose: raw.summary,
|
|
13
|
-
flags: raw.flags?.map((flag) => ({ ...flag })),
|
|
13
|
+
flags: raw.cli?.flags?.map((flag) => ({ ...flag })),
|
|
14
14
|
input: raw.input?.fields.map((field) => ({ ...field })),
|
|
15
15
|
output: raw.output?.fields.map((field) => ({ ...field })),
|
|
16
16
|
examples: raw.examples?.map(formatHelpItem).filter((value) => Boolean(value)),
|
|
@@ -14,6 +14,7 @@ export declare function addJsonBodyFlag(command: Command): Command;
|
|
|
14
14
|
export declare function requestAndPrint(deps: ApiDeps, method: ClientMethod, path: string, options?: {
|
|
15
15
|
query?: Record<string, string | number | boolean | undefined>;
|
|
16
16
|
body?: unknown;
|
|
17
|
+
idempotencyKey?: string;
|
|
17
18
|
output?: OutputFlags;
|
|
18
19
|
}): Promise<void>;
|
|
19
20
|
export declare function bodyFromInputOption(path: string | undefined): Promise<unknown>;
|
|
@@ -16,16 +16,24 @@ export function addJsonBodyFlag(command) {
|
|
|
16
16
|
}
|
|
17
17
|
export async function requestAndPrint(deps, method, path, options) {
|
|
18
18
|
const client = await deps.apiClient();
|
|
19
|
+
const requestOptions = options
|
|
20
|
+
? {
|
|
21
|
+
...(options.query !== undefined ? { query: options.query } : {}),
|
|
22
|
+
...(options.body !== undefined ? { body: options.body } : {}),
|
|
23
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
24
|
+
}
|
|
25
|
+
: undefined;
|
|
26
|
+
const clientOptions = requestOptions && Object.keys(requestOptions).length > 0 ? requestOptions : undefined;
|
|
19
27
|
const result = method === 'get'
|
|
20
|
-
? await client.get(path,
|
|
28
|
+
? await client.get(path, clientOptions)
|
|
21
29
|
: method === 'post'
|
|
22
|
-
? await client.post(path,
|
|
30
|
+
? await client.post(path, clientOptions)
|
|
23
31
|
: method === 'patch'
|
|
24
|
-
? await client.patch(path,
|
|
32
|
+
? await client.patch(path, clientOptions)
|
|
25
33
|
: method === 'put'
|
|
26
|
-
? await client.put(path,
|
|
27
|
-
: await client.delete(path,
|
|
28
|
-
await outputData(deps.io, result, options?.output);
|
|
34
|
+
? await client.put(path, clientOptions)
|
|
35
|
+
: await client.delete(path, clientOptions);
|
|
36
|
+
await outputData(deps.io, result, options?.output ? outputFlags(options.output) : undefined);
|
|
29
37
|
}
|
|
30
38
|
export async function bodyFromInputOption(path) {
|
|
31
39
|
return path ? readJsonFile(path) : {};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { createJsonBodyCommand, createViewCommand } from '../shared/rest.js';
|
|
2
|
+
import { createDeleteCommand, createJsonBodyCommand, createViewCommand } from '../shared/rest.js';
|
|
3
3
|
import { readJsonFile } from '../shared/io.js';
|
|
4
|
-
import { addOutputFlags, outputData } from '../shared/output.js';
|
|
5
4
|
import { createSpaceListCommand } from './list.js';
|
|
6
5
|
export function createSpaceCommand(factory) {
|
|
7
6
|
const command = new Command('space').description('Manage BCTRL spaces');
|
|
8
7
|
command.addCommand(createSpaceListCommand(factory));
|
|
9
8
|
command.addCommand(createViewCommand(factory, {
|
|
9
|
+
name: 'get',
|
|
10
10
|
description: 'View a space',
|
|
11
|
-
path: '/spaces/{
|
|
11
|
+
path: '/spaces/{spaceId}',
|
|
12
|
+
argName: 'spaceId',
|
|
12
13
|
}));
|
|
13
14
|
command.addCommand(createJsonBodyCommand(factory, {
|
|
14
15
|
name: 'create',
|
|
@@ -17,7 +18,6 @@ export function createSpaceCommand(factory) {
|
|
|
17
18
|
path: '/spaces',
|
|
18
19
|
configure: (cmd) => cmd
|
|
19
20
|
.option('--name <name>', 'Space name')
|
|
20
|
-
.option('--region <region>', 'Space region')
|
|
21
21
|
.option('--subaccount-id <id>', 'Subaccount id')
|
|
22
22
|
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
23
23
|
body: async (_args, options) => {
|
|
@@ -25,17 +25,16 @@ export function createSpaceCommand(factory) {
|
|
|
25
25
|
return readJsonFile(options.input);
|
|
26
26
|
return {
|
|
27
27
|
name: options.name,
|
|
28
|
-
region: options.region,
|
|
29
28
|
subaccountId: options.subaccountId,
|
|
30
29
|
};
|
|
31
30
|
},
|
|
32
31
|
}));
|
|
33
32
|
command.addCommand(createJsonBodyCommand(factory, {
|
|
34
|
-
name: '
|
|
33
|
+
name: 'patch',
|
|
35
34
|
description: 'Edit a space',
|
|
36
35
|
method: 'patch',
|
|
37
|
-
path: '/spaces/{
|
|
38
|
-
argNames: ['
|
|
36
|
+
path: '/spaces/{spaceId}',
|
|
37
|
+
argNames: ['spaceId'],
|
|
39
38
|
configure: (cmd) => cmd
|
|
40
39
|
.option('--name <name>', 'Space name')
|
|
41
40
|
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
@@ -45,8 +44,12 @@ export function createSpaceCommand(factory) {
|
|
|
45
44
|
return { name: options.name };
|
|
46
45
|
},
|
|
47
46
|
}));
|
|
47
|
+
command.addCommand(createDeleteCommand(factory, {
|
|
48
|
+
description: 'Delete a space',
|
|
49
|
+
path: '/spaces/{spaceId}',
|
|
50
|
+
argNames: ['spaceId'],
|
|
51
|
+
}));
|
|
48
52
|
command.addCommand(createSpaceEnvironmentCommand(factory));
|
|
49
|
-
command.addCommand(createSpaceAgentContextCommand(factory));
|
|
50
53
|
return command;
|
|
51
54
|
}
|
|
52
55
|
function createSpaceEnvironmentCommand(factory) {
|
|
@@ -54,33 +57,15 @@ function createSpaceEnvironmentCommand(factory) {
|
|
|
54
57
|
command.addCommand(createViewCommand(factory, {
|
|
55
58
|
name: 'get',
|
|
56
59
|
description: 'Get a space environment',
|
|
57
|
-
path: '/spaces/{
|
|
60
|
+
path: '/spaces/{spaceId}/environment',
|
|
61
|
+
argName: 'spaceId',
|
|
58
62
|
}));
|
|
59
63
|
command.addCommand(createJsonBodyCommand(factory, {
|
|
60
|
-
name: '
|
|
64
|
+
name: 'patch',
|
|
61
65
|
description: 'Update a space environment',
|
|
62
66
|
method: 'patch',
|
|
63
|
-
path: '/spaces/{
|
|
64
|
-
argNames: ['
|
|
67
|
+
path: '/spaces/{spaceId}/environment',
|
|
68
|
+
argNames: ['spaceId'],
|
|
65
69
|
}));
|
|
66
70
|
return command;
|
|
67
71
|
}
|
|
68
|
-
function createSpaceAgentContextCommand(factory) {
|
|
69
|
-
return addOutputFlags(new Command('agent-context')
|
|
70
|
-
.description('Get machine-readable context for coding agents')
|
|
71
|
-
.argument('<id>')
|
|
72
|
-
.option('--runtime <id>', 'Runtime id')
|
|
73
|
-
.option('--topic <topic>', 'Context topic')
|
|
74
|
-
.option('--detail <level>', 'Detail level'))
|
|
75
|
-
.action(async (id, options) => {
|
|
76
|
-
const client = await factory.apiClient();
|
|
77
|
-
const result = await client.get(`/spaces/${encodeURIComponent(id)}/agent-context`, {
|
|
78
|
-
query: {
|
|
79
|
-
runtimeId: options.runtime,
|
|
80
|
-
topic: options.topic,
|
|
81
|
-
detail: options.detail,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
await outputData(factory.io, result, options);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
@@ -1,13 +1,3 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import type { BctrlApiClient } from '../../api/client.js';
|
|
3
2
|
import type { Factory } from '../../factory.js';
|
|
4
|
-
|
|
5
|
-
import { type OutputFlags } from '../shared/output.js';
|
|
6
|
-
export type SpaceListOptions = {
|
|
7
|
-
io: IOStreams;
|
|
8
|
-
apiClient: () => Promise<BctrlApiClient>;
|
|
9
|
-
limit?: number;
|
|
10
|
-
output?: OutputFlags;
|
|
11
|
-
};
|
|
12
|
-
export declare function createSpaceListCommand(factory: Factory, run?: (options: SpaceListOptions) => Promise<void>): Command;
|
|
13
|
-
export declare function spaceListRun(options: SpaceListOptions): Promise<void>;
|
|
3
|
+
export declare function createSpaceListCommand(factory: Factory): Command;
|