@bctrl/cli 0.1.3 → 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 -95
- 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/bin/bctrl.js +0 -0
- 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 +274 -78
- 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/generated/help.d.ts +132 -46
- package/dist/generated/help.js +192 -54
- package/dist/root.js +10 -4
- package/package.json +47 -45
- 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, requestAndPrint, } from '../shared/rest.js';
|
|
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,141 +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:
|
|
49
|
-
? await readJsonFile(options.configFile, '--config-file')
|
|
50
|
-
: undefined,
|
|
125
|
+
config: buildRuntimeCreateConfig(config, options),
|
|
51
126
|
metadata: typeof options.metadataFile === 'string'
|
|
52
127
|
? await readJsonFile(options.metadataFile, '--metadata-file')
|
|
53
128
|
: undefined,
|
|
54
129
|
};
|
|
55
130
|
},
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
|
|
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')
|
|
202
|
+
.description('Start a runtime')
|
|
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,
|
|
59
207
|
output: options,
|
|
60
208
|
});
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
|
|
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 });
|
|
220
|
+
}));
|
|
221
|
+
return command;
|
|
222
|
+
}
|
|
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,
|
|
64
254
|
output: options,
|
|
65
255
|
});
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function createRuntimeInvocationWaitCommand(factory) {
|
|
259
|
+
return createJsonBodyCommand(factory, {
|
|
260
|
+
name: 'wait',
|
|
261
|
+
description: 'Wait for an invocation',
|
|
262
|
+
method: 'post',
|
|
263
|
+
path: '/runtimes/{runtimeId}/invocations/{invocationId}/wait',
|
|
264
|
+
argNames: ['runtimeId', 'invocationId'],
|
|
265
|
+
configure: (cmd) => cmd
|
|
266
|
+
.option('--timeout-ms <ms>', 'Wait timeout in milliseconds', parsePositiveInteger)
|
|
267
|
+
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
268
|
+
body: async (_args, options) => {
|
|
269
|
+
if (typeof options.input === 'string')
|
|
270
|
+
return readJsonFile(options.input);
|
|
271
|
+
return { timeoutMs: options.timeoutMs };
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function createRuntimeFileCommand(factory) {
|
|
276
|
+
const command = new Command('file').description('Move files into and out of runtimes');
|
|
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 },
|
|
75
281
|
output: options,
|
|
76
282
|
});
|
|
77
283
|
}));
|
|
284
|
+
command.addCommand(createRuntimeFileUploadCommand(factory));
|
|
285
|
+
command.addCommand(createRuntimeFileStageCommand(factory));
|
|
286
|
+
command.addCommand(createRuntimeFileCollectCommand(factory));
|
|
78
287
|
return command;
|
|
79
288
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.
|
|
84
|
-
.argument('<
|
|
85
|
-
.
|
|
86
|
-
.option('--
|
|
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')
|
|
87
296
|
.option('--name <name>', 'Display name')
|
|
88
|
-
.option('--metadata-file <path>', 'Metadata JSON file')).action(async (
|
|
297
|
+
.option('--metadata-file <path>', 'Metadata JSON file')).action(async (runtimeId, localPath, options) => {
|
|
89
298
|
const client = await factory.apiClient();
|
|
90
299
|
const file = await readBlob(localPath);
|
|
91
300
|
const metadata = typeof options.metadataFile === 'string'
|
|
92
301
|
? JSON.stringify(await readJsonFile(options.metadataFile, '--metadata-file'))
|
|
93
302
|
: undefined;
|
|
94
|
-
const result = await client.uploadFile(`/runtimes/${encodeURIComponent(
|
|
303
|
+
const result = await client.uploadFile(`/runtimes/${encodeURIComponent(runtimeId)}/files/upload`, {
|
|
95
304
|
file: file.blob,
|
|
96
305
|
fileName: options.name ?? file.fileName,
|
|
97
306
|
fields: {
|
|
98
|
-
...(options.
|
|
307
|
+
...(options.destinationPath ? { destinationPath: options.destinationPath } : {}),
|
|
308
|
+
...(options.runtimePath ? { runtimePath: options.runtimePath } : {}),
|
|
99
309
|
...(options.name ? { name: options.name } : {}),
|
|
100
310
|
...(metadata ? { metadata } : {}),
|
|
101
311
|
},
|
|
102
312
|
});
|
|
103
313
|
await outputData(factory.io, result, options);
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
.argument('<runtime-path>')
|
|
109
|
-
.requiredOption('--to <path>', 'Output path, or - for stdout')
|
|
110
|
-
.option('--name <name>', 'Durable file name')
|
|
111
|
-
.option('--storage-path <path>', 'Advanced: durable BCTRL storage path')).action(async (runtime, runtimePath, options) => {
|
|
112
|
-
const client = await factory.apiClient();
|
|
113
|
-
const collected = await client.post(`/runtimes/${encodeURIComponent(runtime)}/files/collect`, {
|
|
114
|
-
body: {
|
|
115
|
-
path: runtimePath,
|
|
116
|
-
name: options.name,
|
|
117
|
-
storagePath: options.storagePath,
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
if (!collected.id) {
|
|
121
|
-
throw new CliError('Expected runtime file collect response to include id');
|
|
122
|
-
}
|
|
123
|
-
const data = await client.download(`/files/${encodeURIComponent(collected.id)}/content`);
|
|
124
|
-
await writeBinary(options.to, data);
|
|
125
|
-
}));
|
|
126
|
-
command.addCommand(createJsonBodyCommand(factory, {
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
function createRuntimeFileStageCommand(factory) {
|
|
317
|
+
return createJsonBodyCommand(factory, {
|
|
127
318
|
name: 'stage',
|
|
128
319
|
description: 'Stage a durable file into a runtime',
|
|
129
320
|
method: 'post',
|
|
130
|
-
path: '/runtimes/{
|
|
131
|
-
argNames: ['
|
|
321
|
+
path: '/runtimes/{runtimeId}/files/stage',
|
|
322
|
+
argNames: ['runtimeId'],
|
|
132
323
|
configure: (cmd) => cmd
|
|
133
324
|
.option('--file <id>', 'Durable file id')
|
|
134
|
-
.option('--
|
|
135
|
-
.option('--name <name>', 'Runtime-local name')
|
|
325
|
+
.option('--runtime-path <path>', 'Runtime workspace destination')
|
|
326
|
+
.option('--name <name>', 'Runtime-local display name')
|
|
136
327
|
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
137
328
|
body: async (_args, options) => {
|
|
138
329
|
if (typeof options.input === 'string')
|
|
139
330
|
return readJsonFile(options.input);
|
|
140
|
-
return { fileId: options.file,
|
|
331
|
+
return { fileId: options.file, runtimePath: options.runtimePath, name: options.name };
|
|
141
332
|
},
|
|
142
|
-
})
|
|
143
|
-
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function createRuntimeFileCollectCommand(factory) {
|
|
336
|
+
return createJsonBodyCommand(factory, {
|
|
144
337
|
name: 'collect',
|
|
145
338
|
description: 'Collect a runtime-local file into durable storage',
|
|
146
339
|
method: 'post',
|
|
147
|
-
path: '/runtimes/{
|
|
148
|
-
argNames: ['
|
|
340
|
+
path: '/runtimes/{runtimeId}/files/collect',
|
|
341
|
+
argNames: ['runtimeId'],
|
|
149
342
|
configure: (cmd) => cmd
|
|
150
|
-
.requiredOption('--path <path>', 'Runtime
|
|
343
|
+
.requiredOption('--runtime-path <path>', 'Runtime workspace source')
|
|
151
344
|
.option('--name <name>', 'Durable file name')
|
|
152
|
-
.option('--
|
|
345
|
+
.option('--destination-path <path>', 'Durable BCTRL storage destination')
|
|
153
346
|
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
154
347
|
body: async (_args, options) => {
|
|
155
348
|
if (typeof options.input === 'string')
|
|
156
349
|
return readJsonFile(options.input);
|
|
157
|
-
return {
|
|
350
|
+
return {
|
|
351
|
+
runtimePath: options.runtimePath,
|
|
352
|
+
name: options.name,
|
|
353
|
+
destinationPath: options.destinationPath,
|
|
354
|
+
};
|
|
158
355
|
},
|
|
159
|
-
})
|
|
160
|
-
return command;
|
|
356
|
+
});
|
|
161
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;
|
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import { addPaginationFlags, createListCommand } from '../shared/rest.js';
|
|
2
|
+
export function createSpaceListCommand(factory) {
|
|
3
|
+
return createListCommand(factory, {
|
|
4
|
+
description: 'List spaces',
|
|
5
|
+
path: '/spaces',
|
|
6
|
+
configure: (cmd) => addPaginationFlags(cmd)
|
|
7
|
+
.option('--subaccount <id>', 'Filter by subaccount id')
|
|
8
|
+
.option('--query <text>', 'Search by id or name'),
|
|
9
|
+
query: (options) => ({
|
|
10
|
+
subaccountId: typeof options.subaccount === 'string' ? options.subaccount : undefined,
|
|
11
|
+
query: typeof options.query === 'string' ? options.query : undefined,
|
|
12
|
+
limit: typeof options.limit === 'number' ? options.limit : undefined,
|
|
13
|
+
cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
|
|
14
|
+
}),
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
|
-
export async function spaceListRun(options) {
|
|
17
|
-
const client = await options.apiClient();
|
|
18
|
-
const result = await client.get('/spaces', {
|
|
19
|
-
query: {
|
|
20
|
-
limit: options.limit,
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
await outputData(options.io, result, options.output);
|
|
24
|
-
}
|
|
25
|
-
import { parsePositiveInteger } from '../shared/options.js';
|