@bctrl/cli 0.1.6 → 0.1.7

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 (62) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +109 -95
  3. package/dist/api/auth.d.ts +10 -1
  4. package/dist/api/auth.js +10 -1
  5. package/dist/api/client.d.ts +2 -1
  6. package/dist/api/client.js +37 -37
  7. package/dist/api/device-auth.d.ts +39 -0
  8. package/dist/api/device-auth.js +93 -0
  9. package/dist/api/errors.d.ts +1 -0
  10. package/dist/api/errors.js +8 -0
  11. package/dist/bin/bctrl.js +0 -0
  12. package/dist/commands/ai/index.js +10 -11
  13. package/dist/commands/api-key/index.js +3 -8
  14. package/dist/commands/auth/device-login.d.ts +6 -0
  15. package/dist/commands/auth/device-login.js +19 -0
  16. package/dist/commands/auth/login.d.ts +11 -0
  17. package/dist/commands/auth/login.js +140 -20
  18. package/dist/commands/auth/logout.d.ts +2 -0
  19. package/dist/commands/auth/logout.js +19 -2
  20. package/dist/commands/auth/status.d.ts +1 -0
  21. package/dist/commands/auth/status.js +22 -5
  22. package/dist/commands/auth/token.js +6 -0
  23. package/dist/commands/browser-extension/index.js +7 -15
  24. package/dist/commands/file/index.js +8 -7
  25. package/dist/commands/help/index.js +5 -4
  26. package/dist/commands/notification-recipient/index.d.ts +3 -0
  27. package/dist/commands/notification-recipient/index.js +72 -0
  28. package/dist/commands/proxy/index.js +6 -4
  29. package/dist/commands/run/index.js +31 -32
  30. package/dist/commands/runtime/index.js +185 -106
  31. package/dist/commands/shared/help.d.ts +26 -3
  32. package/dist/commands/shared/help.js +87 -23
  33. package/dist/commands/shared/io.d.ts +1 -0
  34. package/dist/commands/shared/io.js +4 -2
  35. package/dist/commands/shared/operation.d.ts +50 -3
  36. package/dist/commands/shared/operation.js +171 -25
  37. package/dist/commands/shared/output.js +3 -7
  38. package/dist/commands/space/index.js +35 -32
  39. package/dist/commands/space/list.js +6 -5
  40. package/dist/commands/subaccount/index.js +12 -14
  41. package/dist/commands/usage/index.js +9 -5
  42. package/dist/commands/vault/index.js +15 -21
  43. package/dist/config/auth-store.d.ts +75 -9
  44. package/dist/config/auth-store.js +133 -27
  45. package/dist/config/config.d.ts +2 -2
  46. package/dist/config/config.js +9 -17
  47. package/dist/config/paths.d.ts +16 -0
  48. package/dist/config/paths.js +34 -0
  49. package/dist/config/pending-auth.d.ts +19 -0
  50. package/dist/config/pending-auth.js +72 -0
  51. package/dist/config/secret-store.d.ts +24 -0
  52. package/dist/config/secret-store.js +123 -0
  53. package/dist/factory.js +2 -1
  54. package/dist/generated/help.d.ts +7424 -2768
  55. package/dist/generated/help.js +10116 -4014
  56. package/dist/generated/openapi-routes.d.ts +52 -8
  57. package/dist/generated/openapi-routes.js +20 -9
  58. package/dist/generated/openapi-types.d.ts +2688 -883
  59. package/dist/root.js +2 -0
  60. package/dist/version.d.ts +1 -0
  61. package/dist/version.js +4 -0
  62. package/package.json +56 -47
@@ -1,10 +1,11 @@
1
- import type { Command } from 'commander';
2
- import { type CliHelpCommandId } from '../../generated/help.js';
1
+ import type { Command } from "commander";
2
+ import { type CliHelpCommandId } from "../../generated/help.js";
3
3
  export type CommandHelpField = {
4
4
  name: string;
5
5
  type: string;
6
6
  required?: boolean;
7
7
  description?: string;
8
+ values?: string[];
8
9
  };
9
10
  export type CommandHelpFlag = {
10
11
  name: string;
@@ -12,10 +13,32 @@ export type CommandHelpFlag = {
12
13
  description: string;
13
14
  mapsTo?: string;
14
15
  };
16
+ export type CommandHelpDocLink = {
17
+ title: string;
18
+ url: string;
19
+ markdownUrl?: string;
20
+ };
15
21
  export type CommandHelpSpec = {
16
22
  purpose: string;
23
+ docs?: CommandHelpDocLink[];
17
24
  flags?: CommandHelpFlag[];
18
- input?: CommandHelpField[];
25
+ inputs?: {
26
+ path?: CommandHelpField[];
27
+ query?: CommandHelpField[];
28
+ headers?: CommandHelpField[];
29
+ body?: {
30
+ schema?: string;
31
+ fields?: CommandHelpField[];
32
+ discriminator?: {
33
+ property: string;
34
+ variants: Array<{
35
+ value: string;
36
+ schema?: string;
37
+ summary?: string;
38
+ }>;
39
+ };
40
+ };
41
+ };
19
42
  output?: CommandHelpField[];
20
43
  examples?: string[];
21
44
  next?: string[];
@@ -1,13 +1,15 @@
1
- import { CLI_HELP_COMMANDS } from '../../generated/help.js';
1
+ import { CLI_HELP_COMMANDS, } from "../../generated/help.js";
2
2
  export function addStructuredHelp(command, spec) {
3
- command.addHelpText('after', `\n${renderStructuredHelp(spec)}`);
3
+ command.addHelpText("after", `\n${renderStructuredHelp(spec)}`);
4
4
  return command;
5
5
  }
6
6
  export function addCliHelp(command, commandId) {
7
7
  return addStructuredHelp(command, toCommandHelpSpec(CLI_HELP_COMMANDS[commandId]));
8
8
  }
9
9
  export function addCliOperationHelp(command, operationId) {
10
- return isCliHelpCommandId(operationId) ? addCliHelp(command, operationId) : command;
10
+ return isCliHelpCommandId(operationId)
11
+ ? addCliHelp(command, operationId)
12
+ : command;
11
13
  }
12
14
  function isCliHelpCommandId(value) {
13
15
  return value in CLI_HELP_COMMANDS;
@@ -16,61 +18,123 @@ function toCommandHelpSpec(help) {
16
18
  const raw = help;
17
19
  return {
18
20
  purpose: raw.summary,
21
+ docs: raw.docs?.map((doc) => ({ ...doc })),
19
22
  flags: raw.cli?.flags?.map((flag) => ({ ...flag })),
20
- input: raw.input?.fields.map((field) => ({ ...field })),
23
+ inputs: cloneInputs(raw.inputs),
21
24
  output: raw.output?.fields.map((field) => ({ ...field })),
22
- examples: raw.examples?.map(formatHelpItem).filter((value) => Boolean(value)),
23
- next: raw.next?.map(formatHelpItem).filter((value) => Boolean(value)),
25
+ examples: raw.examples
26
+ ?.map(formatHelpItem)
27
+ .filter((value) => Boolean(value)),
28
+ next: raw.next
29
+ ?.map(formatHelpItem)
30
+ .filter((value) => Boolean(value)),
24
31
  };
25
32
  }
26
33
  function formatHelpItem(value) {
27
- if (typeof value === 'string')
34
+ if (typeof value === "string")
28
35
  return value;
29
- if (!value || typeof value !== 'object' || Array.isArray(value))
36
+ if (!value || typeof value !== "object" || Array.isArray(value))
30
37
  return undefined;
31
38
  const record = value;
32
- if (typeof record.command === 'string')
39
+ if (typeof record.command === "string")
33
40
  return record.command;
34
- if (typeof record.topic === 'string')
41
+ if (typeof record.topic === "string")
35
42
  return record.topic;
36
43
  return undefined;
37
44
  }
38
45
  function renderStructuredHelp(spec) {
39
46
  const sections = [
40
47
  renderPurpose(spec.purpose),
41
- spec.flags && spec.flags.length > 0 ? renderFlags(spec.flags) : '',
42
- spec.input && spec.input.length > 0 ? renderFields('Input', spec.input) : '',
43
- spec.output && spec.output.length > 0 ? renderFields('Output', spec.output) : '',
44
- spec.examples && spec.examples.length > 0 ? renderList('Examples', spec.examples) : '',
45
- spec.next && spec.next.length > 0 ? renderList('Next', spec.next) : '',
48
+ spec.docs && spec.docs.length > 0 ? renderDocs(spec.docs) : "",
49
+ spec.flags && spec.flags.length > 0 ? renderFlags(spec.flags) : "",
50
+ spec.inputs ? renderInputs(spec.inputs) : "",
51
+ spec.output && spec.output.length > 0
52
+ ? renderFields("Output", spec.output)
53
+ : "",
54
+ spec.examples && spec.examples.length > 0
55
+ ? renderList("Examples", spec.examples)
56
+ : "",
57
+ spec.next && spec.next.length > 0 ? renderList("Next", spec.next) : "",
46
58
  ].filter(Boolean);
47
- return sections.join('\n\n');
59
+ return sections.join("\n\n");
60
+ }
61
+ function cloneInputs(inputs) {
62
+ if (!inputs)
63
+ return undefined;
64
+ return {
65
+ path: inputs.path?.map((field) => ({ ...field })),
66
+ query: inputs.query?.map((field) => ({ ...field })),
67
+ headers: inputs.headers?.map((field) => ({ ...field })),
68
+ body: inputs.body
69
+ ? {
70
+ schema: inputs.body.schema,
71
+ fields: inputs.body.fields?.map((field) => ({ ...field })),
72
+ discriminator: inputs.body.discriminator
73
+ ? {
74
+ property: inputs.body.discriminator.property,
75
+ variants: inputs.body.discriminator.variants.map((variant) => ({ ...variant })),
76
+ }
77
+ : undefined,
78
+ }
79
+ : undefined,
80
+ };
81
+ }
82
+ function renderDocs(docs) {
83
+ return renderList("Docs", docs.map((doc) => doc.markdownUrl ?? doc.url));
48
84
  }
49
85
  function renderPurpose(purpose) {
50
86
  return `Purpose:\n ${purpose}`;
51
87
  }
52
88
  function renderFlags(flags) {
53
89
  const rows = flags.map((flag) => {
54
- const left = [flag.name, flag.value].filter(Boolean).join(' ');
55
- const suffix = flag.mapsTo ? ` Maps to ${flag.mapsTo}.` : '';
90
+ const left = [flag.name, flag.value].filter(Boolean).join(" ");
91
+ const suffix = flag.mapsTo ? ` Maps to ${flag.mapsTo}.` : "";
56
92
  return { left, right: `${flag.description}${suffix}` };
57
93
  });
58
- return renderRows('Flags', rows);
94
+ return renderRows("Flags", rows);
95
+ }
96
+ function renderInputs(inputs) {
97
+ const sections = [
98
+ inputs.path && inputs.path.length > 0 ? renderFields("Path params", inputs.path) : "",
99
+ inputs.query && inputs.query.length > 0 ? renderFields("Query params", inputs.query) : "",
100
+ inputs.headers && inputs.headers.length > 0 ? renderFields("Headers", inputs.headers) : "",
101
+ inputs.body ? renderBodyInput(inputs.body) : "",
102
+ ].filter(Boolean);
103
+ return sections.join("\n\n");
104
+ }
105
+ function renderBodyInput(body) {
106
+ const lines = [
107
+ body.schema ? `Schema: ${body.schema}` : "",
108
+ body.fields && body.fields.length > 0 ? renderFields("Body", body.fields) : "",
109
+ body.discriminator ? renderDiscriminator(body.discriminator) : "",
110
+ ].filter(Boolean);
111
+ return lines.join("\n");
112
+ }
113
+ function renderDiscriminator(discriminator) {
114
+ const rows = discriminator.variants.map((variant) => ({
115
+ left: variant.value,
116
+ right: [variant.schema, variant.summary].filter(Boolean).join(" "),
117
+ }));
118
+ return renderRows(`Body variants (${discriminator.property})`, rows);
59
119
  }
60
120
  function renderFields(title, fields) {
61
121
  const rows = fields.map((field) => ({
62
122
  left: field.name,
63
- right: [field.type, field.required === true ? 'required' : 'optional', field.description]
123
+ right: [
124
+ field.type,
125
+ field.required === true ? "required" : "optional",
126
+ field.description,
127
+ ]
64
128
  .filter(Boolean)
65
- .join(' '),
129
+ .join(" "),
66
130
  }));
67
131
  return renderRows(title, rows);
68
132
  }
69
133
  function renderRows(title, rows) {
70
134
  const width = Math.max(...rows.map((row) => row.left.length));
71
135
  const lines = rows.map((row) => ` ${row.left.padEnd(width)} ${row.right}`);
72
- return `${title}:\n${lines.join('\n')}`;
136
+ return `${title}:\n${lines.join("\n")}`;
73
137
  }
74
138
  function renderList(title, lines) {
75
- return `${title}:\n${lines.map((line) => ` ${line}`).join('\n')}`;
139
+ return `${title}:\n${lines.map((line) => ` ${line}`).join("\n")}`;
76
140
  }
@@ -1,4 +1,5 @@
1
1
  export declare function readText(path: string): Promise<string>;
2
+ export declare function parseJsonString(text: string, label?: string): unknown;
2
3
  export declare function readJsonFile(path: string, label?: string): Promise<unknown>;
3
4
  export declare function readStdinText(): Promise<string>;
4
5
  export declare function writeBinary(path: string, data: Uint8Array): Promise<void>;
@@ -8,8 +8,7 @@ export async function readText(path) {
8
8
  }
9
9
  return readFile(path, 'utf8');
10
10
  }
11
- export async function readJsonFile(path, label = '--input') {
12
- const text = await readText(path);
11
+ export function parseJsonString(text, label = 'json input') {
13
12
  try {
14
13
  return JSON.parse(text);
15
14
  }
@@ -18,6 +17,9 @@ export async function readJsonFile(path, label = '--input') {
18
17
  throw new CliError(`Invalid JSON in ${label}: ${reason}`);
19
18
  }
20
19
  }
20
+ export async function readJsonFile(path, label = 'json input') {
21
+ return parseJsonString(await readText(path), label);
22
+ }
21
23
  export async function readStdinText() {
22
24
  const chunks = [];
23
25
  for await (const chunk of processStdin) {
@@ -11,9 +11,14 @@ type UploadFileInput<OperationId extends CliOperationId> = OperationPathInput<Op
11
11
  file: Blob;
12
12
  fileName: string;
13
13
  fields?: Record<string, string>;
14
+ actingSubaccountId?: string;
15
+ };
16
+ type DownloadInput<OperationId extends CliOperationId> = OperationPathInput<OperationId> & {
17
+ actingSubaccountId?: string;
18
+ };
19
+ type StreamInput<OperationId extends CliOperationId> = OperationPathInput<OperationId> & OperationQueryInput<OperationId> & {
20
+ actingSubaccountId?: string;
14
21
  };
15
- type DownloadInput<OperationId extends CliOperationId> = OperationPathInput<OperationId>;
16
- type StreamInput<OperationId extends CliOperationId> = OperationPathInput<OperationId> & OperationQueryInput<OperationId>;
17
22
  type OperationPathInput<OperationId extends CliOperationId> = [
18
23
  CliOperationPathParams<OperationId>
19
24
  ] extends [never] ? {
@@ -37,10 +42,48 @@ type OperationBodyInput<OperationId extends CliOperationId> = [
37
42
  };
38
43
  export type OperationRequestInput<OperationId extends CliOperationId> = OperationPathInput<OperationId> & OperationQueryInput<OperationId> & OperationBodyInput<OperationId> & {
39
44
  idempotencyKey?: string;
45
+ actingSubaccountId?: string;
40
46
  output?: OutputFlags;
41
47
  };
48
+ export declare function optionString(options: Record<string, unknown>, name: string): string | undefined;
49
+ export declare function actingSubaccountOption(name?: string): (options: Record<string, unknown>) => string | undefined;
42
50
  export declare function addPaginationFlags(command: Command): Command;
43
- export declare function addJsonBodyFlag(command: Command): Command;
51
+ /**
52
+ * Generic request-override flags every operation command accepts, so the FULL
53
+ * request surface is reachable without curated per-field flags:
54
+ * --params path + query parameters as one JSON object
55
+ * --body the request body as JSON (POST/PUT/PATCH)
56
+ * Each accepts inline JSON, `@file`, or `-` for stdin. (`--json` is taken by the
57
+ * output formatter, so the body flag is `--body`.)
58
+ */
59
+ export declare function addRequestOverrideFlags(command: Command, opts?: {
60
+ body?: boolean;
61
+ }): Command;
62
+ /**
63
+ * Turn the generic `--params` / `--body` flags into request-input
64
+ * overrides. `--params` keys that match the route's `{placeholders}` become path
65
+ * params; the rest become query params. `--body` becomes the body.
66
+ */
67
+ export declare function resolveRequestOverrides(operationId: CliOperationId, options: Record<string, unknown>): Promise<{
68
+ pathParams?: Record<string, unknown>;
69
+ query?: Record<string, unknown>;
70
+ body?: unknown;
71
+ }>;
72
+ /**
73
+ * Merge the generic `--params` / `--body` overrides into a hand-written
74
+ * command's curated request input. Use this in any command whose `.action` calls
75
+ * `requestOperationAndPrint` directly (not via createOperation*Command), so it gains
76
+ * full `--params`/`--body` coverage with one wrap:
77
+ *
78
+ * await requestOperationAndPrint(factory, 'op.id',
79
+ * await buildOperationInput('op.id', options, {
80
+ * pathParams: { id }, body, query, idempotencyKey, output: outputFlags(options),
81
+ * }));
82
+ *
83
+ * Precedence: `--body` replaces the curated body; `--params` keys fill the
84
+ * route's path placeholders / query, with curated path+query values overlaid on top.
85
+ */
86
+ export declare function buildOperationInput<OperationId extends CliOperationId>(operationId: OperationId, options: Record<string, unknown>, curated: OperationRequestInput<OperationId>): Promise<OperationRequestInput<OperationId>>;
44
87
  export declare function outputFlags(options: Record<string, unknown>): OutputFlags;
45
88
  export declare function operationPath<OperationId extends CliOperationId>(operationId: OperationId, pathParams?: CliOperationPathParams<OperationId>): string;
46
89
  export declare function requestOperationAndPrint<OperationId extends CliOperationId>(deps: ApiDeps, operationId: OperationId, input: OperationRequestInput<OperationId>): Promise<void>;
@@ -54,6 +97,7 @@ export declare function createOperationListCommand<OperationId extends CliOperat
54
97
  description: string;
55
98
  configure?: (command: Command) => Command;
56
99
  query?: (options: Record<string, unknown>) => CliOperationQuery<OperationId>;
100
+ actingSubaccountId?: (options: Record<string, unknown>) => string | undefined;
57
101
  }): Command;
58
102
  export declare function createOperationViewCommand<OperationId extends CliOperationId>(factory: ApiDeps, config: {
59
103
  operationId: OperationId;
@@ -62,6 +106,7 @@ export declare function createOperationViewCommand<OperationId extends CliOperat
62
106
  argName?: string;
63
107
  configure?: (command: Command) => Command;
64
108
  query?: (id: string, options: Record<string, unknown>) => CliOperationQuery<OperationId>;
109
+ actingSubaccountId?: (id: string, options: Record<string, unknown>) => string | undefined;
65
110
  }): Command;
66
111
  export declare function createOperationDeleteCommand<OperationId extends CliOperationId>(factory: ApiDeps, config: {
67
112
  operationId: OperationId;
@@ -70,6 +115,7 @@ export declare function createOperationDeleteCommand<OperationId extends CliOper
70
115
  argNames?: string[];
71
116
  configure?: (command: Command) => Command;
72
117
  query?: (args: Record<string, string>, options: Record<string, unknown>) => CliOperationQuery<OperationId>;
118
+ actingSubaccountId?: (args: Record<string, string>, options: Record<string, unknown>) => string | undefined;
73
119
  }): Command;
74
120
  export declare function createOperationJsonBodyCommand<OperationId extends CliOperationId>(factory: ApiDeps, config: {
75
121
  operationId: OperationId;
@@ -79,5 +125,6 @@ export declare function createOperationJsonBodyCommand<OperationId extends CliOp
79
125
  configure?: (command: Command) => Command;
80
126
  body?: (args: Record<string, string>, options: Record<string, unknown>) => Promise<CliOperationJsonBody<OperationId>>;
81
127
  query?: (args: Record<string, string>, options: Record<string, unknown>) => CliOperationQuery<OperationId>;
128
+ actingSubaccountId?: (args: Record<string, string>, options: Record<string, unknown>) => string | undefined;
82
129
  }): Command;
83
130
  export {};
@@ -5,13 +5,139 @@ import { parsePositiveInteger } from './options.js';
5
5
  import { addOutputFlags, outputData } from './output.js';
6
6
  import { CliError } from '../../runtime/errors.js';
7
7
  import { addCliOperationHelp } from './help.js';
8
+ export function optionString(options, name) {
9
+ return typeof options[name] === 'string' ? options[name] : undefined;
10
+ }
11
+ export function actingSubaccountOption(name = 'subaccountId') {
12
+ return (options) => optionString(options, name);
13
+ }
14
+ function withActingSubaccount(input, actingSubaccountId) {
15
+ return {
16
+ ...input,
17
+ ...(actingSubaccountId ? { actingSubaccountId } : {}),
18
+ };
19
+ }
8
20
  export function addPaginationFlags(command) {
9
21
  return command
10
22
  .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
11
23
  .option('--cursor <cursor>', 'Pagination cursor');
12
24
  }
13
- export function addJsonBodyFlag(command) {
14
- return command.option('--input <path>', 'Read JSON request body from file, or - for stdin');
25
+ /**
26
+ * Generic request-override flags every operation command accepts, so the FULL
27
+ * request surface is reachable without curated per-field flags:
28
+ * --params path + query parameters as one JSON object
29
+ * --body the request body as JSON (POST/PUT/PATCH)
30
+ * Each accepts inline JSON, `@file`, or `-` for stdin. (`--json` is taken by the
31
+ * output formatter, so the body flag is `--body`.)
32
+ */
33
+ export function addRequestOverrideFlags(command, opts = {}) {
34
+ command = command.option('--params <json>', 'Path/query parameters as a JSON object (inline, @file, or - for stdin)');
35
+ if (opts.body) {
36
+ command = command.option('--body <json>', 'Request body as JSON (inline, @file, or - for stdin)');
37
+ }
38
+ return command;
39
+ }
40
+ /** Parse a JSON argument: inline JSON, `@path` for a file, or `-` for stdin. */
41
+ async function parseJsonArg(value, label) {
42
+ const trimmed = value.trim();
43
+ if (trimmed === '-')
44
+ return readJsonFile('-', label);
45
+ if (trimmed.startsWith('@'))
46
+ return readJsonFile(trimmed.slice(1), label);
47
+ try {
48
+ return JSON.parse(value);
49
+ }
50
+ catch (error) {
51
+ const reason = error instanceof Error ? error.message : String(error);
52
+ throw new CliError(`Invalid JSON in ${label}: ${reason}`);
53
+ }
54
+ }
55
+ function pathParamNames(operationId) {
56
+ const names = new Set();
57
+ CLI_OPENAPI_ROUTES[operationId].path.replace(/\{([^}]+)\}/g, (_m, key) => {
58
+ names.add(key);
59
+ return '';
60
+ });
61
+ return names;
62
+ }
63
+ /**
64
+ * Turn the generic `--params` / `--body` flags into request-input
65
+ * overrides. `--params` keys that match the route's `{placeholders}` become path
66
+ * params; the rest become query params. `--body` becomes the body.
67
+ */
68
+ export async function resolveRequestOverrides(operationId, options) {
69
+ const overrides = {};
70
+ if (typeof options.params === 'string') {
71
+ const parsed = await parseJsonArg(options.params, '--params');
72
+ if (!isRecord(parsed)) {
73
+ throw new CliError('--params must be a JSON object, e.g. \'{"limit":50}\'');
74
+ }
75
+ const pathNames = pathParamNames(operationId);
76
+ const pathParams = {};
77
+ const query = {};
78
+ for (const [key, value] of Object.entries(parsed)) {
79
+ if (pathNames.has(key))
80
+ pathParams[key] = value;
81
+ else
82
+ query[key] = value;
83
+ }
84
+ if (Object.keys(pathParams).length > 0)
85
+ overrides.pathParams = pathParams;
86
+ if (Object.keys(query).length > 0)
87
+ overrides.query = query;
88
+ }
89
+ if (typeof options.body === 'string') {
90
+ overrides.body = await parseJsonArg(options.body, '--body');
91
+ }
92
+ return overrides;
93
+ }
94
+ /**
95
+ * Merge the generic `--params` / `--body` overrides into a hand-written
96
+ * command's curated request input. Use this in any command whose `.action` calls
97
+ * `requestOperationAndPrint` directly (not via createOperation*Command), so it gains
98
+ * full `--params`/`--body` coverage with one wrap:
99
+ *
100
+ * await requestOperationAndPrint(factory, 'op.id',
101
+ * await buildOperationInput('op.id', options, {
102
+ * pathParams: { id }, body, query, idempotencyKey, output: outputFlags(options),
103
+ * }));
104
+ *
105
+ * Precedence: `--body` replaces the curated body; `--params` keys fill the
106
+ * route's path placeholders / query, with curated path+query values overlaid on top.
107
+ */
108
+ export async function buildOperationInput(operationId, options, curated) {
109
+ const overrides = await resolveRequestOverrides(operationId, options);
110
+ const c = curated;
111
+ const merged = {};
112
+ const pathParams = overlayDefined(overrides.pathParams, c.pathParams);
113
+ if (pathParams)
114
+ merged.pathParams = pathParams;
115
+ const query = overlayDefined(overrides.query, c.query);
116
+ if (query)
117
+ merged.query = query;
118
+ const body = overrides.body !== undefined ? overrides.body : c.body;
119
+ if (body !== undefined)
120
+ merged.body = body;
121
+ if (c.idempotencyKey)
122
+ merged.idempotencyKey = c.idempotencyKey;
123
+ if (c.actingSubaccountId)
124
+ merged.actingSubaccountId = c.actingSubaccountId;
125
+ if (c.output)
126
+ merged.output = c.output;
127
+ return merged;
128
+ }
129
+ /** Overlay only the defined keys of `overlay` onto `base` (curated values win). */
130
+ function overlayDefined(base, overlay) {
131
+ if (!base)
132
+ return overlay;
133
+ if (!overlay)
134
+ return base;
135
+ const merged = { ...base };
136
+ for (const [key, value] of Object.entries(overlay)) {
137
+ if (value !== undefined)
138
+ merged[key] = value;
139
+ }
140
+ return merged;
15
141
  }
16
142
  export function outputFlags(options) {
17
143
  return {
@@ -44,6 +170,7 @@ export async function requestOperation(deps, operationId, input) {
44
170
  ...('query' in input && input.query !== undefined ? { query: input.query } : {}),
45
171
  ...('body' in input && input.body !== undefined ? { body: input.body } : {}),
46
172
  ...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
173
+ ...(input.actingSubaccountId ? { actingSubaccountId: input.actingSubaccountId } : {}),
47
174
  };
48
175
  const options = Object.keys(requestOptions).length > 0 ? requestOptions : undefined;
49
176
  const result = route.method === 'get'
@@ -64,28 +191,37 @@ export async function uploadOperationFile(deps, operationId, input) {
64
191
  fileName: input.fileName,
65
192
  ...('query' in input && input.query !== undefined ? { query: input.query } : {}),
66
193
  ...(input.fields ? { fields: input.fields } : {}),
194
+ ...(input.actingSubaccountId ? { actingSubaccountId: input.actingSubaccountId } : {}),
67
195
  });
68
196
  }
69
197
  export async function downloadOperation(deps, operationId, input) {
70
198
  const client = await deps.apiClient();
71
- return client.download(operationPath(operationId, input.pathParams));
199
+ return client.download(operationPath(operationId, input.pathParams), {
200
+ ...(input.actingSubaccountId ? { actingSubaccountId: input.actingSubaccountId } : {}),
201
+ });
72
202
  }
73
203
  export async function streamOperationText(deps, operationId, input) {
74
204
  const client = await deps.apiClient();
75
205
  return client.streamText(operationPath(operationId, input.pathParams), {
76
206
  ...('query' in input && input.query !== undefined ? { query: input.query } : {}),
207
+ ...(input.actingSubaccountId ? { actingSubaccountId: input.actingSubaccountId } : {}),
77
208
  });
78
209
  }
79
210
  export function createOperationListCommand(factory, config) {
80
211
  let command = new Command(config.name ?? 'list').description(config.description);
81
212
  command = addCliOperationHelp(command, config.operationId);
82
213
  command = config.configure ? config.configure(command) : addPaginationFlags(command);
214
+ command = addRequestOverrideFlags(command);
83
215
  command = addOutputFlags(command);
84
216
  return command.action(async (options) => {
85
- await requestOperationAndPrint(factory, config.operationId, {
86
- query: config.query ? config.query(options) : defaultListQuery(options),
217
+ const overrides = await resolveRequestOverrides(config.operationId, options);
218
+ const curatedQuery = config.query ? config.query(options) : defaultListQuery(options);
219
+ const actingSubaccountId = config.actingSubaccountId?.(options);
220
+ await requestOperationAndPrint(factory, config.operationId, withActingSubaccount({
221
+ ...(overrides.pathParams ? { pathParams: overrides.pathParams } : {}),
222
+ query: overlayDefined(overrides.query, curatedQuery),
87
223
  output: outputFlags(options),
88
- });
224
+ }, actingSubaccountId));
89
225
  });
90
226
  }
91
227
  export function createOperationViewCommand(factory, config) {
@@ -95,13 +231,17 @@ export function createOperationViewCommand(factory, config) {
95
231
  .argument(`<${argName}>`);
96
232
  command = addCliOperationHelp(command, config.operationId);
97
233
  command = config.configure ? config.configure(command) : command;
234
+ command = addRequestOverrideFlags(command);
98
235
  command = addOutputFlags(command);
99
236
  return command.action(async (id, options) => {
100
- await requestOperationAndPrint(factory, config.operationId, {
101
- pathParams: { [argName]: id, id },
102
- query: config.query ? config.query(id, options) : undefined,
237
+ const overrides = await resolveRequestOverrides(config.operationId, options);
238
+ const curatedQuery = config.query ? config.query(id, options) : undefined;
239
+ const actingSubaccountId = config.actingSubaccountId?.(id, options);
240
+ await requestOperationAndPrint(factory, config.operationId, withActingSubaccount({
241
+ pathParams: overlayDefined(overrides.pathParams, { [argName]: id, id }),
242
+ query: overlayDefined(overrides.query, curatedQuery),
103
243
  output: outputFlags(options),
104
- });
244
+ }, actingSubaccountId));
105
245
  });
106
246
  }
107
247
  export function createOperationDeleteCommand(factory, config) {
@@ -112,6 +252,7 @@ export function createOperationDeleteCommand(factory, config) {
112
252
  command = command.argument(`<${arg}>`);
113
253
  command = command.option('-y, --yes', 'Confirm deletion');
114
254
  command = config.configure ? config.configure(command) : command;
255
+ command = addRequestOverrideFlags(command);
115
256
  command = addOutputFlags(command);
116
257
  return command.action(async (...actionArgs) => {
117
258
  const options = getActionOptions(actionArgs);
@@ -121,11 +262,14 @@ export function createOperationDeleteCommand(factory, config) {
121
262
  const params = {};
122
263
  for (let i = 0; i < argNames.length; i += 1)
123
264
  params[argNames[i]] = String(actionArgs[i]);
124
- await requestOperationAndPrint(factory, config.operationId, {
125
- pathParams: params,
126
- query: config.query ? config.query(params, options) : undefined,
265
+ const overrides = await resolveRequestOverrides(config.operationId, options);
266
+ const curatedQuery = config.query ? config.query(params, options) : undefined;
267
+ const actingSubaccountId = config.actingSubaccountId?.(params, options);
268
+ await requestOperationAndPrint(factory, config.operationId, withActingSubaccount({
269
+ pathParams: overlayDefined(overrides.pathParams, params),
270
+ query: overlayDefined(overrides.query, curatedQuery),
127
271
  output: outputFlags(options),
128
- });
272
+ }, actingSubaccountId));
129
273
  });
130
274
  }
131
275
  export function createOperationJsonBodyCommand(factory, config) {
@@ -134,22 +278,27 @@ export function createOperationJsonBodyCommand(factory, config) {
134
278
  command = addCliOperationHelp(command, config.operationId);
135
279
  for (const arg of argNames)
136
280
  command = command.argument(`<${arg}>`);
137
- command = config.configure ? config.configure(command) : addJsonBodyFlag(command);
281
+ command = config.configure ? config.configure(command) : command;
282
+ command = addRequestOverrideFlags(command, { body: true });
138
283
  command = addOutputFlags(command);
139
284
  return command.action(async (...actionArgs) => {
140
285
  const options = getActionOptions(actionArgs);
141
286
  const args = {};
142
287
  for (let i = 0; i < argNames.length; i += 1)
143
288
  args[argNames[i]] = String(actionArgs[i]);
144
- const body = config.body
145
- ? await config.body(args, options)
146
- : await bodyFromInputOption(String(options.input ?? ''));
147
- await requestOperationAndPrint(factory, config.operationId, {
148
- pathParams: args,
289
+ const overrides = await resolveRequestOverrides(config.operationId, options);
290
+ // --body (raw JSON) takes the whole body; otherwise fall back to the
291
+ // curated per-flag body. Either way the server validates against the schema.
292
+ const curatedBody = config.body ? await config.body(args, options) : undefined;
293
+ const body = overrides.body !== undefined ? overrides.body : (curatedBody ?? {});
294
+ const curatedQuery = config.query ? config.query(args, options) : undefined;
295
+ const actingSubaccountId = config.actingSubaccountId?.(args, options);
296
+ await requestOperationAndPrint(factory, config.operationId, withActingSubaccount({
297
+ pathParams: overlayDefined(overrides.pathParams, args),
149
298
  body,
150
- query: config.query ? config.query(args, options) : undefined,
299
+ query: overlayDefined(overrides.query, curatedQuery),
151
300
  output: outputFlags(options),
152
- });
301
+ }, actingSubaccountId));
153
302
  });
154
303
  }
155
304
  function defaultListQuery(options) {
@@ -158,9 +307,6 @@ function defaultListQuery(options) {
158
307
  cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
159
308
  };
160
309
  }
161
- async function bodyFromInputOption(path) {
162
- return path ? readJsonFile(path) : {};
163
- }
164
310
  function getActionOptions(args) {
165
311
  const candidate = args.at(-2);
166
312
  return isRecord(candidate) ? candidate : {};
@@ -29,13 +29,9 @@ export function writeJson(io, value) {
29
29
  io.writeOut(`${JSON.stringify(value, null, 2)}\n`);
30
30
  }
31
31
  function resolveOutput(flags) {
32
- const jsonChanged = flags.json !== undefined;
33
- if (flags.jq && !jsonChanged) {
34
- throw new CliError('cannot use --jq without specifying --json');
35
- }
36
- if (flags.template && !jsonChanged) {
37
- throw new CliError('cannot use --template without specifying --json');
38
- }
32
+ // --jq / --template are JSON-only transforms, so their presence implies JSON
33
+ // output; they no longer require an explicit --json. --json's remaining job is
34
+ // field projection (`--json a,b`). Default output is already JSON either way.
39
35
  return {
40
36
  fields: parseJsonFields(flags.json),
41
37
  jq: flags.jq,