@settlemint/dalp-cli 3.1.33-main.43433008600 → 3.1.33-release.43440124932
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 +157 -3
- package/README.md +169 -44
- package/dist/dalp.js +188069 -39966
- package/package.json +20 -27
- package/dist/dalp.js.map +0 -249
- package/dist/openapi-surface-thq7xyw6.json +0 -83202
- package/src/bin.ts +0 -6
- package/src/commands/config.ts +0 -73
- package/src/commands/login.ts +0 -483
- package/src/commands/logout.ts +0 -46
- package/src/commands/org.ts +0 -103
- package/src/commands/whoami.ts +0 -29
- package/src/generated/openapi-surface.json +0 -83202
- package/src/hosted-mcp.ts +0 -145
- package/src/lib/api-client.ts +0 -30
- package/src/lib/auth-request.ts +0 -86
- package/src/lib/cli-config.ts +0 -104
- package/src/lib/cli-cta.ts +0 -296
- package/src/lib/cli-status.ts +0 -102
- package/src/lib/coverage.ts +0 -41
- package/src/lib/credential-store.ts +0 -256
- package/src/lib/errors.ts +0 -207
- package/src/lib/feature-gated-operations.ts +0 -18
- package/src/lib/openapi-surface-types.ts +0 -41
- package/src/lib/openapi-surface.ts +0 -17
- package/src/lib/operation-inventory.ts +0 -69
- package/src/lib/register-operations.ts +0 -643
- package/src/lib/run-command.ts +0 -38
- package/src/lib/sdk-cli-schema.ts +0 -366
- package/src/lib/secret-prompt.ts +0 -23
- package/src/lib/vars.ts +0 -19
- package/src/lib/webhook-fixtures.ts +0 -1158
- package/src/main.ts +0 -99
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
|
-
import { commandPathForUrl, isNonCliOperation, routeIds, staticUrlSegments } from "@settlemint/dalp-sdk/routes";
|
|
3
|
-
import type { DalpRouteId } from "@settlemint/dalp-sdk/types";
|
|
4
|
-
import { zodForRequestBody, zodFromSurfaceParams } from "./sdk-cli-schema.ts";
|
|
5
|
-
import { surfaceFor, type SurfaceOperation } from "./openapi-surface.ts";
|
|
6
|
-
export interface OperationDescriptor {
|
|
7
|
-
readonly name: DalpRouteId;
|
|
8
|
-
readonly url: string;
|
|
9
|
-
readonly method: string;
|
|
10
|
-
readonly group: string;
|
|
11
|
-
readonly commandPath: ReadonlyArray<string>;
|
|
12
|
-
readonly pathSchema?: z.ZodType;
|
|
13
|
-
readonly querySchema?: z.ZodType;
|
|
14
|
-
readonly bodySchema?: z.ZodType;
|
|
15
|
-
readonly surface: SurfaceOperation;
|
|
16
|
-
readonly help: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function groupForUrl(url: string): string {
|
|
20
|
-
const segments = staticUrlSegments(url);
|
|
21
|
-
return segments[0] ?? "api";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function operationHintKey(method: string, url: string): string {
|
|
25
|
-
return `${method} ${url}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function listSdkOperationNames(): Array<DalpRouteId> {
|
|
29
|
-
return [...routeIds];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function buildGeneratedOperations(): Array<OperationDescriptor> {
|
|
33
|
-
const descriptors: Array<OperationDescriptor> = [];
|
|
34
|
-
for (const name of listSdkOperationNames()) {
|
|
35
|
-
const surface = surfaceFor(name);
|
|
36
|
-
if (surface === undefined) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const { url, method } = surface;
|
|
40
|
-
descriptors.push({
|
|
41
|
-
name,
|
|
42
|
-
url,
|
|
43
|
-
method,
|
|
44
|
-
group: groupForUrl(url),
|
|
45
|
-
commandPath: commandPathForUrl(method, url),
|
|
46
|
-
pathSchema: zodFromSurfaceParams(surface.pathParams) as z.ZodType | undefined,
|
|
47
|
-
querySchema: zodFromSurfaceParams(surface.queryParams) as z.ZodType | undefined,
|
|
48
|
-
bodySchema: zodForRequestBody(surface),
|
|
49
|
-
surface,
|
|
50
|
-
help: surface.help,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return descriptors.sort((left, right) => left.name.localeCompare(right.name));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let generatedOperations: ReadonlyArray<OperationDescriptor> | undefined;
|
|
57
|
-
|
|
58
|
-
export function listGeneratedOperations(): Array<OperationDescriptor> {
|
|
59
|
-
generatedOperations ??= buildGeneratedOperations();
|
|
60
|
-
return [...generatedOperations];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function listCliOperations(): Array<OperationDescriptor> {
|
|
64
|
-
return listGeneratedOperations().filter((descriptor) => !isNonCliOperation(descriptor.name));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function listGeneratedOperationNames(): Array<string> {
|
|
68
|
-
return listGeneratedOperations().map((descriptor) => descriptor.name);
|
|
69
|
-
}
|
|
@@ -1,643 +0,0 @@
|
|
|
1
|
-
import type { DalpClient } from "@settlemint/dalp-sdk";
|
|
2
|
-
import { Cli, z } from "incur";
|
|
3
|
-
import { buildErrorCta, buildSuccessCtaFromResult, renderCtaText, type CliCtaBlock } from "./cli-cta.ts";
|
|
4
|
-
import { createCliStatus } from "./cli-status.ts";
|
|
5
|
-
import { renderSdkErrorForCli } from "./errors.ts";
|
|
6
|
-
import { gatedFeatureContext } from "./feature-gated-operations.ts";
|
|
7
|
-
import { INDEX_COMMAND } from "@settlemint/dalp-sdk/routes";
|
|
8
|
-
import { listCliOperations, operationHintKey, type OperationDescriptor } from "./operation-inventory.ts";
|
|
9
|
-
import { tagDescription, type SurfaceOperation } from "./openapi-surface.ts";
|
|
10
|
-
import { runCommand } from "./run-command.ts";
|
|
11
|
-
import {
|
|
12
|
-
buildArgsFromSdkPath,
|
|
13
|
-
buildOptionsFromSdk,
|
|
14
|
-
collectBodyBag,
|
|
15
|
-
collectPathBag,
|
|
16
|
-
collectQueryBag,
|
|
17
|
-
type LooseBag,
|
|
18
|
-
parseLooseBag,
|
|
19
|
-
parseWithSdkSchema,
|
|
20
|
-
} from "./sdk-cli-schema.ts";
|
|
21
|
-
import { cliVars } from "./vars.ts";
|
|
22
|
-
|
|
23
|
-
const CAMEL_BOUNDARY_PATTERN = /([a-z0-9])([A-Z])/gu;
|
|
24
|
-
const DASH_RUN_PATTERN = /-+/gu;
|
|
25
|
-
|
|
26
|
-
function readOkHelper<T extends object>(
|
|
27
|
-
context: T
|
|
28
|
-
): ((data: unknown, meta?: { cta?: CliCtaBlock }) => unknown) | undefined {
|
|
29
|
-
const ok = Reflect.get(context, "ok");
|
|
30
|
-
return typeof ok === "function" ? (ok as (data: unknown, meta?: { cta?: CliCtaBlock }) => unknown) : undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function withSuccessCta<T extends object>(context: T, data: unknown): unknown {
|
|
34
|
-
const cta = buildSuccessCtaFromResult(data);
|
|
35
|
-
const ok = readOkHelper(context);
|
|
36
|
-
if (ok === undefined || cta === undefined) {
|
|
37
|
-
return data;
|
|
38
|
-
}
|
|
39
|
-
return ok(data, { cta });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type ApiBag = LooseBag;
|
|
43
|
-
|
|
44
|
-
interface CliRefusal {
|
|
45
|
-
readonly code: string;
|
|
46
|
-
readonly message: string;
|
|
47
|
-
readonly retryable?: boolean;
|
|
48
|
-
readonly exitCode?: number;
|
|
49
|
-
readonly cta?: CliCtaBlock;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface McpAnnotations {
|
|
53
|
-
readonly readOnlyHint: boolean;
|
|
54
|
-
readonly destructiveHint?: boolean;
|
|
55
|
-
readonly idempotentHint?: boolean;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const REGISTERED_OPERATIONS: ReadonlySet<string> = new Set(
|
|
59
|
-
listCliOperations().map((descriptor) => descriptor.name)
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
export interface CommandTreeNode {
|
|
63
|
-
readonly children: Map<string, CommandTreeNode>;
|
|
64
|
-
operations: Array<OperationDescriptor>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const isSseResult = (value: unknown): value is AsyncIterable<unknown> =>
|
|
68
|
-
typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
69
|
-
|
|
70
|
-
function pickPrimaryOp(ops: ReadonlyArray<OperationDescriptor>): OperationDescriptor {
|
|
71
|
-
return [...ops].sort((left, right) => right.surface.pathParams.length - left.surface.pathParams.length)[0]!;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function combinedHelp(ops: ReadonlyArray<OperationDescriptor>): string {
|
|
75
|
-
const helps = ops.map((op) => op.help).filter((help) => help.trim().length > 0);
|
|
76
|
-
if (helps.length === 0) {
|
|
77
|
-
return ops.map((op) => `${op.method} ${op.url}`).join("; ");
|
|
78
|
-
}
|
|
79
|
-
if (helps.length === 1) {
|
|
80
|
-
return helps[0]!;
|
|
81
|
-
}
|
|
82
|
-
const byPathParams = [...ops].sort((left, right) => left.surface.pathParams.length - right.surface.pathParams.length);
|
|
83
|
-
return byPathParams.map((op) => op.help).join("\n\n");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function buildHint(ops: ReadonlyArray<OperationDescriptor>): string {
|
|
87
|
-
return ops.map((op) => operationHintKey(op.method, op.url)).join(" · ");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function buildExamples(ops: ReadonlyArray<OperationDescriptor>, surface: SurfaceOperation) {
|
|
91
|
-
const examples: Array<{
|
|
92
|
-
description: string;
|
|
93
|
-
args?: Record<string, string>;
|
|
94
|
-
options?: Record<string, string | boolean | number>;
|
|
95
|
-
}> = [];
|
|
96
|
-
|
|
97
|
-
for (const example of surface.examples) {
|
|
98
|
-
examples.push({
|
|
99
|
-
description: example.description,
|
|
100
|
-
...(example.path !== undefined ? { args: example.path } : {}),
|
|
101
|
-
...(example.query !== undefined ? { options: example.query } : {}),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (examples.length === 0) {
|
|
106
|
-
examples.push({
|
|
107
|
-
description: ops[0]?.help.split("\n")[0] ?? ops[0]?.name ?? "call",
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
return examples.slice(0, 5);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function selectOperation(ops: ReadonlyArray<OperationDescriptor>, path: ApiBag | undefined): OperationDescriptor {
|
|
114
|
-
if (ops.length === 1) {
|
|
115
|
-
return ops[0]!;
|
|
116
|
-
}
|
|
117
|
-
const ranked = ops
|
|
118
|
-
.map((op) => ({
|
|
119
|
-
op,
|
|
120
|
-
required: op.surface.pathParams.filter((param) => param.required).map((param) => param.name),
|
|
121
|
-
total: op.surface.pathParams.length,
|
|
122
|
-
}))
|
|
123
|
-
.sort((left, right) => right.total - left.total);
|
|
124
|
-
|
|
125
|
-
for (const candidate of ranked) {
|
|
126
|
-
if (candidate.required.length === 0) {
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (path !== undefined && candidate.required.every((name) => path[name] !== undefined && path[name] !== "")) {
|
|
130
|
-
return candidate.op;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return [...ranked].sort((left, right) => left.total - right.total)[0]!.op;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function headerOptionName(name: string): string {
|
|
137
|
-
return name
|
|
138
|
-
.replaceAll(CAMEL_BOUNDARY_PATTERN, "$1-$2")
|
|
139
|
-
.replaceAll("_", "-")
|
|
140
|
-
.replaceAll(DASH_RUN_PATTERN, "-")
|
|
141
|
-
.toLowerCase();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function collectHeaders(surface: SurfaceOperation, options: ApiBag): Headers | undefined {
|
|
145
|
-
const headers = new Headers();
|
|
146
|
-
let any = false;
|
|
147
|
-
for (const param of surface.headerParams) {
|
|
148
|
-
const value = options[headerOptionName(param.name)] ?? options[param.name];
|
|
149
|
-
if (typeof value === "string" && value !== "") {
|
|
150
|
-
headers.set(param.name, value);
|
|
151
|
-
any = true;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return any ? headers : undefined;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function invokeOperation(
|
|
158
|
-
descriptor: OperationDescriptor,
|
|
159
|
-
client: DalpClient,
|
|
160
|
-
input: {
|
|
161
|
-
readonly path?: ApiBag;
|
|
162
|
-
readonly query?: unknown;
|
|
163
|
-
readonly body?: unknown;
|
|
164
|
-
readonly headers?: Headers;
|
|
165
|
-
readonly settlementTimeoutMs?: number;
|
|
166
|
-
},
|
|
167
|
-
signal?: AbortSignal
|
|
168
|
-
): Promise<unknown> {
|
|
169
|
-
const flat = {
|
|
170
|
-
...(input.body !== null && typeof input.body === "object" ? input.body : {}),
|
|
171
|
-
...(input.query !== null && typeof input.query === "object" ? input.query : {}),
|
|
172
|
-
...input.path,
|
|
173
|
-
};
|
|
174
|
-
return client.execute(descriptor.name, flat, {
|
|
175
|
-
...(signal === undefined ? {} : { signal }),
|
|
176
|
-
...(input.settlementTimeoutMs === undefined ? {} : { settlementTimeoutMs: input.settlementTimeoutMs }),
|
|
177
|
-
...(input.headers === undefined ? {} : { headers: Object.fromEntries(input.headers) }),
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function isMutationMethod(method: string): boolean {
|
|
182
|
-
return method !== "GET";
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function listsPreferHeader(surface: SurfaceOperation): boolean {
|
|
186
|
-
return surface.headerParams.some((param) => param.name.toLowerCase() === "prefer");
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function isHeldForApproval(result: unknown): boolean {
|
|
190
|
-
return (
|
|
191
|
-
typeof result === "object" &&
|
|
192
|
-
result !== null &&
|
|
193
|
-
"data" in result &&
|
|
194
|
-
typeof result.data === "object" &&
|
|
195
|
-
result.data !== null &&
|
|
196
|
-
"approvalId" in result.data &&
|
|
197
|
-
"state" in result.data &&
|
|
198
|
-
result.data.state === "proposed"
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function describeMutationOutcome(result: unknown): string {
|
|
203
|
-
if (isHeldForApproval(result)) {
|
|
204
|
-
return "Request held for a second approver";
|
|
205
|
-
}
|
|
206
|
-
if (typeof result !== "object" || result === null || !("statusUrl" in result)) {
|
|
207
|
-
return "Request settled";
|
|
208
|
-
}
|
|
209
|
-
if ("transactionId" in result) {
|
|
210
|
-
return "Request accepted; the transaction is still settling";
|
|
211
|
-
}
|
|
212
|
-
if ("requestId" in result && "state" in result) {
|
|
213
|
-
return "Request accepted; the custody lifecycle is still settling";
|
|
214
|
-
}
|
|
215
|
-
return "Request settled";
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const ANNOTATIONS_BY_METHOD: Readonly<Record<string, McpAnnotations>> = {
|
|
219
|
-
GET: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
220
|
-
DELETE: { readOnlyHint: false, destructiveHint: true },
|
|
221
|
-
};
|
|
222
|
-
const WRITE_ANNOTATIONS: McpAnnotations = { readOnlyHint: false };
|
|
223
|
-
|
|
224
|
-
function mcpMetadataFor(ops: ReadonlyArray<OperationDescriptor>) {
|
|
225
|
-
const primary = pickPrimaryOp(ops);
|
|
226
|
-
return { annotations: ANNOTATIONS_BY_METHOD[primary.method.toUpperCase()] ?? WRITE_ANNOTATIONS };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function makeLeafHandler(ops: ReadonlyArray<OperationDescriptor>) {
|
|
230
|
-
const primary = pickPrimaryOp(ops);
|
|
231
|
-
const { surface } = primary;
|
|
232
|
-
const optionalPathParams = new Set(
|
|
233
|
-
surface.pathParams
|
|
234
|
-
.map((param) => param.name)
|
|
235
|
-
.filter((name) => ops.some((op) => !op.surface.pathParams.some((param) => param.name === name)))
|
|
236
|
-
);
|
|
237
|
-
const mutation = ops.some((op) => isMutationMethod(op.method));
|
|
238
|
-
|
|
239
|
-
const querySchema = ops.map((op) => op.querySchema).find((schema) => schema !== undefined);
|
|
240
|
-
const bodySchema = ops.map((op) => op.bodySchema).find((schema) => schema !== undefined);
|
|
241
|
-
|
|
242
|
-
const argsSchema = buildArgsFromSdkPath(primary.pathSchema, surface, optionalPathParams);
|
|
243
|
-
const optionsSchema = buildOptionsFromSdk(querySchema, bodySchema, surface, {
|
|
244
|
-
...(mutation
|
|
245
|
-
? {
|
|
246
|
-
wait: z
|
|
247
|
-
.boolean()
|
|
248
|
-
.optional()
|
|
249
|
-
.default(true)
|
|
250
|
-
.describe(
|
|
251
|
-
"Ask the platform to wait for settlement before it answers (default: true). Pass --no-wait to take the handle back immediately and follow its statusUrl yourself."
|
|
252
|
-
),
|
|
253
|
-
}
|
|
254
|
-
: {}),
|
|
255
|
-
...(mutation && !ops.some((op) => listsPreferHeader(op.surface))
|
|
256
|
-
? {
|
|
257
|
-
prefer: z
|
|
258
|
-
.string()
|
|
259
|
-
.optional()
|
|
260
|
-
.describe(
|
|
261
|
-
"Accepted and ignored: this operation does not read the Prefer header, so the CLI does not send it."
|
|
262
|
-
),
|
|
263
|
-
}
|
|
264
|
-
: {}),
|
|
265
|
-
});
|
|
266
|
-
const help = combinedHelp(ops);
|
|
267
|
-
|
|
268
|
-
return {
|
|
269
|
-
description: help,
|
|
270
|
-
mcp: mcpMetadataFor(ops),
|
|
271
|
-
hint: buildHint(ops),
|
|
272
|
-
...(argsSchema === undefined ? {} : { args: argsSchema }),
|
|
273
|
-
...(optionsSchema === undefined ? {} : { options: optionsSchema }),
|
|
274
|
-
examples: buildExamples(ops, surface),
|
|
275
|
-
async *run(c: {
|
|
276
|
-
var: { client?: DalpClient };
|
|
277
|
-
args?: unknown;
|
|
278
|
-
options?: unknown;
|
|
279
|
-
request?: Request;
|
|
280
|
-
agent?: boolean;
|
|
281
|
-
displayName?: string;
|
|
282
|
-
error: (error: { code: string; message: string; retryable?: boolean; exitCode?: number }) => unknown;
|
|
283
|
-
}) {
|
|
284
|
-
const refuseWithGuidanceInMessage = ({ cta, ...error }: CliRefusal) =>
|
|
285
|
-
c.error(
|
|
286
|
-
cta === undefined
|
|
287
|
-
? error
|
|
288
|
-
: { ...error, message: `${error.message}\n\n${renderCtaText(c.displayName ?? "dalp", cta)}` }
|
|
289
|
-
);
|
|
290
|
-
const { client } = c.var;
|
|
291
|
-
if (client === undefined) {
|
|
292
|
-
return refuseWithGuidanceInMessage({
|
|
293
|
-
code: "NOT_AUTHENTICATED",
|
|
294
|
-
message: "Not logged in. Run 'dalp login' to authenticate.",
|
|
295
|
-
retryable: true,
|
|
296
|
-
cta: buildErrorCta({
|
|
297
|
-
why: "No stored DALP credentials were found for this CLI command.",
|
|
298
|
-
fix: "Run 'dalp login' against the DALP instance you want to manage, then retry the command.",
|
|
299
|
-
code: "NOT_AUTHENTICATED",
|
|
300
|
-
category: "auth",
|
|
301
|
-
}),
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const argsBag = parseLooseBag(c.args ?? {});
|
|
306
|
-
const optionsBag = parseLooseBag(c.options ?? {});
|
|
307
|
-
const agent = c.agent === true;
|
|
308
|
-
const status = createCliStatus({ agent });
|
|
309
|
-
|
|
310
|
-
const label = ops.map((op) => op.commandPath.join(" ")).join(" | ");
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
const prepareEvent = status.update({
|
|
314
|
-
phase: "prepare",
|
|
315
|
-
message: `Preparing ${label}…`,
|
|
316
|
-
});
|
|
317
|
-
if (prepareEvent !== undefined) {
|
|
318
|
-
yield prepareEvent;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const path = collectPathBag(argsBag);
|
|
322
|
-
const descriptor = selectOperation(ops, path);
|
|
323
|
-
|
|
324
|
-
let validatedPath: ApiBag | undefined = path;
|
|
325
|
-
if (descriptor.pathSchema !== undefined) {
|
|
326
|
-
if (path === undefined || Object.keys(path).length === 0) {
|
|
327
|
-
const requiresPath = descriptor.surface.pathParams.some((param) => param.required);
|
|
328
|
-
if (requiresPath) {
|
|
329
|
-
const failedEvent = status.fail("Missing path parameters");
|
|
330
|
-
if (failedEvent !== undefined) {
|
|
331
|
-
yield failedEvent;
|
|
332
|
-
}
|
|
333
|
-
return refuseWithGuidanceInMessage({
|
|
334
|
-
code: "kit.cli.MISSING_PATH",
|
|
335
|
-
message: `Operation ${descriptor.name} requires path parameters.`,
|
|
336
|
-
retryable: true,
|
|
337
|
-
cta: buildErrorCta({
|
|
338
|
-
why: "This operation needs resource identifiers from the URL path.",
|
|
339
|
-
fix: "Pass the required path arguments (see --help) and retry.",
|
|
340
|
-
code: "kit.cli.MISSING_PATH",
|
|
341
|
-
category: "client",
|
|
342
|
-
}),
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
validatedPath = undefined;
|
|
346
|
-
} else {
|
|
347
|
-
try {
|
|
348
|
-
validatedPath = parseWithSdkSchema<ApiBag>(descriptor.pathSchema, path, "path");
|
|
349
|
-
} catch (error) {
|
|
350
|
-
const failedEvent = status.fail("Invalid path parameters");
|
|
351
|
-
if (failedEvent !== undefined) {
|
|
352
|
-
yield failedEvent;
|
|
353
|
-
}
|
|
354
|
-
const message = error instanceof Error ? error.message : "Invalid path parameters";
|
|
355
|
-
return refuseWithGuidanceInMessage({
|
|
356
|
-
code: "kit.cli.INVALID_PATH",
|
|
357
|
-
message,
|
|
358
|
-
retryable: true,
|
|
359
|
-
cta: buildErrorCta({
|
|
360
|
-
why: "One or more path arguments failed SDK schema validation.",
|
|
361
|
-
fix: "Correct the path arguments to match the API schema (see --help), then retry.",
|
|
362
|
-
code: "kit.cli.INVALID_PATH",
|
|
363
|
-
category: "client",
|
|
364
|
-
}),
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
let validatedQuery: unknown;
|
|
371
|
-
try {
|
|
372
|
-
const rawQuery = collectQueryBag(optionsBag, descriptor.querySchema);
|
|
373
|
-
validatedQuery =
|
|
374
|
-
rawQuery !== undefined && descriptor.querySchema !== undefined
|
|
375
|
-
? parseWithSdkSchema(descriptor.querySchema, rawQuery, "query")
|
|
376
|
-
: rawQuery;
|
|
377
|
-
} catch (error) {
|
|
378
|
-
const failedEvent = status.fail("Invalid query parameters");
|
|
379
|
-
if (failedEvent !== undefined) {
|
|
380
|
-
yield failedEvent;
|
|
381
|
-
}
|
|
382
|
-
const message = error instanceof Error ? error.message : "Invalid query parameters";
|
|
383
|
-
return refuseWithGuidanceInMessage({
|
|
384
|
-
code: "kit.cli.INVALID_QUERY",
|
|
385
|
-
message,
|
|
386
|
-
retryable: true,
|
|
387
|
-
cta: buildErrorCta({
|
|
388
|
-
why: "One or more query options failed SDK schema validation.",
|
|
389
|
-
fix: "Correct the query options to match the API schema (see --help), then retry.",
|
|
390
|
-
code: "kit.cli.INVALID_QUERY",
|
|
391
|
-
category: "client",
|
|
392
|
-
}),
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
let validatedBody: unknown;
|
|
397
|
-
try {
|
|
398
|
-
const rawBody = collectBodyBag(optionsBag, descriptor.bodySchema);
|
|
399
|
-
validatedBody =
|
|
400
|
-
rawBody !== undefined && descriptor.bodySchema !== undefined
|
|
401
|
-
? parseWithSdkSchema(descriptor.bodySchema, rawBody, "body")
|
|
402
|
-
: rawBody;
|
|
403
|
-
} catch (error) {
|
|
404
|
-
const failedEvent = status.fail("Invalid request body");
|
|
405
|
-
if (failedEvent !== undefined) {
|
|
406
|
-
yield failedEvent;
|
|
407
|
-
}
|
|
408
|
-
const message = error instanceof Error ? error.message : "Invalid request body";
|
|
409
|
-
return refuseWithGuidanceInMessage({
|
|
410
|
-
code: "kit.cli.INVALID_BODY",
|
|
411
|
-
message,
|
|
412
|
-
retryable: true,
|
|
413
|
-
cta: buildErrorCta({
|
|
414
|
-
why: "The request body failed SDK schema validation.",
|
|
415
|
-
fix: "Correct --body (or body field options) to match the API schema (see --help), then retry.",
|
|
416
|
-
code: "kit.cli.INVALID_BODY",
|
|
417
|
-
category: "client",
|
|
418
|
-
}),
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const headers = collectHeaders(descriptor.surface, optionsBag);
|
|
423
|
-
const settlementTimeoutMs = mutation && optionsBag["wait"] === false ? 0 : undefined;
|
|
424
|
-
const submitEvent = status.update({
|
|
425
|
-
phase: "submit",
|
|
426
|
-
message: `Submitting ${descriptor.method} ${descriptor.url}…`,
|
|
427
|
-
});
|
|
428
|
-
if (submitEvent !== undefined) {
|
|
429
|
-
yield submitEvent;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const result = await runCommand(
|
|
433
|
-
{ error: refuseWithGuidanceInMessage },
|
|
434
|
-
invokeOperation(
|
|
435
|
-
descriptor,
|
|
436
|
-
client,
|
|
437
|
-
{
|
|
438
|
-
path: validatedPath,
|
|
439
|
-
query: validatedQuery,
|
|
440
|
-
body: validatedBody,
|
|
441
|
-
headers,
|
|
442
|
-
...(settlementTimeoutMs === undefined ? {} : { settlementTimeoutMs }),
|
|
443
|
-
},
|
|
444
|
-
c.request?.signal
|
|
445
|
-
),
|
|
446
|
-
{
|
|
447
|
-
code: "API_OPERATION_FAILED",
|
|
448
|
-
fallbackMessage: "Operation failed.",
|
|
449
|
-
fallbackWhy: "The DALP API rejected the request or the network call failed.",
|
|
450
|
-
fallbackFix: "Verify authentication, organization context, and args/options, then retry.",
|
|
451
|
-
...gatedFeatureContext(descriptor.name),
|
|
452
|
-
}
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
if (typeof result === "object" && result !== null && "code" in result && !("data" in result)) {
|
|
456
|
-
const failedEvent = status.fail("Request failed");
|
|
457
|
-
if (failedEvent !== undefined) {
|
|
458
|
-
const reason: unknown = Reflect.get(result, "message");
|
|
459
|
-
yield typeof reason === "string" && reason !== "" ? { ...failedEvent, message: reason } : failedEvent;
|
|
460
|
-
}
|
|
461
|
-
return result;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (isSseResult(result)) {
|
|
465
|
-
const streamEvent = status.update({
|
|
466
|
-
phase: "stream",
|
|
467
|
-
message: "Streaming response…",
|
|
468
|
-
});
|
|
469
|
-
if (streamEvent !== undefined) {
|
|
470
|
-
yield streamEvent;
|
|
471
|
-
}
|
|
472
|
-
try {
|
|
473
|
-
for await (const chunk of result) {
|
|
474
|
-
yield chunk;
|
|
475
|
-
}
|
|
476
|
-
} catch (streamError) {
|
|
477
|
-
if (streamError instanceof DOMException && streamError.name === "AbortError") {
|
|
478
|
-
throw streamError;
|
|
479
|
-
}
|
|
480
|
-
const rendered = renderSdkErrorForCli(streamError, {
|
|
481
|
-
code: "API_STREAM_FAILED",
|
|
482
|
-
fallbackMessage: "SSE stream failed",
|
|
483
|
-
fallbackWhy: "The Server-Sent Events stream ended with an error before a terminal event.",
|
|
484
|
-
fallbackFix: "Retry the stream command, or poll the matching status endpoint.",
|
|
485
|
-
...gatedFeatureContext(descriptor.name),
|
|
486
|
-
});
|
|
487
|
-
const failed = status.fail(rendered.message);
|
|
488
|
-
if (failed !== undefined) {
|
|
489
|
-
yield failed;
|
|
490
|
-
}
|
|
491
|
-
return refuseWithGuidanceInMessage({
|
|
492
|
-
...rendered,
|
|
493
|
-
exitCode: 1,
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
const done = status.succeed("Stream finished");
|
|
497
|
-
if (done !== undefined) {
|
|
498
|
-
yield done;
|
|
499
|
-
}
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const submittedEvent = status.update({
|
|
504
|
-
phase: "submitted",
|
|
505
|
-
message: "Request accepted by API",
|
|
506
|
-
});
|
|
507
|
-
if (submittedEvent !== undefined) {
|
|
508
|
-
yield submittedEvent;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const done = status.succeed(mutation ? describeMutationOutcome(result) : "Done");
|
|
512
|
-
if (done !== undefined) {
|
|
513
|
-
yield done;
|
|
514
|
-
}
|
|
515
|
-
yield result;
|
|
516
|
-
return withSuccessCta(c, result);
|
|
517
|
-
} catch (error) {
|
|
518
|
-
const rendered = renderSdkErrorForCli(error, {
|
|
519
|
-
code: "API_OPERATION_FAILED",
|
|
520
|
-
fallbackMessage: "Command failed",
|
|
521
|
-
fallbackWhy: "The DALP API rejected the request or the network call failed.",
|
|
522
|
-
fallbackFix: "Verify authentication, organization context, and args/options, then retry.",
|
|
523
|
-
});
|
|
524
|
-
const failed = status.fail(rendered.message);
|
|
525
|
-
if (failed !== undefined) {
|
|
526
|
-
yield failed;
|
|
527
|
-
}
|
|
528
|
-
return refuseWithGuidanceInMessage({
|
|
529
|
-
...rendered,
|
|
530
|
-
exitCode: 1,
|
|
531
|
-
});
|
|
532
|
-
} finally {
|
|
533
|
-
status.stop();
|
|
534
|
-
}
|
|
535
|
-
},
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function ensureChild(parent: CommandTreeNode, name: string): CommandTreeNode {
|
|
540
|
-
const existing = parent.children.get(name);
|
|
541
|
-
if (existing !== undefined) {
|
|
542
|
-
return existing;
|
|
543
|
-
}
|
|
544
|
-
const created: CommandTreeNode = { children: new Map(), operations: [] };
|
|
545
|
-
parent.children.set(name, created);
|
|
546
|
-
return created;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function insertOperation(root: CommandTreeNode, descriptor: OperationDescriptor): void {
|
|
550
|
-
let node = root;
|
|
551
|
-
for (const segment of descriptor.commandPath) {
|
|
552
|
-
node = ensureChild(node, segment);
|
|
553
|
-
}
|
|
554
|
-
node.operations.push(descriptor);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const VERB_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
558
|
-
get: "Read and list resources (maps from GET).",
|
|
559
|
-
create: "Create resources and run POST actions (maps from POST).",
|
|
560
|
-
delete: "Delete resources (maps from DELETE).",
|
|
561
|
-
patch: "Partially update resources (maps from PATCH).",
|
|
562
|
-
replace: "Replace resources (maps from PUT).",
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
function groupDescription(pathPrefix: ReadonlyArray<string>, node: CommandTreeNode): string {
|
|
566
|
-
if (pathPrefix.length === 1) {
|
|
567
|
-
const verb = pathPrefix[0]!;
|
|
568
|
-
if (VERB_DESCRIPTIONS[verb] !== undefined) {
|
|
569
|
-
return VERB_DESCRIPTIONS[verb];
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const queue: Array<CommandTreeNode> = [node];
|
|
574
|
-
const seenTags = new Set<string>();
|
|
575
|
-
while (queue.length > 0) {
|
|
576
|
-
const current = queue.shift()!;
|
|
577
|
-
for (const op of current.operations) {
|
|
578
|
-
for (const tag of op.surface.tags) {
|
|
579
|
-
if (seenTags.has(tag)) {
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
seenTags.add(tag);
|
|
583
|
-
const desc = tagDescription(tag);
|
|
584
|
-
if (desc !== undefined) {
|
|
585
|
-
return desc;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
for (const child of current.children.values()) {
|
|
590
|
-
queue.push(child);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return pathPrefix.join(" ");
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function mountTree(
|
|
598
|
-
parent: Cli.Cli<Record<string, never>, typeof cliVars>,
|
|
599
|
-
nodes: Map<string, CommandTreeNode>,
|
|
600
|
-
pathPrefix: ReadonlyArray<string>
|
|
601
|
-
): void {
|
|
602
|
-
for (const [name, node] of [...nodes.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
603
|
-
const pathHere = [...pathPrefix, name];
|
|
604
|
-
const hasChildren = node.children.size > 0;
|
|
605
|
-
const hasOps = node.operations.length > 0;
|
|
606
|
-
|
|
607
|
-
if (hasOps && !hasChildren) {
|
|
608
|
-
parent.command(name, makeLeafHandler(node.operations));
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const group = Cli.create(name, {
|
|
613
|
-
description: groupDescription(pathHere, node),
|
|
614
|
-
vars: cliVars,
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
if (hasOps) {
|
|
618
|
-
group.command(INDEX_COMMAND, makeLeafHandler(node.operations));
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
if (hasChildren) {
|
|
622
|
-
mountTree(group, node.children, pathHere);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
parent.command(group);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
export function registerGeneratedOperations(
|
|
630
|
-
root: Cli.Cli<Record<string, never>, typeof cliVars>,
|
|
631
|
-
operations: ReadonlyArray<OperationDescriptor> = listCliOperations()
|
|
632
|
-
): void {
|
|
633
|
-
const tree: CommandTreeNode = { children: new Map(), operations: [] };
|
|
634
|
-
|
|
635
|
-
for (const descriptor of operations) {
|
|
636
|
-
insertOperation(tree, descriptor);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
mountTree(root, tree.children, []);
|
|
640
|
-
if (tree.operations.length > 0) {
|
|
641
|
-
root.command(INDEX_COMMAND, makeLeafHandler(tree.operations));
|
|
642
|
-
}
|
|
643
|
-
}
|