@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 +80 -27
- package/dist/api/auth.d.ts +1 -0
- package/dist/api/auth.js +1 -0
- package/dist/commands/ai/index.js +25 -17
- package/dist/commands/auth/login.d.ts +3 -0
- package/dist/commands/auth/login.js +19 -5
- package/dist/commands/auth/logout.d.ts +1 -0
- package/dist/commands/auth/logout.js +1 -1
- package/dist/commands/auth/status.d.ts +2 -0
- package/dist/commands/auth/status.js +49 -5
- package/dist/commands/browser/index.js +3 -3
- package/dist/commands/file/index.js +3 -3
- package/dist/commands/invocation/index.js +3 -3
- package/dist/commands/run/index.js +6 -6
- package/dist/commands/runtime/index.js +31 -20
- package/dist/commands/shared/help.d.ts +24 -0
- package/dist/commands/shared/help.js +72 -0
- package/dist/commands/shared/io.js +1 -1
- package/dist/commands/shared/rest.d.ts +1 -1
- package/dist/commands/shared/rest.js +3 -3
- package/dist/commands/space/index.js +6 -6
- package/dist/commands/subaccount/index.js +12 -12
- package/dist/commands/vault/index.js +3 -3
- package/dist/config/auth-store.d.ts +2 -0
- package/dist/config/auth-store.js +1 -0
- package/dist/config/config.js +1 -1
- package/dist/factory.js +1 -1
- package/dist/generated/help.d.ts +205 -0
- package/dist/generated/help.js +283 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,44 +1,97 @@
|
|
|
1
1
|
# BCTRL CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for controlling BCTRL runtimes, files, runs, vault secrets, tools, and account resources from scripts or AI agents.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
##
|
|
35
|
+
## Browser Runtime Flow
|
|
15
36
|
|
|
16
37
|
```bash
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
##
|
|
45
|
+
## JSON Input
|
|
46
|
+
|
|
47
|
+
Use `--input` for full request bodies:
|
|
24
48
|
|
|
25
|
-
|
|
49
|
+
```bash
|
|
50
|
+
bctrl runtime create --input runtime.json
|
|
51
|
+
```
|
|
26
52
|
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
## Help
|
|
39
90
|
|
|
40
|
-
|
|
91
|
+
Use command help for exact input and output fields:
|
|
41
92
|
|
|
42
93
|
```bash
|
|
43
|
-
|
|
94
|
+
bctrl runtime create --help
|
|
95
|
+
bctrl runtime start --help
|
|
96
|
+
bctrl runtime connection create --help
|
|
44
97
|
```
|
package/dist/api/auth.d.ts
CHANGED
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('--
|
|
43
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
43
44
|
body: async (_args, options) => {
|
|
44
|
-
if (typeof options.
|
|
45
|
-
return readJsonFile(options.
|
|
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'
|
|
52
|
-
|
|
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('--
|
|
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.
|
|
70
|
-
return readJsonFile(options.
|
|
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'
|
|
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
|
|
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
|
|
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
|
-
|
|
18
|
+
const status = {
|
|
19
19
|
apiBaseUrl: config.apiBaseUrl,
|
|
20
20
|
authenticated: false,
|
|
21
21
|
tokenSource: null,
|
|
22
|
-
}
|
|
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
|
|
26
|
-
await
|
|
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
|
-
}
|
|
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('--
|
|
40
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
41
41
|
body: async (_args, options) => {
|
|
42
|
-
if (typeof options.
|
|
43
|
-
return readJsonFile(options.
|
|
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('--
|
|
70
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
71
71
|
body: async (_args, options) => {
|
|
72
|
-
if (typeof options.
|
|
73
|
-
return readJsonFile(options.
|
|
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('--
|
|
20
|
+
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
21
21
|
body: async (_args, options) => {
|
|
22
|
-
if (typeof options.
|
|
23
|
-
return readJsonFile(options.
|
|
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('--
|
|
213
|
+
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
214
214
|
body: async (_args, options) => {
|
|
215
|
-
if (typeof options.
|
|
216
|
-
return readJsonFile(options.
|
|
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('--
|
|
237
|
+
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
238
238
|
body: async (_args, options) => {
|
|
239
|
-
if (typeof options.
|
|
240
|
-
return readJsonFile(options.
|
|
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('
|
|
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
|
-
.
|
|
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('--
|
|
37
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
37
38
|
body: async (_args, options) => {
|
|
38
|
-
if (typeof options.
|
|
39
|
-
return readJsonFile(options.
|
|
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('--
|
|
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.
|
|
98
|
-
return readJsonFile(options.
|
|
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('--
|
|
183
|
+
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
173
184
|
body: async (_args, options) => {
|
|
174
|
-
if (typeof options.
|
|
175
|
-
return readJsonFile(options.
|
|
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('--
|
|
200
|
+
.option('--input <path>', 'Read JSON request body from file, or - for stdin'),
|
|
190
201
|
body: async (_args, options) => {
|
|
191
|
-
if (typeof options.
|
|
192
|
-
return readJsonFile(options.
|
|
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;
|