@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.
Files changed (42) hide show
  1. package/README.md +95 -95
  2. package/dist/api/auth.d.ts +5 -6
  3. package/dist/api/auth.js +4 -5
  4. package/dist/api/client.d.ts +1 -0
  5. package/dist/api/client.js +3 -0
  6. package/dist/api/errors.js +2 -2
  7. package/dist/bin/bctrl.js +0 -0
  8. package/dist/commands/ai/index.js +59 -102
  9. package/dist/commands/{browser → api-key}/index.d.ts +1 -1
  10. package/dist/commands/api-key/index.js +47 -0
  11. package/dist/commands/auth/login.js +2 -1
  12. package/dist/commands/auth/status.js +5 -0
  13. package/dist/commands/browser-extension/index.d.ts +3 -0
  14. package/dist/commands/browser-extension/index.js +86 -0
  15. package/dist/commands/file/index.js +9 -3
  16. package/dist/commands/{invocation → help}/index.d.ts +1 -1
  17. package/dist/commands/help/index.js +17 -0
  18. package/dist/commands/proxy/index.d.ts +3 -0
  19. package/dist/commands/proxy/index.js +66 -0
  20. package/dist/commands/run/index.js +205 -155
  21. package/dist/commands/runtime/index.js +274 -78
  22. package/dist/commands/shared/help.js +1 -1
  23. package/dist/commands/shared/rest.d.ts +1 -0
  24. package/dist/commands/shared/rest.js +14 -6
  25. package/dist/commands/space/index.js +17 -32
  26. package/dist/commands/space/list.d.ts +1 -11
  27. package/dist/commands/space/list.js +14 -23
  28. package/dist/commands/subaccount/index.js +29 -80
  29. package/dist/commands/tool/index.js +15 -7
  30. package/dist/commands/tool-call/index.js +6 -1
  31. package/dist/commands/toolset/index.js +6 -3
  32. package/dist/commands/usage/index.d.ts +3 -0
  33. package/dist/commands/usage/index.js +13 -0
  34. package/dist/commands/vault/index.js +104 -25
  35. package/dist/config/auth-store.d.ts +10 -12
  36. package/dist/config/auth-store.js +4 -5
  37. package/dist/generated/help.d.ts +132 -46
  38. package/dist/generated/help.js +192 -54
  39. package/dist/root.js +10 -4
  40. package/package.json +47 -45
  41. package/dist/commands/browser/index.js +0 -53
  42. package/dist/commands/invocation/index.js +0 -36
@@ -12,6 +12,8 @@ export function createFileCommand(factory) {
12
12
  .option('--space <id>', 'Filter by space id')
13
13
  .option('--source <source>', 'Filter by file source')
14
14
  .option('--prefix <path>', 'Filter by path prefix')
15
+ .option('--folders', 'Directory view: direct files plus subfolder rollups')
16
+ .option('--created-after <iso>', 'Only files created after this timestamp')
15
17
  .option('--query <text>', 'Search query')
16
18
  .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
17
19
  .option('--cursor <cursor>', 'Pagination cursor'),
@@ -19,12 +21,16 @@ export function createFileCommand(factory) {
19
21
  spaceId: typeof options.space === 'string' ? options.space : undefined,
20
22
  source: typeof options.source === 'string' ? options.source : undefined,
21
23
  prefix: typeof options.prefix === 'string' ? options.prefix : undefined,
22
- query: typeof options.query === 'string' ? options.query : undefined,
24
+ include: options.folders === true ? 'folders' : undefined,
25
+ createdAfter: typeof options.createdAfter === 'string' ? options.createdAfter : undefined,
26
+ // The public query param is `q` (was sent as `query`, which the API ignores).
27
+ q: typeof options.query === 'string' ? options.query : undefined,
23
28
  limit: typeof options.limit === 'number' ? options.limit : undefined,
24
29
  cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
25
30
  }),
26
31
  }));
27
32
  command.addCommand(createViewCommand(factory, {
33
+ name: 'get',
28
34
  description: 'View file metadata',
29
35
  path: '/files/{fileId}',
30
36
  argName: 'fileId',
@@ -32,7 +38,7 @@ export function createFileCommand(factory) {
32
38
  command.addCommand(addOutputFlags(new Command('upload')
33
39
  .description('Upload a file')
34
40
  .argument('<path>')
35
- .requiredOption('--space <id>', 'Space id')
41
+ .option('--space <id>', 'Space id; omitted uses caller default space')
36
42
  .option('--path <storagePath>', 'Storage path')
37
43
  .option('--name <name>', 'Display name'))
38
44
  .action(async (path, options) => {
@@ -59,7 +65,7 @@ export function createFileCommand(factory) {
59
65
  await writeBinary(options.to, data);
60
66
  }));
61
67
  command.addCommand(createJsonBodyCommand(factory, {
62
- name: 'edit',
68
+ name: 'patch',
63
69
  description: 'Edit file metadata',
64
70
  method: 'patch',
65
71
  path: '/files/{fileId}',
@@ -1,3 +1,3 @@
1
1
  import { Command } from 'commander';
2
2
  import type { Factory } from '../../factory.js';
3
- export declare function createInvocationCommand(factory: Factory): Command;
3
+ export declare function createHelpCommand(factory: Factory): Command;
@@ -0,0 +1,17 @@
1
+ import { Command } from 'commander';
2
+ import { addOutputFlags } from '../shared/output.js';
3
+ import { requestAndPrint } from '../shared/rest.js';
4
+ export function createHelpCommand(factory) {
5
+ return addOutputFlags(new Command('help')
6
+ .description('Get BCTRL API help')
7
+ .option('--topic <topic>', 'Help topic')
8
+ .option('--audience <audience>', 'Help audience')).action(async (options) => {
9
+ await requestAndPrint(factory, 'get', '/help', {
10
+ query: {
11
+ topic: options.topic,
12
+ audience: options.audience,
13
+ },
14
+ output: options,
15
+ });
16
+ });
17
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import type { Factory } from '../../factory.js';
3
+ export declare function createProxyCommand(factory: Factory): Command;
@@ -0,0 +1,66 @@
1
+ import { Command } from 'commander';
2
+ import { addOutputFlags } from '../shared/output.js';
3
+ import { addPaginationFlags, createDeleteCommand, createJsonBodyCommand, createListCommand, createViewCommand, requestAndPrint, } from '../shared/rest.js';
4
+ export function createProxyCommand(factory) {
5
+ const command = new Command('proxy').description('Manage proxies');
6
+ command.addCommand(createListCommand(factory, {
7
+ description: 'List proxies',
8
+ path: '/proxies',
9
+ }));
10
+ command.addCommand(createViewCommand(factory, {
11
+ name: 'get',
12
+ description: 'Get a proxy',
13
+ path: '/proxies/{proxyId}',
14
+ argName: 'proxyId',
15
+ }));
16
+ command.addCommand(createJsonBodyCommand(factory, {
17
+ name: 'create',
18
+ description: 'Create a proxy',
19
+ method: 'post',
20
+ path: '/proxies',
21
+ }));
22
+ command.addCommand(createJsonBodyCommand(factory, {
23
+ name: 'patch',
24
+ description: 'Update a proxy',
25
+ method: 'patch',
26
+ path: '/proxies/{proxyId}',
27
+ argNames: ['proxyId'],
28
+ }));
29
+ command.addCommand(addOutputFlags(new Command('test').description('Test a proxy').argument('<proxyId>')).action(async (proxyId, options) => {
30
+ await requestAndPrint(factory, 'post', `/proxies/${encodeURIComponent(proxyId)}/test`, {
31
+ output: options,
32
+ });
33
+ }));
34
+ command.addCommand(createDeleteCommand(factory, {
35
+ description: 'Delete a proxy',
36
+ path: '/proxies/{proxyId}',
37
+ argNames: ['proxyId'],
38
+ }));
39
+ command.addCommand(createProxyPoolCommand(factory));
40
+ return command;
41
+ }
42
+ function createProxyPoolCommand(factory) {
43
+ const command = new Command('pool').description('Inspect managed proxy pools');
44
+ command.addCommand(createListCommand(factory, {
45
+ description: 'List proxy pools',
46
+ path: '/proxies/pools',
47
+ configure: (cmd) => addPaginationFlags(cmd)
48
+ .option('--country <country>', 'Filter by ISO country code')
49
+ .option('--category <category>', 'Filter by category')
50
+ .option('--available <boolean>', 'Filter by availability'),
51
+ query: (options) => ({
52
+ country: typeof options.country === 'string' ? options.country : undefined,
53
+ category: typeof options.category === 'string' ? options.category : undefined,
54
+ available: typeof options.available === 'string' ? options.available : undefined,
55
+ limit: typeof options.limit === 'number' ? options.limit : undefined,
56
+ cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
57
+ }),
58
+ }));
59
+ command.addCommand(createViewCommand(factory, {
60
+ name: 'get',
61
+ description: 'Get a proxy pool',
62
+ path: '/proxies/pools/{poolId}',
63
+ argName: 'poolId',
64
+ }));
65
+ return command;
66
+ }
@@ -5,56 +5,224 @@ import { parsePositiveInteger } from '../shared/options.js';
5
5
  import { addOutputFlags } from '../shared/output.js';
6
6
  import { addPaginationFlags, createJsonBodyCommand, createListCommand, createViewCommand, requestAndPrint, } from '../shared/rest.js';
7
7
  export function createRunCommand(factory) {
8
- const command = new Command('run').description('Inspect and stream BCTRL run history');
8
+ const command = new Command('run').description('Inspect and stream runs');
9
9
  command.addCommand(createListCommand(factory, {
10
10
  description: 'List runs',
11
11
  path: '/runs',
12
12
  configure: (cmd) => addPaginationFlags(cmd)
13
13
  .option('--space <id>', 'Filter by space id')
14
14
  .option('--runtime <id>', 'Filter by runtime id')
15
- .option('--status <status>', 'Filter by run status'),
15
+ .option('--status <status>', 'Filter by run status')
16
+ .option('--subaccount <id>', 'Filter by subaccount id'),
16
17
  query: (options) => ({
17
18
  spaceId: typeof options.space === 'string' ? options.space : undefined,
18
19
  runtimeId: typeof options.runtime === 'string' ? options.runtime : undefined,
19
20
  status: typeof options.status === 'string' ? options.status : undefined,
21
+ subaccountId: typeof options.subaccount === 'string' ? options.subaccount : undefined,
20
22
  limit: typeof options.limit === 'number' ? options.limit : undefined,
21
23
  cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
22
24
  }),
23
25
  }));
24
26
  command.addCommand(createViewCommand(factory, {
25
- description: 'View a run',
26
- path: '/runs/{id}',
27
+ name: 'get',
28
+ description: 'Get a run',
29
+ path: '/runs/{runId}',
30
+ argName: 'runId',
27
31
  }));
32
+ command.addCommand(createRunActivityCommand(factory));
28
33
  command.addCommand(createRunEventsCommand(factory));
29
- command.addCommand(createRunCommandsCommand(factory));
30
34
  command.addCommand(createRunFilesCommand(factory));
31
- command.addCommand(createRunExportCommand(factory));
32
- command.addCommand(createRunWatchCommand(factory));
33
- command.addCommand(createRunWaitCommand(factory));
34
- command.addCommand(createRunViewerCommand(factory, 'live', 'Mint a live viewer URL'));
35
- command.addCommand(createRunViewerCommand(factory, 'recording', 'Mint a recording viewer URL'));
35
+ command.addCommand(createRunInvocationsCommand(factory));
36
+ command.addCommand(createRunInvocationCommand(factory));
37
+ command.addCommand(createRunViewerCommand(factory, 'live', 'Create a live viewer URL'));
38
+ command.addCommand(createRunViewerCommand(factory, 'recording', 'Create a recording viewer URL'));
36
39
  return command;
37
40
  }
38
- function createRunWatchCommand(factory) {
39
- return new Command('watch')
40
- .description('Stream run events')
41
- .argument('<id>')
42
- .option('--type <type...>', 'Filter by event type')
43
- .option('--category <category...>', 'Filter by event category')
44
- .option('--connection <id>', 'Filter by connection id')
45
- .option('-L, --limit <number>', 'Maximum number of events per poll', parsePositiveInteger)
46
- .option('--cursor <cursor>', 'Start cursor')
47
- .action(async (id, options) => {
41
+ function createRunActivityCommand(factory) {
42
+ const command = new Command('activity').description('Inspect run activities');
43
+ command.addCommand(createRunActivityListCommand(factory));
44
+ command.addCommand(createSseCommand(factory, {
45
+ name: 'stream',
46
+ description: 'Stream run activities',
47
+ pathSuffix: 'activity/stream',
48
+ configure: (cmd) => addPaginationFlags(cmd)
49
+ .option('--view <view>', 'Activity view')
50
+ .option('--type <type>', 'Filter by activity type')
51
+ .option('--category <category>', 'Filter by activity category')
52
+ .option('--severity <severity>', 'Filter by severity')
53
+ .option('--invocation <id>', 'Filter by invocation id')
54
+ .option('--file <id>', 'Filter by file id'),
55
+ query: (options) => ({
56
+ view: typeof options.view === 'string' ? options.view : undefined,
57
+ type: typeof options.type === 'string' ? options.type : undefined,
58
+ category: typeof options.category === 'string' ? options.category : undefined,
59
+ severity: typeof options.severity === 'string' ? options.severity : undefined,
60
+ invocationId: typeof options.invocation === 'string' ? options.invocation : undefined,
61
+ fileId: typeof options.file === 'string' ? options.file : undefined,
62
+ limit: typeof options.limit === 'number' ? options.limit : undefined,
63
+ cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
64
+ }),
65
+ }));
66
+ return command;
67
+ }
68
+ function createRunActivityListCommand(factory) {
69
+ return addOutputFlags(addPaginationFlags(new Command('list').description('List run activities').argument('<runId>'))
70
+ .option('--view <view>', 'Activity view')
71
+ .option('--type <type>', 'Filter by activity type')
72
+ .option('--category <category>', 'Filter by activity category')
73
+ .option('--severity <severity>', 'Filter by severity')
74
+ .option('--invocation <id>', 'Filter by invocation id')
75
+ .option('--file <id>', 'Filter by file id')).action(async (runId, options) => {
76
+ await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(runId)}/activity`, {
77
+ query: {
78
+ view: options.view,
79
+ type: options.type,
80
+ category: options.category,
81
+ severity: options.severity,
82
+ invocationId: options.invocation,
83
+ fileId: options.file,
84
+ limit: options.limit,
85
+ cursor: options.cursor,
86
+ },
87
+ output: options,
88
+ });
89
+ });
90
+ }
91
+ function createRunEventsCommand(factory) {
92
+ const command = new Command('events').description('Inspect run events');
93
+ command.addCommand(createRunEventsListCommand(factory));
94
+ command.addCommand(createSseCommand(factory, {
95
+ name: 'stream',
96
+ description: 'Stream run events',
97
+ pathSuffix: 'events/stream',
98
+ configure: (cmd) => addPaginationFlags(cmd)
99
+ .option('--type <type>', 'Filter by event type')
100
+ .option('--status <status>', 'Filter by event status')
101
+ .option('--page-id <id>', 'Filter by page id')
102
+ .option('--context-id <id>', 'Filter by browser context id'),
103
+ query: (options) => ({
104
+ type: typeof options.type === 'string' ? options.type : undefined,
105
+ status: typeof options.status === 'string' ? options.status : undefined,
106
+ pageId: typeof options.pageId === 'string' ? options.pageId : undefined,
107
+ contextId: typeof options.contextId === 'string' ? options.contextId : undefined,
108
+ limit: typeof options.limit === 'number' ? options.limit : undefined,
109
+ cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
110
+ }),
111
+ }));
112
+ return command;
113
+ }
114
+ function createRunEventsListCommand(factory) {
115
+ return addOutputFlags(addPaginationFlags(new Command('list').description('List run events').argument('<runId>'))
116
+ .option('--type <type>', 'Filter by event type')
117
+ .option('--status <status>', 'Filter by event status')
118
+ .option('--page-id <id>', 'Filter by page id')
119
+ .option('--context-id <id>', 'Filter by browser context id')).action(async (runId, options) => {
120
+ await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(runId)}/events`, {
121
+ query: {
122
+ type: options.type,
123
+ status: options.status,
124
+ pageId: options.pageId,
125
+ contextId: options.contextId,
126
+ limit: options.limit,
127
+ cursor: options.cursor,
128
+ },
129
+ output: options,
130
+ });
131
+ });
132
+ }
133
+ function createRunFilesCommand(factory) {
134
+ const command = new Command('files').description('Inspect run files');
135
+ command.addCommand(createRunFilesListCommand(factory));
136
+ command.addCommand(createRunFilesExportCommand(factory));
137
+ return command;
138
+ }
139
+ function createRunFilesListCommand(factory) {
140
+ return addOutputFlags(addPaginationFlags(new Command('list').description('List run files').argument('<runId>')).option('--type <type>', 'Filter by file type')).action(async (runId, options) => {
141
+ await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(runId)}/files`, {
142
+ query: { type: options.type, limit: options.limit, cursor: options.cursor },
143
+ output: options,
144
+ });
145
+ });
146
+ }
147
+ function createRunFilesExportCommand(factory) {
148
+ return addOutputFlags(new Command('export')
149
+ .description('Export run files as an archive')
150
+ .argument('<runId>')
151
+ .requiredOption('--to <path>', 'Output archive path, or - for stdout')
152
+ .option('--name <name>', 'Export file name', 'run-files.zip')
153
+ .option('--type <type...>', 'Filter exported file types')
154
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin')).action(async (runId, options) => {
48
155
  const client = await factory.apiClient();
49
- const stream = await client.streamText(`/runs/${encodeURIComponent(id)}/events/stream`, {
156
+ const body = typeof options.input === 'string'
157
+ ? await readJsonFile(options.input)
158
+ : {
159
+ format: 'zip',
160
+ name: options.name,
161
+ filter: options.type ? { type: options.type } : undefined,
162
+ };
163
+ const result = await client.post(`/runs/${encodeURIComponent(runId)}/files/export`, { body });
164
+ const fileId = result.fileId ?? result.id;
165
+ if (!fileId) {
166
+ throw new CliError('Expected run export response to include fileId');
167
+ }
168
+ const data = await client.download(`/files/${encodeURIComponent(fileId)}/content`);
169
+ await writeBinary(options.to, data);
170
+ });
171
+ }
172
+ function createRunInvocationsCommand(factory) {
173
+ const command = addOutputFlags(addPaginationFlags(new Command('invocations').description('List run invocations').argument('<runId>'))
174
+ .option('--status <status>', 'Filter by invocation status')
175
+ .option('--action <action>', 'Filter by invocation action'));
176
+ command.action(async (runId, options) => {
177
+ await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(runId)}/invocations`, {
50
178
  query: {
51
- type: options.type?.join(','),
52
- category: options.category?.join(','),
53
- connectionId: options.connection,
179
+ status: options.status,
180
+ action: options.action,
54
181
  limit: options.limit,
55
182
  cursor: options.cursor,
56
183
  },
184
+ output: options,
57
185
  });
186
+ });
187
+ return command;
188
+ }
189
+ function createRunInvocationCommand(factory) {
190
+ const command = new Command('invocation').description('Inspect run invocations');
191
+ command.addCommand(addOutputFlags(new Command('get')
192
+ .description('Get a run invocation')
193
+ .argument('<runId>')
194
+ .argument('<invocationId>')).action(async (runId, invocationId, options) => {
195
+ await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(runId)}/invocations/${encodeURIComponent(invocationId)}`, { output: options });
196
+ }));
197
+ return command;
198
+ }
199
+ function createRunViewerCommand(factory, name, description) {
200
+ return createJsonBodyCommand(factory, {
201
+ name,
202
+ description,
203
+ method: 'post',
204
+ path: `/runs/{runId}/${name}`,
205
+ argNames: ['runId'],
206
+ configure: (cmd) => cmd
207
+ .option('--expires-in-seconds <seconds>', 'Viewer expiration in seconds', parsePositiveInteger)
208
+ .option('--control <none|input>', 'Live viewer control mode')
209
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
210
+ body: async (_args, options) => {
211
+ if (typeof options.input === 'string')
212
+ return readJsonFile(options.input);
213
+ return {
214
+ expiresInSeconds: typeof options.expiresInSeconds === 'number' ? options.expiresInSeconds : undefined,
215
+ control: name === 'live' ? options.control : undefined,
216
+ };
217
+ },
218
+ });
219
+ }
220
+ function createSseCommand(factory, config) {
221
+ let command = new Command(config.name).description(config.description).argument('<runId>');
222
+ command = config.configure(command);
223
+ return command.action(async (runId, options) => {
224
+ const client = await factory.apiClient();
225
+ const stream = await client.streamText(`/runs/${encodeURIComponent(runId)}/${config.pathSuffix}`, { query: config.query(options) });
58
226
  await renderSseStream(factory, stream);
59
227
  });
60
228
  }
@@ -110,138 +278,20 @@ function formatRunEvent(value) {
110
278
  if (!value || typeof value !== 'object')
111
279
  return JSON.stringify(value);
112
280
  const event = value;
113
- const time = typeof event.time === 'string' ? event.time : '';
281
+ const time = typeof event.time === 'string'
282
+ ? event.time
283
+ : typeof event.createdAt === 'string'
284
+ ? event.createdAt
285
+ : '';
114
286
  const label = [event.category, event.type].filter((part) => typeof part === 'string').join('/');
115
287
  const status = typeof event.status === 'string' ? ` ${event.status}` : '';
116
- const name = typeof event.name === 'string' ? ` ${event.name}` : '';
117
- const suffix = event.data && Object.keys(event.data).length > 0 ? ` ${JSON.stringify(event.data)}` : '';
288
+ const name = typeof event.title === 'string'
289
+ ? ` ${event.title}`
290
+ : typeof event.name === 'string'
291
+ ? ` ${event.name}`
292
+ : '';
293
+ const suffix = event.data && Object.keys(event.data).length > 0
294
+ ? ` ${JSON.stringify(event.data)}`
295
+ : '';
118
296
  return [time, label || 'event'].filter(Boolean).join(' ') + status + name + suffix;
119
297
  }
120
- function createRunEventsCommand(factory) {
121
- return addOutputFlags(new Command('events')
122
- .description('List run events')
123
- .argument('<id>')
124
- .option('--type <type...>', 'Filter by event type')
125
- .option('--category <category...>', 'Filter by event category')
126
- .option('--connection <id>', 'Filter by connection id')
127
- .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
128
- .option('--cursor <cursor>', 'Pagination cursor'))
129
- .action(async (id, options) => {
130
- await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(id)}/events`, {
131
- query: {
132
- type: options.type?.join(','),
133
- category: options.category?.join(','),
134
- connectionId: options.connection,
135
- limit: options.limit,
136
- cursor: options.cursor,
137
- },
138
- output: options,
139
- });
140
- });
141
- }
142
- function createRunCommandsCommand(factory) {
143
- return addOutputFlags(new Command('commands')
144
- .description('List run commands')
145
- .argument('<id>')
146
- .option('--type <type>', 'Filter by command type')
147
- .option('--connection <id>', 'Filter by connection id')
148
- .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
149
- .option('--cursor <cursor>', 'Pagination cursor'))
150
- .action(async (id, options) => {
151
- await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(id)}/commands`, {
152
- query: {
153
- type: options.type,
154
- connectionId: options.connection,
155
- limit: options.limit,
156
- cursor: options.cursor,
157
- },
158
- output: options,
159
- });
160
- });
161
- }
162
- function createRunFilesCommand(factory) {
163
- return addOutputFlags(new Command('files')
164
- .description('List run files')
165
- .argument('<id>')
166
- .option('--type <type>', 'Filter by file type')
167
- .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
168
- .option('--cursor <cursor>', 'Pagination cursor'))
169
- .action(async (id, options) => {
170
- await requestAndPrint(factory, 'get', `/runs/${encodeURIComponent(id)}/files`, {
171
- query: { type: options.type, limit: options.limit, cursor: options.cursor },
172
- output: options,
173
- });
174
- });
175
- }
176
- function createRunExportCommand(factory) {
177
- return addOutputFlags(new Command('export')
178
- .description('Export run files as an archive')
179
- .argument('<id>')
180
- .requiredOption('--to <path>', 'Output archive path, or - for stdout')
181
- .option('--name <name>', 'Export file name', 'run-files.zip')
182
- .option('--type <type...>', 'Filter exported file types'))
183
- .action(async (id, options) => {
184
- const client = await factory.apiClient();
185
- const result = await client.post(`/runs/${encodeURIComponent(id)}/files/export`, {
186
- body: {
187
- format: 'zip',
188
- name: options.name,
189
- filter: options.type ? { type: options.type } : undefined,
190
- },
191
- });
192
- const fileId = result.fileId ?? result.id;
193
- if (!fileId) {
194
- throw new CliError('Expected run export response to include fileId');
195
- }
196
- const data = await client.download(`/files/${encodeURIComponent(fileId)}/content`);
197
- await writeBinary(options.to, data);
198
- });
199
- }
200
- function createRunWaitCommand(factory) {
201
- return createJsonBodyCommand(factory, {
202
- name: 'wait',
203
- description: 'Wait for a run event',
204
- method: 'post',
205
- path: '/runs/{id}/events/wait',
206
- argNames: ['id'],
207
- configure: (cmd) => cmd
208
- .option('--type <type...>', 'Event type')
209
- .option('--category <category...>', 'Event category')
210
- .option('--connection <id>', 'Connection id')
211
- .option('--cursor <cursor>', 'Cursor')
212
- .option('--timeout-ms <ms>', 'Timeout in milliseconds')
213
- .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
214
- body: async (_args, options) => {
215
- if (typeof options.input === 'string')
216
- return readJsonFile(options.input);
217
- return {
218
- type: options.type,
219
- category: options.category,
220
- connectionId: options.connection,
221
- cursor: options.cursor,
222
- timeoutMs: options.timeoutMs ? Number(options.timeoutMs) : undefined,
223
- };
224
- },
225
- });
226
- }
227
- function createRunViewerCommand(factory, name, description) {
228
- return createJsonBodyCommand(factory, {
229
- name,
230
- description,
231
- method: 'post',
232
- path: `/runs/{id}/${name}`,
233
- argNames: ['id'],
234
- configure: (cmd) => cmd
235
- .option('--expires-in-seconds <seconds>', 'Viewer expiration in seconds')
236
- .option('--control <none|input>', 'Live viewer control mode')
237
- .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
238
- body: async (_args, options) => {
239
- if (typeof options.input === 'string')
240
- return readJsonFile(options.input);
241
- return {
242
- expiresInSeconds: options.expiresInSeconds ? Number(options.expiresInSeconds) : undefined,
243
- control: name === 'live' ? options.control : undefined,
244
- };
245
- },
246
- });
247
- }