@bctrl/cli 0.1.0 → 0.1.3

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 CHANGED
@@ -1,44 +1,95 @@
1
1
  # BCTRL CLI
2
2
 
3
- TypeScript command-line interface for BCTRL.
3
+ CLI for controlling BCTRL runtimes, files, runs, vault secrets, tools, and account resources from scripts or AI agents.
4
4
 
5
- This package is intentionally scaffolded around a GitHub CLI-style command architecture:
5
+ ## Install
6
6
 
7
- - tiny bin entrypoint
8
- - testable runtime `main()`
9
- - lazy factory dependencies
10
- - IO streams instead of direct `console.log`
11
- - one module per leaf command
12
- - `Options + createCommand + run` per command
7
+ ```bash
8
+ npm install -g @bctrl/cli
9
+ ```
10
+
11
+ ## Auth
12
+
13
+ The CLI authenticates with a BCTRL API key.
14
+
15
+ Use an API key for the current shell:
16
+
17
+ ```bash
18
+ export BCTRL_API_KEY="bctrl_..."
19
+ ```
20
+
21
+ Or save it locally:
22
+
23
+ ```bash
24
+ bctrl auth login
25
+ ```
26
+
27
+ If `BCTRL_API_KEY` is already set, `auth login` saves that key. Otherwise, it prompts you to paste an API key.
28
+
29
+ Check the active auth:
30
+
31
+ ```bash
32
+ bctrl auth status
33
+ ```
13
34
 
14
- ## Development
35
+ ## Browser Runtime Flow
15
36
 
16
37
  ```bash
17
- pnpm -C cli run dev -- --help
18
- pnpm -C cli run dev -- version
19
- pnpm -C cli run dev -- auth status
20
- pnpm -C cli run dev -- space list
38
+ bctrl space list
39
+ bctrl runtime create --space sp_123 --name checkout-test
40
+ bctrl runtime start rt_123
41
+ bctrl runtime stop rt_123
21
42
  ```
22
43
 
23
- ## Command Boundary
44
+ ## JSON Input
45
+
46
+ Use `--input` for full request bodies:
24
47
 
25
- This package is for public BCTRL API usage:
48
+ ```bash
49
+ bctrl runtime create --input runtime.json
50
+ ```
26
51
 
27
- - auth
28
- - spaces
29
- - runtimes
30
- - runs
31
- - invocations
32
- - files
33
- - subaccounts
34
- - `bctrl version` for current version and future non-blocking update hints
52
+ Use `-` to read from stdin:
35
53
 
36
- Do not add maintainer-only release commands here. CLI publishing belongs in pnpm scripts and CI/CD. Infrastructure rollout commands belong in `@bctrl/machine-cli`.
54
+ ```bash
55
+ echo '{"type":"browser","spaceId":"sp_123","name":"agent-runtime"}' \
56
+ | bctrl runtime create --input -
57
+ ```
58
+
59
+ ## Output
60
+
61
+ Print full JSON:
62
+
63
+ ```bash
64
+ bctrl runtime list --space sp_123 --json
65
+ ```
66
+
67
+ Print selected JSON fields:
68
+
69
+ ```bash
70
+ bctrl runtime list --space sp_123 --json id,status,name
71
+ ```
72
+
73
+ Filter with `jq`:
74
+
75
+ ```bash
76
+ bctrl runtime list --space sp_123 --json --jq '.data[] | select(.status == "active")'
77
+ ```
78
+
79
+ Render with a template:
80
+
81
+ ```bash
82
+ bctrl runtime list \
83
+ --space sp_123 \
84
+ --json \
85
+ --template '{{#each data}}{{id}} {{status}}{{newline}}{{/each}}'
86
+ ```
37
87
 
38
- Do not add a public `bctrl update` command initially. The public update surface is `bctrl version`, which can later show a safe upgrade command such as `npm install -g @bctrl/cli@latest`.
88
+ ## Help
39
89
 
40
- ## Build
90
+ Use command help for exact input and output fields:
41
91
 
42
92
  ```bash
43
- pnpm -C cli run build
93
+ bctrl runtime create --help
94
+ bctrl runtime start --help
44
95
  ```
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  export declare const AuthWhoamiSchema: z.ZodObject<{
3
3
  authenticated: z.ZodLiteral<true>;
4
+ email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
4
5
  keyKind: z.ZodEnum<{
5
6
  user: "user";
6
7
  subaccount: "subaccount";
package/dist/api/auth.js CHANGED
@@ -4,6 +4,7 @@ import { apiErrorFromResponse } from './errors.js';
4
4
  export const AuthWhoamiSchema = z
5
5
  .object({
6
6
  authenticated: z.literal(true),
7
+ email: z.string().email().nullable().optional(),
7
8
  keyKind: z.enum(['user', 'subaccount']),
8
9
  organizationId: z.string().min(1),
9
10
  subaccountId: z.string().min(1).nullable().optional(),
package/dist/bin/bctrl.js CHANGED
File without changes
@@ -38,18 +38,26 @@ function createAiProviderCommand(factory) {
38
38
  .option('--api-key <key>', 'Provider API key')
39
39
  .option('--status <status>', 'Provider status')
40
40
  .option('--default-model <model>', 'Default model')
41
+ .option('--base-url <url>', 'OpenAI-compatible provider base URL')
41
42
  .option('--subaccount <id>', 'Subaccount scope')
42
- .option('--from-file <path>', 'Read full JSON request body from file, or - for stdin'),
43
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
43
44
  body: async (_args, options) => {
44
- if (typeof options.fromFile === 'string')
45
- return readJsonFile(options.fromFile);
45
+ if (typeof options.input === 'string')
46
+ return readJsonFile(options.input);
46
47
  return {
47
48
  name: options.name,
48
49
  provider: options.provider,
49
50
  apiKey: options.apiKey,
50
51
  status: options.status,
51
- scope: typeof options.subaccount === 'string' ? { subaccountId: options.subaccount } : undefined,
52
- config: typeof options.defaultModel === 'string' ? { defaultModel: options.defaultModel } : undefined,
52
+ scope: typeof options.subaccount === 'string'
53
+ ? { subaccountId: options.subaccount }
54
+ : undefined,
55
+ config: typeof options.defaultModel === 'string' || typeof options.baseUrl === 'string'
56
+ ? {
57
+ defaultModel: typeof options.defaultModel === 'string' ? options.defaultModel : undefined,
58
+ baseUrl: typeof options.baseUrl === 'string' ? options.baseUrl : undefined,
59
+ }
60
+ : undefined,
53
61
  };
54
62
  },
55
63
  }));
@@ -64,22 +72,25 @@ function createAiProviderCommand(factory) {
64
72
  .option('--api-key <key>', 'Provider API key')
65
73
  .option('--status <status>', 'Provider status')
66
74
  .option('--default-model <model>', 'Default model')
67
- .option('--from-file <path>', 'Read full JSON request body from file, or - for stdin'),
75
+ .option('--base-url <url>', 'OpenAI-compatible provider base URL')
76
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
68
77
  body: async (_args, options) => {
69
- if (typeof options.fromFile === 'string')
70
- return readJsonFile(options.fromFile);
78
+ if (typeof options.input === 'string')
79
+ return readJsonFile(options.input);
71
80
  return {
72
81
  name: options.name,
73
82
  apiKey: options.apiKey,
74
83
  status: options.status,
75
- config: typeof options.defaultModel === 'string' ? { defaultModel: options.defaultModel } : undefined,
84
+ config: typeof options.defaultModel === 'string' || typeof options.baseUrl === 'string'
85
+ ? {
86
+ defaultModel: typeof options.defaultModel === 'string' ? options.defaultModel : undefined,
87
+ baseUrl: typeof options.baseUrl === 'string' ? options.baseUrl : undefined,
88
+ }
89
+ : undefined,
76
90
  };
77
91
  },
78
92
  }));
79
- command.addCommand(addOutputFlags(new Command('test')
80
- .description('Test an AI provider')
81
- .argument('<id>'))
82
- .action(async (id, options) => {
93
+ command.addCommand(addOutputFlags(new Command('test').description('Test an AI provider').argument('<id>')).action(async (id, options) => {
83
94
  await requestAndPrint(factory, 'post', `/ai-providers/${encodeURIComponent(id)}/test`, {
84
95
  output: options,
85
96
  });
@@ -114,10 +125,7 @@ function createAiProxyCommand(factory) {
114
125
  path: '/proxies/{id}',
115
126
  argNames: ['id'],
116
127
  }));
117
- command.addCommand(addOutputFlags(new Command('test')
118
- .description('Test a proxy')
119
- .argument('<id>'))
120
- .action(async (id, options) => {
128
+ command.addCommand(addOutputFlags(new Command('test').description('Test a proxy').argument('<id>')).action(async (id, options) => {
121
129
  await requestAndPrint(factory, 'post', `/proxies/${encodeURIComponent(id)}/test`, {
122
130
  output: options,
123
131
  });
@@ -2,10 +2,13 @@ import { Command } from 'commander';
2
2
  import type { BctrlConfig } from '../../config/config.js';
3
3
  import type { Factory } from '../../factory.js';
4
4
  import type { IOStreams } from '../../io/streams.js';
5
+ import { validateAuthToken } from '../../api/auth.js';
5
6
  export type AuthLoginOptions = {
6
7
  io: IOStreams;
7
8
  config: () => Promise<BctrlConfig>;
8
9
  withToken?: boolean;
10
+ validateToken?: typeof validateAuthToken;
11
+ env?: NodeJS.ProcessEnv;
9
12
  };
10
13
  export declare function createAuthLoginCommand(factory: Factory, run?: (options: AuthLoginOptions) => Promise<void>): Command;
11
14
  export declare function authLoginRun(options: AuthLoginOptions): Promise<void>;
@@ -17,13 +17,12 @@ export function createAuthLoginCommand(factory, run = authLoginRun) {
17
17
  }
18
18
  export async function authLoginRun(options) {
19
19
  const config = await options.config();
20
- const token = options.withToken
21
- ? (await readStreamText(options.io.in)).trim()
22
- : await readHiddenInput(options.io, 'Paste BCTRL API key: ');
20
+ const { token, source } = await resolveLoginToken(options, config);
23
21
  if (!token) {
24
22
  throw new CliError('No API key provided');
25
23
  }
26
- const whoami = await validateAuthToken(config.apiBaseUrl, token);
24
+ const validateToken = options.validateToken ?? validateAuthToken;
25
+ const whoami = await validateToken(config.apiBaseUrl, token);
27
26
  const now = new Date().toISOString();
28
27
  const path = await writeStoredAuth({
29
28
  apiBaseUrl: config.apiBaseUrl,
@@ -31,7 +30,10 @@ export async function authLoginRun(options) {
31
30
  whoami,
32
31
  createdAt: now,
33
32
  validatedAt: now,
34
- });
33
+ }, options.env);
34
+ if (source === 'env') {
35
+ options.io.writeErr('Using BCTRL_API_KEY from environment.\n');
36
+ }
35
37
  options.io.writeErr(`Logged in to ${config.apiBaseUrl}\n`);
36
38
  options.io.writeErr(`Credentials saved to ${path}\n`);
37
39
  options.io.writeErr(`Key kind: ${whoami.keyKind}\n`);
@@ -43,6 +45,18 @@ export async function authLoginRun(options) {
43
45
  options.io.writeErr('BCTRL_API_KEY is set and will take precedence over stored credentials.\n');
44
46
  }
45
47
  }
48
+ async function resolveLoginToken(options, config) {
49
+ if (options.withToken) {
50
+ return { token: (await readStreamText(options.io.in)).trim(), source: 'stdin' };
51
+ }
52
+ if (config.activeToken?.source === 'BCTRL_API_KEY') {
53
+ return { token: config.activeToken.token, source: 'env' };
54
+ }
55
+ return {
56
+ token: await readHiddenInput(options.io, 'Paste BCTRL API key: '),
57
+ source: 'prompt',
58
+ };
59
+ }
46
60
  async function readStreamText(stream) {
47
61
  const chunks = [];
48
62
  for await (const chunk of stream) {
@@ -5,6 +5,7 @@ import type { IOStreams } from '../../io/streams.js';
5
5
  export type AuthLogoutOptions = {
6
6
  io: IOStreams;
7
7
  config: () => Promise<BctrlConfig>;
8
+ env?: NodeJS.ProcessEnv;
8
9
  };
9
10
  export declare function createAuthLogoutCommand(factory: Factory, run?: (options: AuthLogoutOptions) => Promise<void>): Command;
10
11
  export declare function authLogoutRun(options: AuthLogoutOptions): Promise<void>;
@@ -10,7 +10,7 @@ export function createAuthLogoutCommand(factory, run = authLogoutRun) {
10
10
  }
11
11
  export async function authLogoutRun(options) {
12
12
  const config = await options.config();
13
- const removed = await deleteStoredAuth();
13
+ const removed = await deleteStoredAuth(options.env);
14
14
  if (removed) {
15
15
  options.io.writeErr('Removed stored BCTRL credentials.\n');
16
16
  }
@@ -1,4 +1,5 @@
1
1
  import { Command } from 'commander';
2
+ import { validateAuthToken } from '../../api/auth.js';
2
3
  import type { BctrlConfig } from '../../config/config.js';
3
4
  import type { Factory } from '../../factory.js';
4
5
  import type { IOStreams } from '../../io/streams.js';
@@ -7,6 +8,7 @@ export type AuthStatusOptions = {
7
8
  io: IOStreams;
8
9
  config: () => Promise<BctrlConfig>;
9
10
  output?: OutputFlags;
11
+ validateToken?: typeof validateAuthToken;
10
12
  };
11
13
  export declare function createAuthStatusCommand(factory: Factory, run?: (options: AuthStatusOptions) => Promise<void>): Command;
12
14
  export declare function authStatusRun(options: AuthStatusOptions): Promise<void>;
@@ -15,17 +15,61 @@ export function createAuthStatusCommand(factory, run = authStatusRun) {
15
15
  export async function authStatusRun(options) {
16
16
  const config = await options.config();
17
17
  if (!config.activeToken) {
18
- await outputData(options.io, {
18
+ const status = {
19
19
  apiBaseUrl: config.apiBaseUrl,
20
20
  authenticated: false,
21
21
  tokenSource: null,
22
- }, options.output);
22
+ };
23
+ if (usesStructuredOutput(options.output)) {
24
+ await outputData(options.io, status, options.output);
25
+ }
26
+ else {
27
+ writeHumanAuthStatus(options.io, status);
28
+ }
23
29
  return;
24
30
  }
25
- const whoami = await validateAuthToken(config.apiBaseUrl, config.activeToken.token);
26
- await outputData(options.io, {
31
+ const validateToken = options.validateToken ?? validateAuthToken;
32
+ const whoami = await validateToken(config.apiBaseUrl, config.activeToken.token);
33
+ const status = {
27
34
  apiBaseUrl: config.apiBaseUrl,
28
35
  tokenSource: config.activeToken.source,
29
36
  ...whoami,
30
- }, options.output);
37
+ };
38
+ if (usesStructuredOutput(options.output)) {
39
+ await outputData(options.io, status, options.output);
40
+ }
41
+ else {
42
+ writeHumanAuthStatus(options.io, status);
43
+ }
44
+ }
45
+ function usesStructuredOutput(output) {
46
+ return Boolean(output?.json !== undefined || output?.jq || output?.template);
47
+ }
48
+ function writeHumanAuthStatus(io, status) {
49
+ if (!status.authenticated) {
50
+ io.writeOut([
51
+ 'Authenticated: no',
52
+ `API: ${status.apiBaseUrl}`,
53
+ '',
54
+ 'Run:',
55
+ ' bctrl auth login',
56
+ '',
57
+ ].join('\n'));
58
+ return;
59
+ }
60
+ io.writeOut([
61
+ 'Authenticated: yes',
62
+ `Email: ${status.email ?? '-'}`,
63
+ `Plan: ${status.plan}`,
64
+ `Auth: ${formatTokenSource(status.tokenSource)}`,
65
+ `API: ${status.apiBaseUrl}`,
66
+ '',
67
+ ].join('\n'));
68
+ }
69
+ function formatTokenSource(source) {
70
+ if (source === 'BCTRL_API_KEY')
71
+ return 'environment API key';
72
+ if (source === 'stored')
73
+ return 'saved API key';
74
+ return source;
31
75
  }
@@ -37,10 +37,10 @@ function createBrowserExtensionCommand(factory) {
37
37
  path: '/browser-extensions/import',
38
38
  configure: (cmd) => cmd
39
39
  .option('--url <url>', 'Extension URL')
40
- .option('--from-file <path>', 'Read full JSON request body from file, or - for stdin'),
40
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
41
41
  body: async (_args, options) => {
42
- if (typeof options.fromFile === 'string') {
43
- return readJsonFile(options.fromFile);
42
+ if (typeof options.input === 'string') {
43
+ return readJsonFile(options.input);
44
44
  }
45
45
  return { source: { type: 'url', url: options.url } };
46
46
  },
@@ -67,10 +67,10 @@ export function createFileCommand(factory) {
67
67
  configure: (cmd) => cmd
68
68
  .option('--name <name>', 'Display name')
69
69
  .option('--metadata-file <path>', 'Metadata JSON file')
70
- .option('--from-file <path>', 'Read full JSON request body from file, or - for stdin'),
70
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
71
71
  body: async (_args, options) => {
72
- if (typeof options.fromFile === 'string')
73
- return readJsonFile(options.fromFile);
72
+ if (typeof options.input === 'string')
73
+ return readJsonFile(options.input);
74
74
  return {
75
75
  name: options.name,
76
76
  metadata: typeof options.metadataFile === 'string'
@@ -17,10 +17,10 @@ export function createInvocationCommand(factory) {
17
17
  argNames: ['id'],
18
18
  configure: (cmd) => cmd
19
19
  .option('--timeout-ms <ms>', 'Timeout in milliseconds')
20
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
20
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
21
21
  body: async (_args, options) => {
22
- if (typeof options.fromFile === 'string')
23
- return readJsonFile(options.fromFile);
22
+ if (typeof options.input === 'string')
23
+ return readJsonFile(options.input);
24
24
  return { timeoutMs: options.timeoutMs ? Number(options.timeoutMs) : undefined };
25
25
  },
26
26
  }));
@@ -210,10 +210,10 @@ function createRunWaitCommand(factory) {
210
210
  .option('--connection <id>', 'Connection id')
211
211
  .option('--cursor <cursor>', 'Cursor')
212
212
  .option('--timeout-ms <ms>', 'Timeout in milliseconds')
213
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
213
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
214
214
  body: async (_args, options) => {
215
- if (typeof options.fromFile === 'string')
216
- return readJsonFile(options.fromFile);
215
+ if (typeof options.input === 'string')
216
+ return readJsonFile(options.input);
217
217
  return {
218
218
  type: options.type,
219
219
  category: options.category,
@@ -234,10 +234,10 @@ function createRunViewerCommand(factory, name, description) {
234
234
  configure: (cmd) => cmd
235
235
  .option('--expires-in-seconds <seconds>', 'Viewer expiration in seconds')
236
236
  .option('--control <none|input>', 'Live viewer control mode')
237
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
237
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
238
238
  body: async (_args, options) => {
239
- if (typeof options.fromFile === 'string')
240
- return readJsonFile(options.fromFile);
239
+ if (typeof options.input === 'string')
240
+ return readJsonFile(options.input);
241
241
  return {
242
242
  expiresInSeconds: options.expiresInSeconds ? Number(options.expiresInSeconds) : undefined,
243
243
  control: name === 'live' ? options.control : undefined,
@@ -1,11 +1,12 @@
1
1
  import { Command } from 'commander';
2
2
  import { CliError } from '../../runtime/errors.js';
3
+ import { addCliHelp } from '../shared/help.js';
3
4
  import { readBlob, readJsonFile, writeBinary } from '../shared/io.js';
4
5
  import { parsePositiveInteger } from '../shared/options.js';
5
6
  import { addOutputFlags, outputData } from '../shared/output.js';
6
- import { addPaginationFlags, createJsonBodyCommand, createListCommand, createViewCommand, pathTemplate, requestAndPrint, } from '../shared/rest.js';
7
+ import { addPaginationFlags, createJsonBodyCommand, createListCommand, createViewCommand, requestAndPrint, } from '../shared/rest.js';
7
8
  export function createRuntimeCommand(factory) {
8
- const command = new Command('runtime').description('Launch and manage BCTRL runtimes');
9
+ const command = new Command('runtime').description('Create, start, stop, and manage BCTRL runtimes');
9
10
  command.addCommand(createListCommand(factory, {
10
11
  description: 'List runtimes',
11
12
  path: '/runtimes',
@@ -23,46 +24,52 @@ export function createRuntimeCommand(factory) {
23
24
  description: 'View a runtime',
24
25
  path: '/runtimes/{id}',
25
26
  }));
26
- command.addCommand(createJsonBodyCommand(factory, {
27
+ command.addCommand(addCliHelp(createJsonBodyCommand(factory, {
27
28
  name: 'create',
28
29
  description: 'Create a runtime',
29
30
  method: 'post',
30
31
  path: '/runtimes',
31
32
  configure: (cmd) => cmd
32
- .requiredOption('--space <id>', 'Space id')
33
+ .option('--space <id>', 'Space id')
33
34
  .option('--name <name>', 'Runtime name')
34
35
  .option('--config-file <path>', 'Runtime config JSON file')
35
36
  .option('--metadata-file <path>', 'Runtime metadata JSON file')
36
- .option('--from-file <path>', 'Read full JSON request body from file, or - for stdin'),
37
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
37
38
  body: async (_args, options) => {
38
- if (typeof options.fromFile === 'string')
39
- return readJsonFile(options.fromFile);
39
+ if (typeof options.input === 'string')
40
+ return readJsonFile(options.input);
41
+ if (typeof options.space !== 'string' || options.space.length === 0) {
42
+ throw new CliError('runtime create requires --space unless --input is used');
43
+ }
40
44
  return {
45
+ type: 'browser',
41
46
  spaceId: options.space,
42
47
  name: options.name,
43
- config: typeof options.configFile === 'string' ? await readJsonFile(options.configFile, '--config-file') : undefined,
48
+ config: typeof options.configFile === 'string'
49
+ ? await readJsonFile(options.configFile, '--config-file')
50
+ : undefined,
44
51
  metadata: typeof options.metadataFile === 'string'
45
52
  ? await readJsonFile(options.metadataFile, '--metadata-file')
46
53
  : undefined,
47
54
  };
48
55
  },
49
- }));
50
- command.addCommand(addOutputFlags(new Command('stop')
51
- .description('Stop a runtime')
52
- .argument('<id>'))
53
- .action(async (id, options) => {
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,
60
+ });
61
+ }), 'runtime.start'));
62
+ command.addCommand(addOutputFlags(new Command('stop').description('Stop a runtime').argument('<id>')).action(async (id, options) => {
54
63
  await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(id)}/stop`, {
55
64
  output: options,
56
65
  });
57
66
  }));
58
- command.addCommand(createRuntimeConnectionCommand(factory));
59
67
  command.addCommand(createRuntimeFileCommand(factory));
60
68
  command.addCommand(addOutputFlags(new Command('runs')
61
69
  .description('List runs for a runtime')
62
70
  .argument('<id>')
63
71
  .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
64
- .option('--cursor <cursor>', 'Pagination cursor'))
65
- .action(async (id, options) => {
72
+ .option('--cursor <cursor>', 'Pagination cursor')).action(async (id, options) => {
66
73
  await requestAndPrint(factory, 'get', `/runtimes/${encodeURIComponent(id)}/runs`, {
67
74
  query: { limit: options.limit, cursor: options.cursor },
68
75
  output: options,
@@ -70,47 +77,6 @@ export function createRuntimeCommand(factory) {
70
77
  }));
71
78
  return command;
72
79
  }
73
- function createRuntimeConnectionCommand(factory) {
74
- const command = new Command('connection').description('Manage runtime connections');
75
- command.addCommand(addOutputFlags(new Command('list')
76
- .description('List runtime connections')
77
- .argument('<runtime>')
78
- .option('--status <status>', 'Filter by connection status')
79
- .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger))
80
- .action(async (runtime, options) => {
81
- await requestAndPrint(factory, 'get', `/runtimes/${encodeURIComponent(runtime)}/connections`, {
82
- query: { status: options.status, limit: options.limit },
83
- output: options,
84
- });
85
- }));
86
- command.addCommand(createJsonBodyCommand(factory, {
87
- name: 'create',
88
- description: 'Create a runtime connection',
89
- method: 'post',
90
- path: '/runtimes/{runtime}/connections',
91
- argNames: ['runtime'],
92
- configure: (cmd) => cmd
93
- .option('--protocol <protocol>', 'Connection protocol', 'cdp')
94
- .option('--ttl-seconds <seconds>', 'Connection TTL in seconds')
95
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
96
- body: async (_args, options) => {
97
- if (typeof options.fromFile === 'string')
98
- return readJsonFile(options.fromFile);
99
- return {
100
- protocol: options.protocol,
101
- ttlSeconds: options.ttlSeconds ? Number(options.ttlSeconds) : undefined,
102
- };
103
- },
104
- }));
105
- command.addCommand(addOutputFlags(new Command('delete')
106
- .description('Revoke a runtime connection')
107
- .argument('<runtime>')
108
- .argument('<connection>'))
109
- .action(async (runtime, connection, options) => {
110
- await requestAndPrint(factory, 'delete', pathTemplate('/runtimes/{runtime}/connections/{connection}', { runtime, connection }), { output: options });
111
- }));
112
- return command;
113
- }
114
80
  function createRuntimeFileCommand(factory) {
115
81
  const command = new Command('file').description('Move files into and out of runtimes');
116
82
  command.addCommand(addOutputFlags(new Command('push')
@@ -119,8 +85,7 @@ function createRuntimeFileCommand(factory) {
119
85
  .argument('<local-path>')
120
86
  .option('--storage-path <path>', 'Advanced: durable BCTRL storage path')
121
87
  .option('--name <name>', 'Display name')
122
- .option('--metadata-file <path>', 'Metadata JSON file'))
123
- .action(async (runtime, localPath, options) => {
88
+ .option('--metadata-file <path>', 'Metadata JSON file')).action(async (runtime, localPath, options) => {
124
89
  const client = await factory.apiClient();
125
90
  const file = await readBlob(localPath);
126
91
  const metadata = typeof options.metadataFile === 'string'
@@ -143,8 +108,7 @@ function createRuntimeFileCommand(factory) {
143
108
  .argument('<runtime-path>')
144
109
  .requiredOption('--to <path>', 'Output path, or - for stdout')
145
110
  .option('--name <name>', 'Durable file name')
146
- .option('--storage-path <path>', 'Advanced: durable BCTRL storage path'))
147
- .action(async (runtime, runtimePath, options) => {
111
+ .option('--storage-path <path>', 'Advanced: durable BCTRL storage path')).action(async (runtime, runtimePath, options) => {
148
112
  const client = await factory.apiClient();
149
113
  const collected = await client.post(`/runtimes/${encodeURIComponent(runtime)}/files/collect`, {
150
114
  body: {
@@ -169,10 +133,10 @@ function createRuntimeFileCommand(factory) {
169
133
  .option('--file <id>', 'Durable file id')
170
134
  .option('--storage-path <path>', 'Advanced: durable BCTRL storage path')
171
135
  .option('--name <name>', 'Runtime-local name')
172
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
136
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
173
137
  body: async (_args, options) => {
174
- if (typeof options.fromFile === 'string')
175
- return readJsonFile(options.fromFile);
138
+ if (typeof options.input === 'string')
139
+ return readJsonFile(options.input);
176
140
  return { fileId: options.file, storagePath: options.storagePath, name: options.name };
177
141
  },
178
142
  }));
@@ -186,10 +150,10 @@ function createRuntimeFileCommand(factory) {
186
150
  .requiredOption('--path <path>', 'Runtime-local path')
187
151
  .option('--name <name>', 'Durable file name')
188
152
  .option('--storage-path <path>', 'Advanced: durable BCTRL storage path')
189
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
153
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
190
154
  body: async (_args, options) => {
191
- if (typeof options.fromFile === 'string')
192
- return readJsonFile(options.fromFile);
155
+ if (typeof options.input === 'string')
156
+ return readJsonFile(options.input);
193
157
  return { path: options.path, name: options.name, storagePath: options.storagePath };
194
158
  },
195
159
  }));