@bctrl/cli 0.1.6 → 0.1.8
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/LICENSE +15 -0
- package/README.md +113 -95
- package/dist/api/auth.d.ts +10 -1
- package/dist/api/auth.js +10 -1
- package/dist/api/client.d.ts +2 -1
- package/dist/api/client.js +37 -37
- package/dist/api/device-auth.d.ts +39 -0
- package/dist/api/device-auth.js +93 -0
- package/dist/api/errors.d.ts +1 -0
- package/dist/api/errors.js +8 -0
- package/dist/commands/ai/index.js +10 -11
- package/dist/commands/api-key/index.js +3 -8
- package/dist/commands/auth/device-login.d.ts +6 -0
- package/dist/commands/auth/device-login.js +19 -0
- package/dist/commands/auth/login.d.ts +11 -0
- package/dist/commands/auth/login.js +140 -20
- package/dist/commands/auth/logout.d.ts +2 -0
- package/dist/commands/auth/logout.js +19 -2
- package/dist/commands/auth/status.d.ts +1 -0
- package/dist/commands/auth/status.js +22 -5
- package/dist/commands/auth/token.js +6 -0
- package/dist/commands/browser-extension/index.js +7 -15
- package/dist/commands/file/index.js +8 -7
- package/dist/commands/help/index.js +5 -4
- package/dist/commands/notification-recipient/index.d.ts +3 -0
- package/dist/commands/notification-recipient/index.js +72 -0
- package/dist/commands/proxy/index.js +6 -4
- package/dist/commands/run/index.js +31 -32
- package/dist/commands/runtime/index.js +185 -106
- package/dist/commands/shared/help.d.ts +26 -3
- package/dist/commands/shared/help.js +87 -23
- package/dist/commands/shared/io.d.ts +1 -0
- package/dist/commands/shared/io.js +4 -2
- package/dist/commands/shared/operation.d.ts +50 -3
- package/dist/commands/shared/operation.js +171 -25
- package/dist/commands/shared/output.js +3 -7
- package/dist/commands/space/index.js +35 -32
- package/dist/commands/space/list.js +6 -5
- package/dist/commands/subaccount/index.js +12 -14
- package/dist/commands/usage/index.js +9 -5
- package/dist/commands/vault/index.js +15 -21
- package/dist/config/auth-store.d.ts +75 -9
- package/dist/config/auth-store.js +133 -27
- package/dist/config/config.d.ts +2 -2
- package/dist/config/config.js +9 -17
- package/dist/config/paths.d.ts +16 -0
- package/dist/config/paths.js +34 -0
- package/dist/config/pending-auth.d.ts +19 -0
- package/dist/config/pending-auth.js +72 -0
- package/dist/config/secret-store.d.ts +24 -0
- package/dist/config/secret-store.js +123 -0
- package/dist/factory.js +2 -1
- package/dist/generated/help.d.ts +7424 -2768
- package/dist/generated/help.js +10116 -4014
- package/dist/generated/openapi-routes.d.ts +52 -8
- package/dist/generated/openapi-routes.js +20 -9
- package/dist/generated/openapi-types.d.ts +2688 -883
- package/dist/root.js +2 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +58 -47
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Command } from
|
|
2
|
-
import { type CliHelpCommandId } from
|
|
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
|
-
|
|
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
|
|
1
|
+
import { CLI_HELP_COMMANDS, } from "../../generated/help.js";
|
|
2
2
|
export function addStructuredHelp(command, spec) {
|
|
3
|
-
command.addHelpText(
|
|
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)
|
|
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
|
-
|
|
23
|
+
inputs: cloneInputs(raw.inputs),
|
|
21
24
|
output: raw.output?.fields.map((field) => ({ ...field })),
|
|
22
|
-
examples: raw.examples
|
|
23
|
-
|
|
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 ===
|
|
34
|
+
if (typeof value === "string")
|
|
28
35
|
return value;
|
|
29
|
-
if (!value || typeof value !==
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
30
37
|
return undefined;
|
|
31
38
|
const record = value;
|
|
32
|
-
if (typeof record.command ===
|
|
39
|
+
if (typeof record.command === "string")
|
|
33
40
|
return record.command;
|
|
34
|
-
if (typeof record.topic ===
|
|
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.
|
|
42
|
-
spec.
|
|
43
|
-
spec.
|
|
44
|
-
spec.
|
|
45
|
-
|
|
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(
|
|
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(
|
|
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: [
|
|
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(
|
|
136
|
+
return `${title}:\n${lines.join("\n")}`;
|
|
73
137
|
}
|
|
74
138
|
function renderList(title, lines) {
|
|
75
|
-
return `${title}:\n${lines.map((line) => ` ${line}`).join(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
|
86
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
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) :
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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:
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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,
|