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