@bctrl/cli 0.1.0 → 0.1.2

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,97 @@
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 connection create rt_123
42
+ bctrl runtime stop rt_123
21
43
  ```
22
44
 
23
- ## Command Boundary
45
+ ## JSON Input
46
+
47
+ Use `--input` for full request bodies:
24
48
 
25
- This package is for public BCTRL API usage:
49
+ ```bash
50
+ bctrl runtime create --input runtime.json
51
+ ```
26
52
 
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
53
+ Use `-` to read from stdin:
35
54
 
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`.
55
+ ```bash
56
+ echo '{"type":"browser","spaceId":"sp_123","name":"agent-runtime"}' \
57
+ | bctrl runtime create --input -
58
+ ```
59
+
60
+ ## Output
61
+
62
+ Print full JSON:
63
+
64
+ ```bash
65
+ bctrl runtime list --space sp_123 --json
66
+ ```
67
+
68
+ Print selected JSON fields:
69
+
70
+ ```bash
71
+ bctrl runtime list --space sp_123 --json id,status,name
72
+ ```
73
+
74
+ Filter with `jq`:
75
+
76
+ ```bash
77
+ bctrl runtime list --space sp_123 --json --jq '.data[] | select(.status == "active")'
78
+ ```
79
+
80
+ Render with a template:
81
+
82
+ ```bash
83
+ bctrl runtime list \
84
+ --space sp_123 \
85
+ --json \
86
+ --template '{{#each data}}{{id}} {{status}}{{newline}}{{/each}}'
87
+ ```
37
88
 
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`.
89
+ ## Help
39
90
 
40
- ## Build
91
+ Use command help for exact input and output fields:
41
92
 
42
93
  ```bash
43
- pnpm -C cli run build
94
+ bctrl runtime create --help
95
+ bctrl runtime start --help
96
+ bctrl runtime connection create --help
44
97
  ```
@@ -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(),
@@ -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
7
  import { addPaginationFlags, createJsonBodyCommand, createListCommand, createViewCommand, pathTemplate, 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,21 +24,25 @@ 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
48
  config: typeof options.configFile === 'string' ? await readJsonFile(options.configFile, '--config-file') : undefined,
@@ -46,7 +51,15 @@ export function createRuntimeCommand(factory) {
46
51
  : undefined,
47
52
  };
48
53
  },
49
- }));
54
+ }), 'runtime.create'));
55
+ command.addCommand(addCliHelp(addOutputFlags(new Command('start')
56
+ .description('Start a runtime')
57
+ .argument('<id>'))
58
+ .action(async (id, options) => {
59
+ await requestAndPrint(factory, 'post', `/runtimes/${encodeURIComponent(id)}/start`, {
60
+ output: options,
61
+ });
62
+ }), 'runtime.start'));
50
63
  command.addCommand(addOutputFlags(new Command('stop')
51
64
  .description('Stop a runtime')
52
65
  .argument('<id>'))
@@ -83,7 +96,7 @@ function createRuntimeConnectionCommand(factory) {
83
96
  output: options,
84
97
  });
85
98
  }));
86
- command.addCommand(createJsonBodyCommand(factory, {
99
+ command.addCommand(addCliHelp(createJsonBodyCommand(factory, {
87
100
  name: 'create',
88
101
  description: 'Create a runtime connection',
89
102
  method: 'post',
@@ -91,17 +104,15 @@ function createRuntimeConnectionCommand(factory) {
91
104
  argNames: ['runtime'],
92
105
  configure: (cmd) => cmd
93
106
  .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'),
107
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
96
108
  body: async (_args, options) => {
97
- if (typeof options.fromFile === 'string')
98
- return readJsonFile(options.fromFile);
109
+ if (typeof options.input === 'string')
110
+ return readJsonFile(options.input);
99
111
  return {
100
112
  protocol: options.protocol,
101
- ttlSeconds: options.ttlSeconds ? Number(options.ttlSeconds) : undefined,
102
113
  };
103
114
  },
104
- }));
115
+ }), 'runtime.connection.create'));
105
116
  command.addCommand(addOutputFlags(new Command('delete')
106
117
  .description('Revoke a runtime connection')
107
118
  .argument('<runtime>')
@@ -169,10 +180,10 @@ function createRuntimeFileCommand(factory) {
169
180
  .option('--file <id>', 'Durable file id')
170
181
  .option('--storage-path <path>', 'Advanced: durable BCTRL storage path')
171
182
  .option('--name <name>', 'Runtime-local name')
172
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
183
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
173
184
  body: async (_args, options) => {
174
- if (typeof options.fromFile === 'string')
175
- return readJsonFile(options.fromFile);
185
+ if (typeof options.input === 'string')
186
+ return readJsonFile(options.input);
176
187
  return { fileId: options.file, storagePath: options.storagePath, name: options.name };
177
188
  },
178
189
  }));
@@ -186,10 +197,10 @@ function createRuntimeFileCommand(factory) {
186
197
  .requiredOption('--path <path>', 'Runtime-local path')
187
198
  .option('--name <name>', 'Durable file name')
188
199
  .option('--storage-path <path>', 'Advanced: durable BCTRL storage path')
189
- .option('--from-file <path>', 'Read JSON request body from file, or - for stdin'),
200
+ .option('--input <path>', 'Read JSON request body from file, or - for stdin'),
190
201
  body: async (_args, options) => {
191
- if (typeof options.fromFile === 'string')
192
- return readJsonFile(options.fromFile);
202
+ if (typeof options.input === 'string')
203
+ return readJsonFile(options.input);
193
204
  return { path: options.path, name: options.name, storagePath: options.storagePath };
194
205
  },
195
206
  }));
@@ -0,0 +1,24 @@
1
+ import type { Command } from 'commander';
2
+ import { type CliHelpCommandId } from '../../generated/help.js';
3
+ export type CommandHelpField = {
4
+ name: string;
5
+ type: string;
6
+ required?: boolean;
7
+ description?: string;
8
+ };
9
+ export type CommandHelpFlag = {
10
+ name: string;
11
+ value?: string;
12
+ description: string;
13
+ mapsTo?: string;
14
+ };
15
+ export type CommandHelpSpec = {
16
+ purpose: string;
17
+ flags?: CommandHelpFlag[];
18
+ input?: CommandHelpField[];
19
+ output?: CommandHelpField[];
20
+ examples?: string[];
21
+ next?: string[];
22
+ };
23
+ export declare function addStructuredHelp(command: Command, spec: CommandHelpSpec): Command;
24
+ export declare function addCliHelp(command: Command, commandId: CliHelpCommandId): Command;