@bctrl/cli 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +17 -89
  2. package/dist/api/client.d.ts +1 -0
  3. package/dist/api/client.js +1 -0
  4. package/dist/api/errors.js +2 -2
  5. package/dist/bin/bctrl.js +0 -0
  6. package/dist/commands/{vault → account}/index.d.ts +1 -1
  7. package/dist/commands/account/index.js +21 -0
  8. package/dist/commands/ai/index.js +13 -8
  9. package/dist/commands/api-key/index.js +1 -1
  10. package/dist/commands/browser-extension/index.js +8 -8
  11. package/dist/commands/conversation/index.d.ts +3 -0
  12. package/dist/commands/conversation/index.js +51 -0
  13. package/dist/commands/file/index.js +8 -3
  14. package/dist/commands/run/index.js +23 -292
  15. package/dist/commands/runtime/index.js +32 -453
  16. package/dist/commands/shared/operation.d.ts +3 -0
  17. package/dist/commands/shared/operation.js +20 -7
  18. package/dist/commands/space/index.js +0 -25
  19. package/dist/commands/subaccount/index.js +2 -2
  20. package/dist/commands/tool/index.js +15 -5
  21. package/dist/commands/tool-call/index.js +22 -6
  22. package/dist/commands/toolset/index.js +1 -1
  23. package/dist/commands/view/index.d.ts +3 -0
  24. package/dist/commands/view/index.js +26 -0
  25. package/dist/commands/webhook/index.d.ts +3 -0
  26. package/dist/commands/webhook/index.js +64 -0
  27. package/dist/generated/help.d.ts +4896 -5977
  28. package/dist/generated/help.js +6208 -7532
  29. package/dist/generated/openapi-routes.d.ts +114 -142
  30. package/dist/generated/openapi-routes.js +37 -44
  31. package/dist/generated/openapi-types.d.ts +4451 -5302
  32. package/dist/root.js +8 -2
  33. package/package.json +14 -12
  34. package/dist/commands/vault/index.js +0 -171
package/README.md CHANGED
@@ -1,109 +1,37 @@
1
1
  # BCTRL CLI
2
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
3
  ```bash
8
4
  npm install -g @bctrl/cli
9
- ```
10
-
11
- ## Auth
12
-
13
- The CLI authenticates with either a browser-approved CLI session or a BCTRL API key.
14
-
15
- For a local terminal, start a browser approval and let the CLI poll until you approve it:
16
-
17
- ```bash
18
- bctrl auth login --url --wait
19
- ```
20
-
21
- For agents that need to hand the URL to a human, split the flow into two commands:
22
-
23
- ```bash
24
- bctrl auth login --url
25
- # approve the printed URL
26
5
  bctrl auth login
27
6
  ```
28
7
 
29
- Use an API key for CI or a single shell:
30
-
31
- ```bash
32
- export BCTRL_API_KEY="bctrl_..."
33
- ```
34
-
35
- `BCTRL_API_KEY` takes precedence over stored credentials.
36
-
37
- Check the active auth:
38
-
39
- ```bash
40
- bctrl auth status
41
- ```
42
-
43
- Point contributors at a local or staging stack with `BCTRL_API_URL`:
44
-
45
- ```bash
46
- BCTRL_API_URL=http://127.0.0.1:8787/v1 bctrl auth login --url --wait
47
- ```
48
-
49
- ## Browser Runtime Flow
50
-
51
- ```bash
52
- bctrl space list
53
- bctrl runtime create --space sp_123 --name checkout-test
54
- bctrl runtime start rt_123
55
- bctrl runtime stop rt_123
56
- ```
57
-
58
- ## JSON Input
59
-
60
- Use `--input` for full request bodies:
8
+ Create and start runtimes with `bctrl runtime`. Automation is exposed through a
9
+ small generic Tool interface:
61
10
 
62
11
  ```bash
63
- bctrl runtime create --input runtime.json
64
- ```
65
-
66
- Use `-` to read from stdin:
12
+ bctrl tools call stagehand.act \
13
+ --body '{"runtimeId":"rt_...","instruction":"Click Continue"}'
67
14
 
68
- ```bash
69
- echo '{"type":"browser","spaceId":"sp_123","name":"agent-runtime"}' \
70
- | bctrl runtime create --input -
15
+ bctrl tools start captcha.solve --body '{"runtimeId":"rt_..."}'
16
+ bctrl tool-calls result call_... --params '{"waitSeconds":60}'
71
17
  ```
72
18
 
73
- ## Output
74
-
75
- Print full JSON:
19
+ Use persistent conversations for long-running agents:
76
20
 
77
21
  ```bash
78
- bctrl runtime list --space sp_123 --json
22
+ bctrl conversations create \
23
+ --body '{"agent":"browser-use","runtimeId":"rt_..."}'
24
+ bctrl conversations message conv_... --body '{"text":"Complete checkout"}'
25
+ bctrl conversations stream conv_...
79
26
  ```
80
27
 
81
- Print selected JSON fields:
28
+ Every automation path contributes to one Run:
82
29
 
83
30
  ```bash
84
- bctrl runtime list --space sp_123 --json id,status,name
31
+ bctrl runs trace run_...
32
+ bctrl runs events run_...
33
+ bctrl runs stream run_...
85
34
  ```
86
35
 
87
- Filter with `jq`:
88
-
89
- ```bash
90
- bctrl runtime list --space sp_123 --json --jq '.data[] | select(.status == "active")'
91
- ```
92
-
93
- Render with a template:
94
-
95
- ```bash
96
- bctrl runtime list \
97
- --space sp_123 \
98
- --json \
99
- --template '{{#each data}}{{id}} {{status}}{{newline}}{{/each}}'
100
- ```
101
-
102
- ## Help
103
-
104
- Use command help for exact input and output fields:
105
-
106
- ```bash
107
- bctrl runtime create --help
108
- bctrl runtime start --help
109
- ```
36
+ All JSON-body commands accept inline JSON, `@file.json`, or `-` for stdin through
37
+ `--body`. Use `--json`, `--jq`, or `--template` to control output.
@@ -17,6 +17,7 @@ export type RequestOptions = {
17
17
  query?: Record<string, string | number | boolean | undefined>;
18
18
  idempotencyKey?: string;
19
19
  actingSubaccountId?: string;
20
+ runtimeId?: string;
20
21
  };
21
22
  export type JsonRequestOptions = RequestOptions & {
22
23
  body?: unknown;
@@ -120,6 +120,7 @@ function requestHeaders(token, options, accept) {
120
120
  authorization: `Bearer ${token}`,
121
121
  'user-agent': 'BCTRL CLI',
122
122
  ...(options?.actingSubaccountId ? { 'BCTRL-Subaccount-Id': options.actingSubaccountId } : {}),
123
+ ...(options?.runtimeId ? { 'BCTRL-Runtime-Id': options.runtimeId } : {}),
123
124
  };
124
125
  }
125
126
  function buildUrl(baseUrl, path, query) {
@@ -77,8 +77,8 @@ function suggestionForApiError(error) {
77
77
  return commandWithOptionalSpace('bctrl toolset list', error.details);
78
78
  case 'tool_call.not_found':
79
79
  return 'bctrl tool-call list';
80
- case 'vault_secret.not_found':
81
- return 'bctrl vault list';
80
+ case 'tool.not_found':
81
+ return 'bctrl tools list';
82
82
  case 'browser_extension.not_found':
83
83
  return 'bctrl browser extension list';
84
84
  case 'ai_credential.not_found':
package/dist/bin/bctrl.js CHANGED
File without changes
@@ -1,3 +1,3 @@
1
1
  import { Command } from 'commander';
2
2
  import type { Factory } from '../../factory.js';
3
- export declare function createVaultCommand(factory: Factory): Command;
3
+ export declare function createAccountCommand(factory: Factory): Command;
@@ -0,0 +1,21 @@
1
+ import { Command } from 'commander';
2
+ import { addOutputFlags } from '../shared/output.js';
3
+ import { createOperationJsonBodyCommand, outputFlags, requestOperationAndPrint, } from '../shared/operation.js';
4
+ export function createAccountCommand(factory) {
5
+ const command = new Command('account').description('Manage organization settings');
6
+ command.addCommand(addOutputFlags(new Command('get').description('Get organization settings')).action(async (options) => {
7
+ await requestOperationAndPrint(factory, 'account.get', {
8
+ output: outputFlags(options),
9
+ });
10
+ }));
11
+ command.addCommand(createOperationJsonBodyCommand(factory, {
12
+ operationId: 'account.update',
13
+ name: 'patch',
14
+ description: 'Update organization settings (use --body for branding merge-patch JSON)',
15
+ configure: (cmd) => cmd.option('--dry-run', 'Validate and resolve without persisting'),
16
+ query: (_args, options) => ({
17
+ dryRun: options.dryRun === true,
18
+ }),
19
+ }));
20
+ return command;
21
+ }
@@ -70,14 +70,19 @@ function createAiCredentialWriteCommand(factory, name, operationId, argNames = [
70
70
  name,
71
71
  description: name === 'create' ? 'Create an AI credential' : 'Update an AI credential',
72
72
  argNames,
73
- configure: (cmd) => cmd
74
- .option('--name <name>', 'Credential name')
75
- .option('--provider <provider>', 'Provider key')
76
- .option('--api-key <key>', 'Provider API key')
77
- .option('--status <status>', 'Credential status')
78
- .option('--default-model <model>', 'Default model')
79
- .option('--base-url <url>', 'Custom provider base URL')
80
- .option('--subaccount-id <id>', 'Subaccount scope when using a parent/org key'),
73
+ configure: (cmd) => {
74
+ const configured = cmd
75
+ .option('--name <name>', 'Credential name')
76
+ .option('--provider <provider>', 'Provider key')
77
+ .option('--api-key <key>', 'Provider API key')
78
+ .option('--status <status>', 'Credential status')
79
+ .option('--default-model <model>', 'Default model')
80
+ .option('--base-url <url>', 'Custom provider base URL');
81
+ if (name === 'create') {
82
+ configured.option('--subaccount-id <id>', 'Use this Subaccount account context with an Organization key');
83
+ }
84
+ return configured;
85
+ },
81
86
  body: async (_args, options) => {
82
87
  return {
83
88
  name: options.name,
@@ -23,7 +23,7 @@ export function createApiKeyCommand(factory) {
23
23
  description: 'Create an API key',
24
24
  configure: (cmd) => cmd
25
25
  .option('--name <name>', 'API key name')
26
- .option('--subaccount-id <id>', 'Create a subaccount-scoped key')
26
+ .option('--subaccount-id <id>', 'Create a key confined to this Subaccount')
27
27
  .option('--expires-at <iso>', 'Expiration timestamp'),
28
28
  body: async (_args, options) => {
29
29
  return {
@@ -5,7 +5,7 @@ import { actingSubaccountOption, createOperationDeleteCommand, createOperationJs
5
5
  export function createBrowserExtensionCommand(factory) {
6
6
  const command = new Command('browser-extension').description('Manage browser extensions');
7
7
  command.addCommand(createOperationListCommand(factory, {
8
- operationId: 'browser-extensions.list',
8
+ operationId: 'browser.extensions.list',
9
9
  description: 'List browser extensions',
10
10
  configure: (cmd) => cmd
11
11
  .option('--subaccount-id <id>', 'Filter by subaccount id')
@@ -20,7 +20,7 @@ export function createBrowserExtensionCommand(factory) {
20
20
  actingSubaccountId: actingSubaccountOption(),
21
21
  }));
22
22
  command.addCommand(createOperationViewCommand(factory, {
23
- operationId: 'browser-extensions.get',
23
+ operationId: 'browser.extensions.get',
24
24
  name: 'get',
25
25
  description: 'Get a browser extension',
26
26
  argName: 'extensionId',
@@ -29,9 +29,9 @@ export function createBrowserExtensionCommand(factory) {
29
29
  .description('Upload a browser extension package')
30
30
  .argument('<path>')
31
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) => {
32
+ .option('--subaccount-id <id>', 'Create in this Subaccount account context with an Organization key')).action(async (path, options) => {
33
33
  const file = await readBlob(path);
34
- const result = await uploadOperationFile(factory, 'browser-extensions.upload', {
34
+ const result = await uploadOperationFile(factory, 'browser.extensions.create', {
35
35
  file: file.blob,
36
36
  fileName: file.fileName,
37
37
  fields: {
@@ -42,13 +42,13 @@ export function createBrowserExtensionCommand(factory) {
42
42
  await outputData(factory.io, result, options);
43
43
  }));
44
44
  command.addCommand(createOperationJsonBodyCommand(factory, {
45
- operationId: 'browser-extensions.import',
45
+ operationId: 'browser.extensions.create',
46
46
  name: 'import',
47
47
  description: 'Import a browser extension from a URL',
48
48
  configure: (cmd) => cmd
49
49
  .option('--url <url>', 'Extension URL')
50
50
  .option('--name <name>', 'Display name')
51
- .option('--subaccount-id <id>', 'Create under a subaccount when using a parent/org key'),
51
+ .option('--subaccount-id <id>', 'Create in this Subaccount account context with an Organization key'),
52
52
  body: async (_args, options) => {
53
53
  return {
54
54
  url: options.url,
@@ -58,7 +58,7 @@ export function createBrowserExtensionCommand(factory) {
58
58
  actingSubaccountId: (_args, options) => actingSubaccountOption()(options),
59
59
  }));
60
60
  command.addCommand(createOperationJsonBodyCommand(factory, {
61
- operationId: 'browser-extensions.update',
61
+ operationId: 'browser.extensions.update',
62
62
  name: 'patch',
63
63
  description: 'Update a browser extension',
64
64
  argNames: ['extensionId'],
@@ -69,7 +69,7 @@ export function createBrowserExtensionCommand(factory) {
69
69
  },
70
70
  }));
71
71
  command.addCommand(createOperationDeleteCommand(factory, {
72
- operationId: 'browser-extensions.delete',
72
+ operationId: 'browser.extensions.delete',
73
73
  description: 'Delete a browser extension',
74
74
  argNames: ['extensionId'],
75
75
  }));
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import type { Factory } from '../../factory.js';
3
+ export declare function createConversationCommand(factory: Factory): Command;
@@ -0,0 +1,51 @@
1
+ import { Command } from 'commander';
2
+ import { createOperationJsonBodyCommand, createOperationListCommand, createOperationViewCommand, streamOperationText, } from '../shared/operation.js';
3
+ export function createConversationCommand(factory) {
4
+ const command = new Command('conversations').description('Converse with runtime agents');
5
+ command.addCommand(createOperationListCommand(factory, {
6
+ operationId: 'conversations.list',
7
+ description: 'List conversations',
8
+ }));
9
+ command.addCommand(createOperationJsonBodyCommand(factory, {
10
+ operationId: 'conversations.create',
11
+ name: 'create',
12
+ description: 'Create a conversation',
13
+ }));
14
+ command.addCommand(createOperationViewCommand(factory, {
15
+ operationId: 'conversations.get',
16
+ name: 'get',
17
+ description: 'Get a conversation and its messages',
18
+ argName: 'conversationId',
19
+ }));
20
+ command.addCommand(createOperationJsonBodyCommand(factory, {
21
+ operationId: 'conversations.update',
22
+ name: 'patch',
23
+ description: 'Update the default agent, model, toolset, or title',
24
+ argNames: ['conversationId'],
25
+ }));
26
+ command.addCommand(createOperationJsonBodyCommand(factory, {
27
+ operationId: 'conversations.messages.create',
28
+ name: 'message',
29
+ description: 'Send a message and start an agent turn',
30
+ argNames: ['conversationId'],
31
+ }));
32
+ command.addCommand(createOperationJsonBodyCommand(factory, {
33
+ operationId: 'conversations.cancel',
34
+ name: 'cancel',
35
+ description: 'Cancel the active agent turn',
36
+ argNames: ['conversationId'],
37
+ }));
38
+ command.addCommand(new Command('stream')
39
+ .description('Stream normalized conversation events')
40
+ .argument('<conversationId>')
41
+ .option('--after <cursor>', 'Resume after a stream cursor')
42
+ .action(async (conversationId, options) => {
43
+ const stream = await streamOperationText(factory, 'conversations.stream', {
44
+ pathParams: { conversationId },
45
+ query: { after: options.after },
46
+ });
47
+ for await (const chunk of stream)
48
+ factory.io.writeOut(chunk);
49
+ }));
50
+ return command;
51
+ }
@@ -10,21 +10,26 @@ export function createFileCommand(factory) {
10
10
  description: 'List files',
11
11
  configure: (cmd) => cmd
12
12
  .option('--space <id>', 'Filter by space id')
13
+ .option('--run <id>', 'Filter by run id')
14
+ .option('--runtime <id>', 'Filter by runtime id')
15
+ .option('--type <type...>', 'Filter by file artifact type')
13
16
  .option('--source <source>', 'Filter by file source')
14
17
  .option('--prefix <path>', 'Filter by path prefix')
15
18
  .option('--folders', 'Directory view: direct files plus subfolder rollups')
16
19
  .option('--created-after <iso>', 'Only files created after this timestamp')
17
- .option('--query <text>', 'Search query')
20
+ .option('--q <text>', 'Search query')
18
21
  .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
19
22
  .option('--cursor <cursor>', 'Pagination cursor'),
20
23
  query: (options) => ({
21
24
  spaceId: typeof options.space === 'string' ? options.space : undefined,
25
+ runId: typeof options.run === 'string' ? options.run : undefined,
26
+ runtimeId: typeof options.runtime === 'string' ? options.runtime : undefined,
27
+ type: Array.isArray(options.type) ? options.type : undefined,
22
28
  source: typeof options.source === 'string' ? options.source : undefined,
23
29
  prefix: typeof options.prefix === 'string' ? options.prefix : undefined,
24
30
  include: options.folders === true ? 'folders' : undefined,
25
31
  createdAfter: typeof options.createdAfter === 'string' ? options.createdAfter : undefined,
26
- // The public query param is `q` (was sent as `query`, which the API ignores).
27
- q: typeof options.query === 'string' ? options.query : undefined,
32
+ q: typeof options.q === 'string' ? options.q : undefined,
28
33
  limit: typeof options.limit === 'number' ? options.limit : undefined,
29
34
  cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
30
35
  }),