@bctrl/cli 0.1.2 → 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.
- package/README.md +95 -97
- package/dist/api/auth.d.ts +5 -6
- package/dist/api/auth.js +4 -5
- package/dist/api/client.d.ts +1 -0
- package/dist/api/client.js +3 -0
- package/dist/api/errors.js +2 -2
- package/dist/commands/ai/index.js +59 -102
- package/dist/commands/{browser → api-key}/index.d.ts +1 -1
- package/dist/commands/api-key/index.js +47 -0
- package/dist/commands/auth/login.js +2 -1
- package/dist/commands/auth/status.js +5 -0
- package/dist/commands/browser-extension/index.d.ts +3 -0
- package/dist/commands/browser-extension/index.js +86 -0
- package/dist/commands/file/index.js +9 -3
- package/dist/commands/{invocation → help}/index.d.ts +1 -1
- package/dist/commands/help/index.js +17 -0
- package/dist/commands/proxy/index.d.ts +3 -0
- package/dist/commands/proxy/index.js +66 -0
- package/dist/commands/run/index.js +205 -155
- package/dist/commands/runtime/index.js +265 -116
- package/dist/commands/shared/help.js +1 -1
- package/dist/commands/shared/rest.d.ts +1 -0
- package/dist/commands/shared/rest.js +14 -6
- package/dist/commands/space/index.js +17 -32
- package/dist/commands/space/list.d.ts +1 -11
- package/dist/commands/space/list.js +14 -23
- package/dist/commands/subaccount/index.js +29 -80
- package/dist/commands/tool/index.js +15 -7
- package/dist/commands/tool-call/index.js +6 -1
- package/dist/commands/toolset/index.js +6 -3
- package/dist/commands/usage/index.d.ts +3 -0
- package/dist/commands/usage/index.js +13 -0
- package/dist/commands/vault/index.js +104 -25
- package/dist/config/auth-store.d.ts +10 -12
- package/dist/config/auth-store.js +4 -5
- package/dist/factory.js +1 -1
- package/dist/generated/help.d.ts +125 -110
- package/dist/generated/help.js +175 -141
- package/dist/root.js +10 -4
- package/package.json +47 -47
- package/dist/commands/browser/index.js +0 -53
- package/dist/commands/invocation/index.js +0 -36
package/README.md
CHANGED
|
@@ -1,97 +1,95 @@
|
|
|
1
|
-
# BCTRL CLI
|
|
2
|
-
|
|
3
|
-
CLI for controlling BCTRL runtimes, files, runs, vault secrets, tools, and account resources from scripts or AI agents.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
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
|
-
```
|
|
34
|
-
|
|
35
|
-
## Browser Runtime Flow
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
bctrl space list
|
|
39
|
-
bctrl runtime create --space sp_123 --name checkout-test
|
|
40
|
-
bctrl runtime start rt_123
|
|
41
|
-
bctrl runtime
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
--
|
|
85
|
-
--
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
bctrl runtime
|
|
95
|
-
|
|
96
|
-
bctrl runtime connection create --help
|
|
97
|
-
```
|
|
1
|
+
# BCTRL CLI
|
|
2
|
+
|
|
3
|
+
CLI for controlling BCTRL runtimes, files, runs, vault secrets, tools, and account resources from scripts or AI agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
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
|
+
```
|
|
34
|
+
|
|
35
|
+
## Browser Runtime Flow
|
|
36
|
+
|
|
37
|
+
```bash
|
|
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
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## JSON Input
|
|
45
|
+
|
|
46
|
+
Use `--input` for full request bodies:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bctrl runtime create --input runtime.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Use `-` to read from stdin:
|
|
53
|
+
|
|
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
|
+
```
|
|
87
|
+
|
|
88
|
+
## Help
|
|
89
|
+
|
|
90
|
+
Use command help for exact input and output fields:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bctrl runtime create --help
|
|
94
|
+
bctrl runtime start --help
|
|
95
|
+
```
|
package/dist/api/auth.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const AuthWhoamiSchema: z.ZodObject<{
|
|
3
|
-
authenticated: z.ZodLiteral<true>;
|
|
4
3
|
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
scope: z.ZodEnum<{
|
|
5
|
+
organization: "organization";
|
|
7
6
|
subaccount: "subaccount";
|
|
8
7
|
}>;
|
|
9
8
|
organizationId: z.ZodString;
|
|
10
|
-
subaccountId: z.
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
subaccountId: z.ZodNullable<z.ZodString>;
|
|
10
|
+
defaultSpaceId: z.ZodString;
|
|
11
|
+
keyId: z.ZodString;
|
|
13
12
|
plan: z.ZodString;
|
|
14
13
|
}, z.core.$strict>;
|
|
15
14
|
export type AuthWhoami = z.infer<typeof AuthWhoamiSchema>;
|
package/dist/api/auth.js
CHANGED
|
@@ -3,13 +3,12 @@ import { CliError } from '../runtime/errors.js';
|
|
|
3
3
|
import { apiErrorFromResponse } from './errors.js';
|
|
4
4
|
export const AuthWhoamiSchema = z
|
|
5
5
|
.object({
|
|
6
|
-
authenticated: z.literal(true),
|
|
7
6
|
email: z.string().email().nullable().optional(),
|
|
8
|
-
|
|
7
|
+
scope: z.enum(['organization', 'subaccount']),
|
|
9
8
|
organizationId: z.string().min(1),
|
|
10
|
-
subaccountId: z.string().min(1).nullable()
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
subaccountId: z.string().min(1).nullable(),
|
|
10
|
+
defaultSpaceId: z.string().min(1),
|
|
11
|
+
keyId: z.string().min(1),
|
|
13
12
|
plan: z.string().min(1),
|
|
14
13
|
})
|
|
15
14
|
.strict();
|
package/dist/api/client.d.ts
CHANGED
package/dist/api/client.js
CHANGED
|
@@ -21,6 +21,9 @@ async function requestJson(config, method, path, options) {
|
|
|
21
21
|
authorization: `Bearer ${config.activeToken.token}`,
|
|
22
22
|
'user-agent': 'BCTRL CLI',
|
|
23
23
|
};
|
|
24
|
+
if (options?.idempotencyKey) {
|
|
25
|
+
headers['idempotency-key'] = options.idempotencyKey;
|
|
26
|
+
}
|
|
24
27
|
let body;
|
|
25
28
|
if (options && 'body' in options && options.body !== undefined) {
|
|
26
29
|
headers['content-type'] = 'application/json';
|
package/dist/api/errors.js
CHANGED
|
@@ -73,8 +73,8 @@ function suggestionForApiError(error) {
|
|
|
73
73
|
return 'bctrl vault list';
|
|
74
74
|
case 'browser_extension.not_found':
|
|
75
75
|
return 'bctrl browser extension list';
|
|
76
|
-
case '
|
|
77
|
-
return 'bctrl ai
|
|
76
|
+
case 'ai_credential.not_found':
|
|
77
|
+
return 'bctrl ai credentials list';
|
|
78
78
|
case 'proxy.not_found':
|
|
79
79
|
return 'bctrl ai proxy list';
|
|
80
80
|
case 'subaccount.not_found':
|
|
@@ -3,16 +3,35 @@ import { readJsonFile } from '../shared/io.js';
|
|
|
3
3
|
import { addOutputFlags } from '../shared/output.js';
|
|
4
4
|
import { createDeleteCommand, createJsonBodyCommand, createListCommand, createViewCommand, requestAndPrint, } from '../shared/rest.js';
|
|
5
5
|
export function createAiCommand(factory) {
|
|
6
|
-
const command = new Command('ai').description('Manage AI
|
|
7
|
-
command.addCommand(
|
|
8
|
-
command.addCommand(
|
|
6
|
+
const command = new Command('ai').description('Manage AI models and credentials');
|
|
7
|
+
command.addCommand(createAiModelsCommand(factory));
|
|
8
|
+
command.addCommand(createAiCredentialsCommand(factory));
|
|
9
9
|
return command;
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
const command = new Command('
|
|
11
|
+
function createAiModelsCommand(factory) {
|
|
12
|
+
const command = new Command('models').description('Discover AI models');
|
|
13
13
|
command.addCommand(createListCommand(factory, {
|
|
14
|
-
description: 'List AI
|
|
15
|
-
path: '/ai
|
|
14
|
+
description: 'List AI models',
|
|
15
|
+
path: '/ai/models',
|
|
16
|
+
configure: (cmd) => cmd
|
|
17
|
+
.option('--provider <provider>', 'Filter by provider')
|
|
18
|
+
.option('--status <status>', 'Filter by support status')
|
|
19
|
+
.option('--managed <value>', 'Filter by managed availability')
|
|
20
|
+
.option('--engine <engine>', 'Filter by engine: stagehand or browserUse'),
|
|
21
|
+
query: (options) => ({
|
|
22
|
+
provider: typeof options.provider === 'string' ? options.provider : undefined,
|
|
23
|
+
status: typeof options.status === 'string' ? options.status : undefined,
|
|
24
|
+
managed: typeof options.managed === 'string' ? options.managed : undefined,
|
|
25
|
+
engine: typeof options.engine === 'string' ? options.engine : undefined,
|
|
26
|
+
}),
|
|
27
|
+
}));
|
|
28
|
+
return command;
|
|
29
|
+
}
|
|
30
|
+
function createAiCredentialsCommand(factory) {
|
|
31
|
+
const command = new Command('credentials').description('Manage BYOK AI credentials');
|
|
32
|
+
command.addCommand(createListCommand(factory, {
|
|
33
|
+
description: 'List AI credentials',
|
|
34
|
+
path: '/ai/credentials',
|
|
16
35
|
configure: (cmd) => cmd
|
|
17
36
|
.option('--provider <provider>', 'Filter by provider')
|
|
18
37
|
.option('--name <name>', 'Filter by name')
|
|
@@ -24,22 +43,40 @@ function createAiProviderCommand(factory) {
|
|
|
24
43
|
}),
|
|
25
44
|
}));
|
|
26
45
|
command.addCommand(createViewCommand(factory, {
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
name: 'get',
|
|
47
|
+
description: 'Get an AI credential',
|
|
48
|
+
path: '/ai/credentials/{credentialId}',
|
|
49
|
+
argName: 'credentialId',
|
|
50
|
+
}));
|
|
51
|
+
command.addCommand(createAiCredentialWriteCommand(factory, 'create', 'post', '/ai/credentials'));
|
|
52
|
+
command.addCommand(createAiCredentialWriteCommand(factory, 'patch', 'patch', '/ai/credentials/{credentialId}', [
|
|
53
|
+
'credentialId',
|
|
54
|
+
]));
|
|
55
|
+
command.addCommand(addOutputFlags(new Command('test').description('Test an AI credential').argument('<credentialId>')).action(async (credentialId, options) => {
|
|
56
|
+
await requestAndPrint(factory, 'post', `/ai/credentials/${encodeURIComponent(credentialId)}/test`, { output: options });
|
|
29
57
|
}));
|
|
30
|
-
command.addCommand(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
command.addCommand(createDeleteCommand(factory, {
|
|
59
|
+
description: 'Delete an AI credential',
|
|
60
|
+
path: '/ai/credentials/{credentialId}',
|
|
61
|
+
argNames: ['credentialId'],
|
|
62
|
+
}));
|
|
63
|
+
return command;
|
|
64
|
+
}
|
|
65
|
+
function createAiCredentialWriteCommand(factory, name, method, path, argNames = []) {
|
|
66
|
+
return createJsonBodyCommand(factory, {
|
|
67
|
+
name,
|
|
68
|
+
description: name === 'create' ? 'Create an AI credential' : 'Update an AI credential',
|
|
69
|
+
method,
|
|
70
|
+
path,
|
|
71
|
+
argNames,
|
|
35
72
|
configure: (cmd) => cmd
|
|
36
|
-
.option('--name <name>', '
|
|
73
|
+
.option('--name <name>', 'Credential name')
|
|
37
74
|
.option('--provider <provider>', 'Provider key')
|
|
38
75
|
.option('--api-key <key>', 'Provider API key')
|
|
39
|
-
.option('--status <status>', '
|
|
76
|
+
.option('--status <status>', 'Credential status')
|
|
40
77
|
.option('--default-model <model>', 'Default model')
|
|
41
|
-
.option('--base-url <url>', '
|
|
42
|
-
.option('--subaccount <id>', 'Subaccount scope')
|
|
78
|
+
.option('--base-url <url>', 'Custom provider base URL')
|
|
79
|
+
.option('--subaccount-id <id>', 'Subaccount scope when using a parent/org key')
|
|
43
80
|
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
44
81
|
body: async (_args, options) => {
|
|
45
82
|
if (typeof options.input === 'string')
|
|
@@ -49,90 +86,10 @@ function createAiProviderCommand(factory) {
|
|
|
49
86
|
provider: options.provider,
|
|
50
87
|
apiKey: options.apiKey,
|
|
51
88
|
status: options.status,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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,
|
|
89
|
+
subaccountId: options.subaccountId,
|
|
90
|
+
defaultModel: options.defaultModel,
|
|
91
|
+
baseUrl: options.baseUrl,
|
|
61
92
|
};
|
|
62
93
|
},
|
|
63
|
-
})
|
|
64
|
-
command.addCommand(createJsonBodyCommand(factory, {
|
|
65
|
-
name: 'edit',
|
|
66
|
-
description: 'Edit an AI provider',
|
|
67
|
-
method: 'patch',
|
|
68
|
-
path: '/ai-providers/{id}',
|
|
69
|
-
argNames: ['id'],
|
|
70
|
-
configure: (cmd) => cmd
|
|
71
|
-
.option('--name <name>', 'Provider name')
|
|
72
|
-
.option('--api-key <key>', 'Provider API key')
|
|
73
|
-
.option('--status <status>', 'Provider status')
|
|
74
|
-
.option('--default-model <model>', 'Default model')
|
|
75
|
-
.option('--base-url <url>', 'OpenAI-compatible provider base URL')
|
|
76
|
-
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
77
|
-
body: async (_args, options) => {
|
|
78
|
-
if (typeof options.input === 'string')
|
|
79
|
-
return readJsonFile(options.input);
|
|
80
|
-
return {
|
|
81
|
-
name: options.name,
|
|
82
|
-
apiKey: options.apiKey,
|
|
83
|
-
status: options.status,
|
|
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,
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
}));
|
|
93
|
-
command.addCommand(addOutputFlags(new Command('test').description('Test an AI provider').argument('<id>')).action(async (id, options) => {
|
|
94
|
-
await requestAndPrint(factory, 'post', `/ai-providers/${encodeURIComponent(id)}/test`, {
|
|
95
|
-
output: options,
|
|
96
|
-
});
|
|
97
|
-
}));
|
|
98
|
-
command.addCommand(createDeleteCommand(factory, {
|
|
99
|
-
description: 'Delete an AI provider',
|
|
100
|
-
path: '/ai-providers/{id}',
|
|
101
|
-
}));
|
|
102
|
-
return command;
|
|
103
|
-
}
|
|
104
|
-
function createAiProxyCommand(factory) {
|
|
105
|
-
const command = new Command('proxy').description('Manage AI/browser proxies');
|
|
106
|
-
command.addCommand(createListCommand(factory, {
|
|
107
|
-
description: 'List proxies',
|
|
108
|
-
path: '/proxies',
|
|
109
|
-
configure: (cmd) => cmd,
|
|
110
|
-
}));
|
|
111
|
-
command.addCommand(createViewCommand(factory, {
|
|
112
|
-
description: 'View a proxy',
|
|
113
|
-
path: '/proxies/{id}',
|
|
114
|
-
}));
|
|
115
|
-
command.addCommand(createJsonBodyCommand(factory, {
|
|
116
|
-
name: 'create',
|
|
117
|
-
description: 'Create a proxy',
|
|
118
|
-
method: 'post',
|
|
119
|
-
path: '/proxies',
|
|
120
|
-
}));
|
|
121
|
-
command.addCommand(createJsonBodyCommand(factory, {
|
|
122
|
-
name: 'edit',
|
|
123
|
-
description: 'Edit a proxy',
|
|
124
|
-
method: 'patch',
|
|
125
|
-
path: '/proxies/{id}',
|
|
126
|
-
argNames: ['id'],
|
|
127
|
-
}));
|
|
128
|
-
command.addCommand(addOutputFlags(new Command('test').description('Test a proxy').argument('<id>')).action(async (id, options) => {
|
|
129
|
-
await requestAndPrint(factory, 'post', `/proxies/${encodeURIComponent(id)}/test`, {
|
|
130
|
-
output: options,
|
|
131
|
-
});
|
|
132
|
-
}));
|
|
133
|
-
command.addCommand(createDeleteCommand(factory, {
|
|
134
|
-
description: 'Delete a proxy',
|
|
135
|
-
path: '/proxies/{id}',
|
|
136
|
-
}));
|
|
137
|
-
return command;
|
|
94
|
+
});
|
|
138
95
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readJsonFile } from '../shared/io.js';
|
|
3
|
+
import { createDeleteCommand, createJsonBodyCommand, createListCommand, } from '../shared/rest.js';
|
|
4
|
+
export function createApiKeyCommand(factory) {
|
|
5
|
+
const command = new Command('api-key').description('Manage API keys');
|
|
6
|
+
command.addCommand(createListCommand(factory, {
|
|
7
|
+
description: 'List API keys',
|
|
8
|
+
path: '/api-keys',
|
|
9
|
+
configure: (cmd) => cmd
|
|
10
|
+
.option('--subaccount-id <id>', 'Filter by subaccount id')
|
|
11
|
+
.option('--kind <kind>', 'Filter by key kind: organization or subaccount')
|
|
12
|
+
.option('-L, --limit <number>', 'Maximum number of results to return')
|
|
13
|
+
.option('--cursor <cursor>', 'Pagination cursor'),
|
|
14
|
+
query: (options) => ({
|
|
15
|
+
subaccountId: typeof options.subaccountId === 'string' ? options.subaccountId : undefined,
|
|
16
|
+
keyKind: typeof options.kind === 'string' ? options.kind : undefined,
|
|
17
|
+
limit: typeof options.limit === 'string' ? Number(options.limit) : undefined,
|
|
18
|
+
cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
command.addCommand(createJsonBodyCommand(factory, {
|
|
22
|
+
name: 'create',
|
|
23
|
+
description: 'Create an API key',
|
|
24
|
+
method: 'post',
|
|
25
|
+
path: '/api-keys',
|
|
26
|
+
configure: (cmd) => cmd
|
|
27
|
+
.option('--name <name>', 'API key name')
|
|
28
|
+
.option('--subaccount-id <id>', 'Create a subaccount-scoped key')
|
|
29
|
+
.option('--expires-at <iso>', 'Expiration timestamp')
|
|
30
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
31
|
+
body: async (_args, options) => {
|
|
32
|
+
if (typeof options.input === 'string')
|
|
33
|
+
return readJsonFile(options.input);
|
|
34
|
+
return {
|
|
35
|
+
name: options.name,
|
|
36
|
+
subaccountId: options.subaccountId,
|
|
37
|
+
expiresAt: options.expiresAt,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
command.addCommand(createDeleteCommand(factory, {
|
|
42
|
+
description: 'Delete an API key',
|
|
43
|
+
path: '/api-keys/{keyId}',
|
|
44
|
+
argNames: ['keyId'],
|
|
45
|
+
}));
|
|
46
|
+
return command;
|
|
47
|
+
}
|
|
@@ -36,11 +36,12 @@ export async function authLoginRun(options) {
|
|
|
36
36
|
}
|
|
37
37
|
options.io.writeErr(`Logged in to ${config.apiBaseUrl}\n`);
|
|
38
38
|
options.io.writeErr(`Credentials saved to ${path}\n`);
|
|
39
|
-
options.io.writeErr(`
|
|
39
|
+
options.io.writeErr(`Scope: ${whoami.scope}\n`);
|
|
40
40
|
options.io.writeErr(`Organization: ${whoami.organizationId}\n`);
|
|
41
41
|
if (whoami.subaccountId) {
|
|
42
42
|
options.io.writeErr(`Subaccount: ${whoami.subaccountId}\n`);
|
|
43
43
|
}
|
|
44
|
+
options.io.writeErr(`Default space: ${whoami.defaultSpaceId}\n`);
|
|
44
45
|
if (config.activeToken?.source === 'BCTRL_API_KEY') {
|
|
45
46
|
options.io.writeErr('BCTRL_API_KEY is set and will take precedence over stored credentials.\n');
|
|
46
47
|
}
|
|
@@ -31,6 +31,7 @@ export async function authStatusRun(options) {
|
|
|
31
31
|
const validateToken = options.validateToken ?? validateAuthToken;
|
|
32
32
|
const whoami = await validateToken(config.apiBaseUrl, config.activeToken.token);
|
|
33
33
|
const status = {
|
|
34
|
+
authenticated: true,
|
|
34
35
|
apiBaseUrl: config.apiBaseUrl,
|
|
35
36
|
tokenSource: config.activeToken.source,
|
|
36
37
|
...whoami,
|
|
@@ -60,6 +61,10 @@ function writeHumanAuthStatus(io, status) {
|
|
|
60
61
|
io.writeOut([
|
|
61
62
|
'Authenticated: yes',
|
|
62
63
|
`Email: ${status.email ?? '-'}`,
|
|
64
|
+
`Scope: ${status.scope}`,
|
|
65
|
+
`Organization: ${status.organizationId}`,
|
|
66
|
+
...(status.subaccountId ? [`Subaccount: ${status.subaccountId}`] : []),
|
|
67
|
+
`Default space: ${status.defaultSpaceId}`,
|
|
63
68
|
`Plan: ${status.plan}`,
|
|
64
69
|
`Auth: ${formatTokenSource(status.tokenSource)}`,
|
|
65
70
|
`API: ${status.apiBaseUrl}`,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readBlob, readJsonFile } from '../shared/io.js';
|
|
3
|
+
import { addOutputFlags, outputData } from '../shared/output.js';
|
|
4
|
+
import { createDeleteCommand, createJsonBodyCommand, createListCommand, createViewCommand, } from '../shared/rest.js';
|
|
5
|
+
export function createBrowserExtensionCommand(factory) {
|
|
6
|
+
const command = new Command('browser-extension').description('Manage browser extensions');
|
|
7
|
+
command.addCommand(createListCommand(factory, {
|
|
8
|
+
description: 'List browser extensions',
|
|
9
|
+
path: '/browser-extensions',
|
|
10
|
+
configure: (cmd) => cmd
|
|
11
|
+
.option('--subaccount-id <id>', 'Filter by subaccount id')
|
|
12
|
+
.option('--q <text>', 'Search query')
|
|
13
|
+
.option('--format <format>', 'Filter by format')
|
|
14
|
+
.option('--source <source>', 'Filter by source'),
|
|
15
|
+
query: (options) => ({
|
|
16
|
+
subaccountId: typeof options.subaccountId === 'string' ? options.subaccountId : undefined,
|
|
17
|
+
q: typeof options.q === 'string' ? options.q : undefined,
|
|
18
|
+
format: typeof options.format === 'string' ? options.format : undefined,
|
|
19
|
+
source: typeof options.source === 'string' ? options.source : undefined,
|
|
20
|
+
}),
|
|
21
|
+
}));
|
|
22
|
+
command.addCommand(createViewCommand(factory, {
|
|
23
|
+
name: 'get',
|
|
24
|
+
description: 'Get a browser extension',
|
|
25
|
+
path: '/browser-extensions/{extensionId}',
|
|
26
|
+
argName: 'extensionId',
|
|
27
|
+
}));
|
|
28
|
+
command.addCommand(addOutputFlags(new Command('upload')
|
|
29
|
+
.description('Upload a browser extension package')
|
|
30
|
+
.argument('<path>')
|
|
31
|
+
.option('--name <name>', 'Display name')
|
|
32
|
+
.option('--subaccount-id <id>', 'Create under a subaccount when using a parent/org key')).action(async (path, options) => {
|
|
33
|
+
const client = await factory.apiClient();
|
|
34
|
+
const file = await readBlob(path);
|
|
35
|
+
const result = await client.uploadFile('/browser-extensions/upload', {
|
|
36
|
+
file: file.blob,
|
|
37
|
+
fileName: file.fileName,
|
|
38
|
+
fields: {
|
|
39
|
+
...(options.name ? { name: options.name } : {}),
|
|
40
|
+
...(options.subaccountId ? { subaccountId: options.subaccountId } : {}),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
await outputData(factory.io, result, options);
|
|
44
|
+
}));
|
|
45
|
+
command.addCommand(createJsonBodyCommand(factory, {
|
|
46
|
+
name: 'import',
|
|
47
|
+
description: 'Import a browser extension from a URL',
|
|
48
|
+
method: 'post',
|
|
49
|
+
path: '/browser-extensions/import',
|
|
50
|
+
configure: (cmd) => cmd
|
|
51
|
+
.option('--url <url>', 'Extension URL')
|
|
52
|
+
.option('--name <name>', 'Display name')
|
|
53
|
+
.option('--subaccount-id <id>', 'Create under a subaccount when using a parent/org key')
|
|
54
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
55
|
+
body: async (_args, options) => {
|
|
56
|
+
if (typeof options.input === 'string')
|
|
57
|
+
return readJsonFile(options.input);
|
|
58
|
+
return {
|
|
59
|
+
url: options.url,
|
|
60
|
+
name: options.name,
|
|
61
|
+
subaccountId: options.subaccountId,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
command.addCommand(createJsonBodyCommand(factory, {
|
|
66
|
+
name: 'patch',
|
|
67
|
+
description: 'Update a browser extension',
|
|
68
|
+
method: 'patch',
|
|
69
|
+
path: '/browser-extensions/{extensionId}',
|
|
70
|
+
argNames: ['extensionId'],
|
|
71
|
+
configure: (cmd) => cmd
|
|
72
|
+
.option('--name <name>', 'Display name')
|
|
73
|
+
.option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
|
|
74
|
+
body: async (_args, options) => {
|
|
75
|
+
if (typeof options.input === 'string')
|
|
76
|
+
return readJsonFile(options.input);
|
|
77
|
+
return { name: options.name };
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
command.addCommand(createDeleteCommand(factory, {
|
|
81
|
+
description: 'Delete a browser extension',
|
|
82
|
+
path: '/browser-extensions/{extensionId}',
|
|
83
|
+
argNames: ['extensionId'],
|
|
84
|
+
}));
|
|
85
|
+
return command;
|
|
86
|
+
}
|