specli 0.0.4 → 0.0.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.
- package/cli.ts +13 -4
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2331 -0
- package/dist/cli.js.map +53 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2032 -0
- package/dist/index.js.map +48 -0
- package/dist/src/ai/tools.d.ts +139 -0
- package/dist/src/ai/tools.d.ts.map +1 -0
- package/dist/src/ai/tools.js +1656 -0
- package/dist/src/ai/tools.js.map +45 -0
- package/dist/src/cli/auth-requirements.d.ts +10 -0
- package/dist/src/cli/auth-requirements.d.ts.map +1 -0
- package/dist/src/cli/auth-requirements.js +66 -0
- package/dist/src/cli/auth-requirements.js.map +10 -0
- package/dist/src/cli/auth-schemes.d.ts +22 -0
- package/dist/src/cli/auth-schemes.d.ts.map +1 -0
- package/dist/src/cli/auth-schemes.js +116 -0
- package/dist/src/cli/auth-schemes.js.map +11 -0
- package/dist/src/cli/capabilities.d.ts +32 -0
- package/dist/src/cli/capabilities.d.ts.map +1 -0
- package/dist/src/cli/capabilities.js +45 -0
- package/dist/src/cli/capabilities.js.map +10 -0
- package/dist/src/cli/command-id.d.ts +8 -0
- package/dist/src/cli/command-id.d.ts.map +1 -0
- package/dist/src/cli/command-id.js +18 -0
- package/dist/src/cli/command-id.js.map +11 -0
- package/dist/src/cli/command-index.d.ts +6 -0
- package/dist/src/cli/command-index.d.ts.map +1 -0
- package/dist/src/cli/command-index.js +15 -0
- package/dist/src/cli/command-index.js.map +10 -0
- package/dist/src/cli/command-model.d.ts +40 -0
- package/dist/src/cli/command-model.d.ts.map +1 -0
- package/dist/src/cli/command-model.js +274 -0
- package/dist/src/cli/command-model.js.map +18 -0
- package/dist/src/cli/compile.d.ts +15 -0
- package/dist/src/cli/compile.d.ts.map +1 -0
- package/dist/src/cli/compile.js +146 -0
- package/dist/src/cli/compile.js.map +11 -0
- package/dist/src/cli/crypto.d.ts +2 -0
- package/dist/src/cli/crypto.d.ts.map +1 -0
- package/dist/src/cli/crypto.js +15 -0
- package/dist/src/cli/crypto.js.map +10 -0
- package/dist/src/cli/derive-name.d.ts +9 -0
- package/dist/src/cli/derive-name.d.ts.map +1 -0
- package/dist/src/cli/derive-name.js +70 -0
- package/dist/src/cli/derive-name.js.map +10 -0
- package/dist/src/cli/exec.d.ts +14 -0
- package/dist/src/cli/exec.d.ts.map +1 -0
- package/dist/src/cli/exec.js +2077 -0
- package/dist/src/cli/exec.js.map +49 -0
- package/dist/src/cli/main.d.ts +10 -0
- package/dist/src/cli/main.d.ts.map +1 -0
- package/dist/src/cli/main.js +2032 -0
- package/dist/src/cli/main.js.map +48 -0
- package/dist/src/cli/naming.d.ts +12 -0
- package/dist/src/cli/naming.d.ts.map +1 -0
- package/dist/src/cli/naming.js +216 -0
- package/dist/src/cli/naming.js.map +12 -0
- package/dist/src/cli/operations.d.ts +3 -0
- package/dist/src/cli/operations.d.ts.map +1 -0
- package/dist/src/cli/operations.js +103 -0
- package/dist/src/cli/operations.js.map +10 -0
- package/dist/src/cli/params.d.ts +19 -0
- package/dist/src/cli/params.d.ts.map +1 -0
- package/dist/src/cli/params.js +79 -0
- package/dist/src/cli/params.js.map +12 -0
- package/dist/src/cli/pluralize.d.ts +2 -0
- package/dist/src/cli/pluralize.d.ts.map +1 -0
- package/dist/src/cli/pluralize.js +43 -0
- package/dist/src/cli/pluralize.js.map +10 -0
- package/dist/src/cli/positional.d.ts +19 -0
- package/dist/src/cli/positional.d.ts.map +1 -0
- package/dist/src/cli/positional.js +39 -0
- package/dist/src/cli/positional.js.map +10 -0
- package/dist/src/cli/request-body.d.ts +20 -0
- package/dist/src/cli/request-body.d.ts.map +1 -0
- package/dist/src/cli/request-body.js +82 -0
- package/dist/src/cli/request-body.js.map +12 -0
- package/dist/src/cli/runtime/argv.d.ts +3 -0
- package/dist/src/cli/runtime/argv.d.ts.map +1 -0
- package/dist/src/cli/runtime/argv.js +22 -0
- package/dist/src/cli/runtime/argv.js.map +10 -0
- package/dist/src/cli/runtime/auth/resolve.d.ts +9 -0
- package/dist/src/cli/runtime/auth/resolve.d.ts.map +1 -0
- package/dist/src/cli/runtime/auth/resolve.js +38 -0
- package/dist/src/cli/runtime/auth/resolve.js.map +10 -0
- package/dist/src/cli/runtime/body-flags.d.ts +41 -0
- package/dist/src/cli/runtime/body-flags.d.ts.map +1 -0
- package/dist/src/cli/runtime/body-flags.js +86 -0
- package/dist/src/cli/runtime/body-flags.js.map +10 -0
- package/dist/src/cli/runtime/body.d.ts +15 -0
- package/dist/src/cli/runtime/body.d.ts.map +1 -0
- package/dist/src/cli/runtime/body.js +40 -0
- package/dist/src/cli/runtime/body.js.map +11 -0
- package/dist/src/cli/runtime/collect.d.ts +2 -0
- package/dist/src/cli/runtime/collect.d.ts.map +1 -0
- package/dist/src/cli/runtime/collect.js +9 -0
- package/dist/src/cli/runtime/collect.js.map +10 -0
- package/dist/src/cli/runtime/compat.d.ts +35 -0
- package/dist/src/cli/runtime/compat.d.ts.map +1 -0
- package/dist/src/cli/runtime/compat.js +62 -0
- package/dist/src/cli/runtime/compat.js.map +10 -0
- package/dist/src/cli/runtime/context.d.ts +16 -0
- package/dist/src/cli/runtime/context.d.ts.map +1 -0
- package/dist/src/cli/runtime/context.js +936 -0
- package/dist/src/cli/runtime/context.js.map +32 -0
- package/dist/src/cli/runtime/execute.d.ts +33 -0
- package/dist/src/cli/runtime/execute.d.ts.map +1 -0
- package/dist/src/cli/runtime/execute.js +670 -0
- package/dist/src/cli/runtime/execute.js.map +22 -0
- package/dist/src/cli/runtime/generated.d.ts +14 -0
- package/dist/src/cli/runtime/generated.d.ts.map +1 -0
- package/dist/src/cli/runtime/generated.js +869 -0
- package/dist/src/cli/runtime/generated.js.map +23 -0
- package/dist/src/cli/runtime/headers.d.ts +9 -0
- package/dist/src/cli/runtime/headers.d.ts.map +1 -0
- package/dist/src/cli/runtime/headers.js +36 -0
- package/dist/src/cli/runtime/headers.js.map +10 -0
- package/dist/src/cli/runtime/index.d.ts +4 -0
- package/dist/src/cli/runtime/index.d.ts.map +1 -0
- package/dist/src/cli/runtime/index.js +1808 -0
- package/dist/src/cli/runtime/index.js.map +46 -0
- package/dist/src/cli/runtime/profile/secrets.d.ts +25 -0
- package/dist/src/cli/runtime/profile/secrets.d.ts.map +1 -0
- package/dist/src/cli/runtime/profile/secrets.js +51 -0
- package/dist/src/cli/runtime/profile/secrets.js.map +11 -0
- package/dist/src/cli/runtime/profile/store.d.ts +15 -0
- package/dist/src/cli/runtime/profile/store.d.ts.map +1 -0
- package/dist/src/cli/runtime/profile/store.js +102 -0
- package/dist/src/cli/runtime/profile/store.js.map +11 -0
- package/dist/src/cli/runtime/request.d.ts +36 -0
- package/dist/src/cli/runtime/request.d.ts.map +1 -0
- package/dist/src/cli/runtime/request.js +571 -0
- package/dist/src/cli/runtime/request.js.map +21 -0
- package/dist/src/cli/runtime/server-url.d.ts +8 -0
- package/dist/src/cli/runtime/server-url.d.ts.map +1 -0
- package/dist/src/cli/runtime/server-url.js +55 -0
- package/dist/src/cli/runtime/server-url.js.map +11 -0
- package/dist/src/cli/runtime/template.d.ts +5 -0
- package/dist/src/cli/runtime/template.d.ts.map +1 -0
- package/dist/src/cli/runtime/template.js +29 -0
- package/dist/src/cli/runtime/template.js.map +10 -0
- package/dist/src/cli/runtime/validate/ajv.d.ts +3 -0
- package/dist/src/cli/runtime/validate/ajv.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/ajv.js +17 -0
- package/dist/src/cli/runtime/validate/ajv.js.map +10 -0
- package/dist/src/cli/runtime/validate/coerce.d.ts +4 -0
- package/dist/src/cli/runtime/validate/coerce.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/coerce.js +60 -0
- package/dist/src/cli/runtime/validate/coerce.js.map +10 -0
- package/dist/src/cli/runtime/validate/error.d.ts +3 -0
- package/dist/src/cli/runtime/validate/error.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/error.js +21 -0
- package/dist/src/cli/runtime/validate/error.js.map +10 -0
- package/dist/src/cli/runtime/validate/index.d.ts +5 -0
- package/dist/src/cli/runtime/validate/index.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/index.js +122 -0
- package/dist/src/cli/runtime/validate/index.js.map +13 -0
- package/dist/src/cli/runtime/validate/schema.d.ts +9 -0
- package/dist/src/cli/runtime/validate/schema.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/schema.js +36 -0
- package/dist/src/cli/runtime/validate/schema.js.map +10 -0
- package/dist/src/cli/schema-shape.d.ts +5 -0
- package/dist/src/cli/schema-shape.d.ts.map +1 -0
- package/dist/src/cli/schema-shape.js +41 -0
- package/dist/src/cli/schema-shape.js.map +10 -0
- package/dist/src/cli/schema.d.ts +30 -0
- package/dist/src/cli/schema.d.ts.map +1 -0
- package/dist/src/cli/schema.js +38 -0
- package/dist/src/cli/schema.js.map +10 -0
- package/dist/src/cli/server.d.ts +16 -0
- package/dist/src/cli/server.d.ts.map +1 -0
- package/dist/src/cli/server.js +64 -0
- package/dist/src/cli/server.js.map +11 -0
- package/dist/src/cli/spec-id.d.ts +3 -0
- package/dist/src/cli/spec-id.d.ts.map +1 -0
- package/dist/src/cli/spec-id.js +21 -0
- package/dist/src/cli/spec-id.js.map +11 -0
- package/dist/src/cli/spec-loader.d.ts +7 -0
- package/dist/src/cli/spec-loader.d.ts.map +1 -0
- package/dist/src/cli/spec-loader.js +110 -0
- package/dist/src/cli/spec-loader.js.map +15 -0
- package/dist/src/cli/stable-json.d.ts +4 -0
- package/dist/src/cli/stable-json.d.ts.map +1 -0
- package/dist/src/cli/stable-json.js +35 -0
- package/dist/src/cli/stable-json.js.map +10 -0
- package/dist/src/cli/strings.d.ts +3 -0
- package/dist/src/cli/strings.d.ts.map +1 -0
- package/dist/src/cli/strings.js +16 -0
- package/dist/src/cli/strings.js.map +10 -0
- package/dist/src/cli/types.d.ts +53 -0
- package/dist/src/cli/types.d.ts.map +1 -0
- package/dist/src/cli/types.js +9 -0
- package/dist/src/cli/types.js.map +10 -0
- package/package.json +31 -4
- package/src/ai/tools.ts +211 -0
- package/src/cli/main.ts +73 -163
- package/src/cli/runtime/auth/resolve.ts +20 -0
- package/src/cli/runtime/body.ts +3 -3
- package/src/cli/runtime/compat.ts +89 -0
- package/src/cli/runtime/execute.ts +98 -39
- package/src/cli/runtime/generated.ts +111 -4
- package/src/cli/runtime/profile/secrets.ts +42 -1
- package/src/cli/runtime/profile/store.ts +15 -13
- package/src/cli/runtime/request.ts +22 -11
- package/src/cli/spec-loader.ts +2 -2
package/src/cli/runtime/body.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseYamlContent, readFileText } from "./compat.ts";
|
|
2
2
|
|
|
3
3
|
export type BodyInput =
|
|
4
4
|
| { kind: "none" }
|
|
@@ -11,7 +11,7 @@ export async function loadBody(
|
|
|
11
11
|
if (input.kind === "none") return undefined;
|
|
12
12
|
if (input.kind === "data") return { raw: input.data };
|
|
13
13
|
|
|
14
|
-
const text = await
|
|
14
|
+
const text = await readFileText(input.path);
|
|
15
15
|
return { raw: text };
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -20,5 +20,5 @@ export function parseBodyAsJsonOrYaml(text: string): unknown {
|
|
|
20
20
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
21
21
|
return JSON.parse(text);
|
|
22
22
|
}
|
|
23
|
-
return
|
|
23
|
+
return parseYamlContent(text);
|
|
24
24
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-runtime compatibility utilities for Bun and Node.js
|
|
3
|
+
*
|
|
4
|
+
* This module provides abstractions over Bun-specific APIs to allow
|
|
5
|
+
* the exec command to run in Node.js while compile remains Bun-only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detect if we're running in Bun
|
|
13
|
+
*/
|
|
14
|
+
export const isBun = typeof globalThis.Bun !== "undefined";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read a file's text content - works in both Bun and Node.js
|
|
18
|
+
*/
|
|
19
|
+
export async function readFileText(path: string): Promise<string> {
|
|
20
|
+
if (isBun) {
|
|
21
|
+
return Bun.file(path).text();
|
|
22
|
+
}
|
|
23
|
+
return readFileSync(path, "utf-8");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a file exists - works in both Bun and Node.js
|
|
28
|
+
*/
|
|
29
|
+
export async function fileExists(path: string): Promise<boolean> {
|
|
30
|
+
if (isBun) {
|
|
31
|
+
return Bun.file(path).exists();
|
|
32
|
+
}
|
|
33
|
+
return existsSync(path);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Write text to a file - works in both Bun and Node.js
|
|
38
|
+
*/
|
|
39
|
+
export async function writeFileText(
|
|
40
|
+
path: string,
|
|
41
|
+
content: string,
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
if (isBun) {
|
|
44
|
+
await Bun.write(path, content);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
writeFileSync(path, content, "utf-8");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create directory recursively - works in both Bun and Node.js
|
|
52
|
+
*/
|
|
53
|
+
export async function mkdirp(path: string): Promise<void> {
|
|
54
|
+
if (isBun) {
|
|
55
|
+
await Bun.$`mkdir -p ${path}`;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
mkdirSync(path, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse YAML content - works in both Bun and Node.js
|
|
63
|
+
*/
|
|
64
|
+
export function parseYamlContent(text: string): unknown {
|
|
65
|
+
if (isBun) {
|
|
66
|
+
const { YAML } = globalThis.Bun;
|
|
67
|
+
return YAML.parse(text);
|
|
68
|
+
}
|
|
69
|
+
return parseYaml(text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read from stdin - works in both Bun and Node.js
|
|
74
|
+
*/
|
|
75
|
+
export async function readStdinText(): Promise<string> {
|
|
76
|
+
if (isBun) {
|
|
77
|
+
return Bun.stdin.text();
|
|
78
|
+
}
|
|
79
|
+
// Node.js stdin reading
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
let data = "";
|
|
82
|
+
process.stdin.setEncoding("utf8");
|
|
83
|
+
process.stdin.on("data", (chunk) => {
|
|
84
|
+
data += chunk;
|
|
85
|
+
});
|
|
86
|
+
process.stdin.on("end", () => resolve(data));
|
|
87
|
+
process.stdin.on("error", reject);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -13,51 +13,108 @@ export type ExecuteInput = {
|
|
|
13
13
|
specId: string;
|
|
14
14
|
embeddedDefaults?: EmbeddedDefaults;
|
|
15
15
|
bodyFlagDefs?: BodyFlagDef[];
|
|
16
|
+
/** Resource name for error messages (e.g. "plans") */
|
|
17
|
+
resourceName?: string;
|
|
16
18
|
};
|
|
17
19
|
|
|
20
|
+
export type ExecuteResult = {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
status: number;
|
|
23
|
+
body: unknown;
|
|
24
|
+
curl: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format an error message with a help hint.
|
|
29
|
+
*/
|
|
30
|
+
function formatError(
|
|
31
|
+
message: string,
|
|
32
|
+
resourceName: string | undefined,
|
|
33
|
+
actionName: string,
|
|
34
|
+
): string {
|
|
35
|
+
const helpCmd = resourceName
|
|
36
|
+
? `${resourceName} ${actionName} --help`
|
|
37
|
+
: `${actionName} --help`;
|
|
38
|
+
return `${message}\n\nRun '${helpCmd}' to see available options.`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Execute an action and return the result as data.
|
|
43
|
+
* This is the core execution function used by both CLI and programmatic API.
|
|
44
|
+
*/
|
|
45
|
+
export async function execute(
|
|
46
|
+
input: Omit<ExecuteInput, "resourceName">,
|
|
47
|
+
): Promise<ExecuteResult> {
|
|
48
|
+
const { request, curl } = await buildRequest({
|
|
49
|
+
specId: input.specId,
|
|
50
|
+
action: input.action,
|
|
51
|
+
positionalValues: input.positionalValues,
|
|
52
|
+
flagValues: input.flagValues,
|
|
53
|
+
globals: input.globals,
|
|
54
|
+
servers: input.servers,
|
|
55
|
+
authSchemes: input.authSchemes,
|
|
56
|
+
embeddedDefaults: input.embeddedDefaults,
|
|
57
|
+
bodyFlagDefs: input.bodyFlagDefs,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const res = await fetch(request);
|
|
61
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
62
|
+
|
|
63
|
+
const text = await res.text();
|
|
64
|
+
let body: unknown = text;
|
|
65
|
+
|
|
66
|
+
if (contentType.includes("json") && text) {
|
|
67
|
+
try {
|
|
68
|
+
body = JSON.parse(text);
|
|
69
|
+
} catch {
|
|
70
|
+
// keep as text
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
ok: res.ok,
|
|
76
|
+
status: res.status,
|
|
77
|
+
body,
|
|
78
|
+
curl,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Execute an action and write output to stdout/stderr.
|
|
84
|
+
* This is the CLI-facing wrapper around execute().
|
|
85
|
+
*/
|
|
18
86
|
export async function executeAction(input: ExecuteInput): Promise<void> {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
specId: input.specId,
|
|
22
|
-
action: input.action,
|
|
23
|
-
positionalValues: input.positionalValues,
|
|
24
|
-
flagValues: input.flagValues,
|
|
25
|
-
globals: input.globals,
|
|
26
|
-
servers: input.servers,
|
|
27
|
-
authSchemes: input.authSchemes,
|
|
28
|
-
embeddedDefaults: input.embeddedDefaults,
|
|
29
|
-
bodyFlagDefs: input.bodyFlagDefs,
|
|
30
|
-
});
|
|
87
|
+
const actionName = input.action.action;
|
|
88
|
+
const resourceName = input.resourceName;
|
|
31
89
|
|
|
90
|
+
try {
|
|
32
91
|
if (input.globals.curl) {
|
|
92
|
+
const { curl } = await buildRequest({
|
|
93
|
+
specId: input.specId,
|
|
94
|
+
action: input.action,
|
|
95
|
+
positionalValues: input.positionalValues,
|
|
96
|
+
flagValues: input.flagValues,
|
|
97
|
+
globals: input.globals,
|
|
98
|
+
servers: input.servers,
|
|
99
|
+
authSchemes: input.authSchemes,
|
|
100
|
+
embeddedDefaults: input.embeddedDefaults,
|
|
101
|
+
bodyFlagDefs: input.bodyFlagDefs,
|
|
102
|
+
});
|
|
33
103
|
process.stdout.write(`${curl}\n`);
|
|
34
104
|
return;
|
|
35
105
|
}
|
|
36
106
|
|
|
37
|
-
const
|
|
38
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
39
|
-
const status = res.status;
|
|
40
|
-
|
|
41
|
-
const text = await res.text();
|
|
42
|
-
let body: unknown = text;
|
|
43
|
-
let parsedJson: unknown | undefined;
|
|
107
|
+
const result = await execute(input);
|
|
44
108
|
|
|
45
|
-
if (
|
|
46
|
-
try {
|
|
47
|
-
parsedJson = text ? JSON.parse(text) : null;
|
|
48
|
-
body = parsedJson;
|
|
49
|
-
} catch {
|
|
50
|
-
// keep as text
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!res.ok) {
|
|
109
|
+
if (!result.ok) {
|
|
55
110
|
if (input.globals.json) {
|
|
56
|
-
process.stdout.write(
|
|
111
|
+
process.stdout.write(
|
|
112
|
+
`${JSON.stringify({ status: result.status, body: result.body })}\n`,
|
|
113
|
+
);
|
|
57
114
|
} else {
|
|
58
|
-
process.stderr.write(`HTTP ${status}\n`);
|
|
115
|
+
process.stderr.write(`HTTP ${result.status}\n`);
|
|
59
116
|
process.stderr.write(
|
|
60
|
-
`${typeof body === "string" ? body : JSON.stringify(body, null, 2)}\n`,
|
|
117
|
+
`${typeof result.body === "string" ? result.body : JSON.stringify(result.body, null, 2)}\n`,
|
|
61
118
|
);
|
|
62
119
|
}
|
|
63
120
|
process.exitCode = 1;
|
|
@@ -65,21 +122,23 @@ export async function executeAction(input: ExecuteInput): Promise<void> {
|
|
|
65
122
|
}
|
|
66
123
|
|
|
67
124
|
if (input.globals.json) {
|
|
68
|
-
process.stdout.write(`${JSON.stringify(body)}\n`);
|
|
125
|
+
process.stdout.write(`${JSON.stringify(result.body)}\n`);
|
|
69
126
|
return;
|
|
70
127
|
}
|
|
71
128
|
|
|
72
129
|
// default (human + agent readable)
|
|
73
|
-
if (typeof
|
|
74
|
-
process.stdout.write(
|
|
130
|
+
if (typeof result.body === "string") {
|
|
131
|
+
process.stdout.write(result.body);
|
|
132
|
+
if (!result.body.endsWith("\n")) process.stdout.write("\n");
|
|
75
133
|
} else {
|
|
76
|
-
process.stdout.write(
|
|
77
|
-
if (!text.endsWith("\n")) process.stdout.write("\n");
|
|
134
|
+
process.stdout.write(`${JSON.stringify(result.body, null, 2)}\n`);
|
|
78
135
|
}
|
|
79
136
|
} catch (err) {
|
|
80
|
-
const
|
|
137
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
138
|
+
const message = formatError(rawMessage, resourceName, actionName);
|
|
139
|
+
|
|
81
140
|
if (input.globals.json) {
|
|
82
|
-
process.stdout.write(`${JSON.stringify({ error:
|
|
141
|
+
process.stdout.write(`${JSON.stringify({ error: rawMessage })}\n`);
|
|
83
142
|
} else {
|
|
84
143
|
process.stderr.write(`error: ${message}\n`);
|
|
85
144
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
|
|
3
3
|
import type { AuthScheme } from "../auth-schemes.ts";
|
|
4
|
-
import type { CommandModel } from "../command-model.ts";
|
|
4
|
+
import type { CommandAction, CommandModel } from "../command-model.ts";
|
|
5
5
|
import type { ServerInfo } from "../server.ts";
|
|
6
6
|
|
|
7
7
|
import { type BodyFlagDef, generateBodyFlags } from "./body-flags.ts";
|
|
@@ -9,6 +9,106 @@ import { executeAction } from "./execute.ts";
|
|
|
9
9
|
import type { EmbeddedDefaults } from "./request.ts";
|
|
10
10
|
import { coerceArrayInput, coerceValue } from "./validate/index.ts";
|
|
11
11
|
|
|
12
|
+
// Flag type from CommandAction
|
|
13
|
+
type CommandFlag = CommandAction["flags"][number];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format help output that is clear for both humans and AI agents.
|
|
17
|
+
* Groups options into Required, Optional, and Global sections.
|
|
18
|
+
*/
|
|
19
|
+
function formatCustomHelp(
|
|
20
|
+
cmd: Command,
|
|
21
|
+
action: CommandAction,
|
|
22
|
+
operationFlags: CommandFlag[],
|
|
23
|
+
bodyFlagDefs: BodyFlagDef[],
|
|
24
|
+
): string {
|
|
25
|
+
const lines: string[] = [];
|
|
26
|
+
const cmdName = cmd.name();
|
|
27
|
+
const parentName = cmd.parent?.name() ?? "";
|
|
28
|
+
const fullCmd = parentName ? `${parentName} ${cmdName}` : cmdName;
|
|
29
|
+
|
|
30
|
+
// Usage line
|
|
31
|
+
const positionals = action.positionals.map((p) => `<${p.name}>`).join(" ");
|
|
32
|
+
const usageSuffix = positionals ? ` ${positionals}` : "";
|
|
33
|
+
lines.push(`Usage: ${fullCmd}${usageSuffix} [options]`);
|
|
34
|
+
lines.push("");
|
|
35
|
+
|
|
36
|
+
// Description
|
|
37
|
+
const desc =
|
|
38
|
+
action.summary ?? action.description ?? `${action.method} ${action.path}`;
|
|
39
|
+
lines.push(desc);
|
|
40
|
+
lines.push("");
|
|
41
|
+
|
|
42
|
+
// Collect all options into categories
|
|
43
|
+
const requiredOpts: string[] = [];
|
|
44
|
+
const optionalOpts: string[] = [];
|
|
45
|
+
|
|
46
|
+
// Format a single option line
|
|
47
|
+
const formatOpt = (
|
|
48
|
+
flag: string,
|
|
49
|
+
type: string,
|
|
50
|
+
desc: string,
|
|
51
|
+
required: boolean,
|
|
52
|
+
): string => {
|
|
53
|
+
const typeStr = type === "boolean" ? "" : ` <${type}>`;
|
|
54
|
+
const reqMarker = required ? " (required)" : "";
|
|
55
|
+
return ` ${flag}${typeStr}${reqMarker}\n ${desc}`;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Operation flags (query/header/path params)
|
|
59
|
+
for (const f of operationFlags) {
|
|
60
|
+
const type = f.type === "array" ? `${f.itemType ?? "string"}[]` : f.type;
|
|
61
|
+
const line = formatOpt(
|
|
62
|
+
f.flag,
|
|
63
|
+
type,
|
|
64
|
+
f.description ?? `${f.in} parameter`,
|
|
65
|
+
f.required,
|
|
66
|
+
);
|
|
67
|
+
if (f.required) {
|
|
68
|
+
requiredOpts.push(line);
|
|
69
|
+
} else {
|
|
70
|
+
optionalOpts.push(line);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Body flags
|
|
75
|
+
for (const def of bodyFlagDefs) {
|
|
76
|
+
const line = formatOpt(def.flag, def.type, def.description, def.required);
|
|
77
|
+
if (def.required) {
|
|
78
|
+
requiredOpts.push(line);
|
|
79
|
+
} else {
|
|
80
|
+
optionalOpts.push(line);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Required options section
|
|
85
|
+
if (requiredOpts.length > 0) {
|
|
86
|
+
lines.push("Required:");
|
|
87
|
+
lines.push(...requiredOpts);
|
|
88
|
+
lines.push("");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Optional options section
|
|
92
|
+
if (optionalOpts.length > 0) {
|
|
93
|
+
lines.push("Options:");
|
|
94
|
+
lines.push(...optionalOpts);
|
|
95
|
+
lines.push("");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Global options (always available)
|
|
99
|
+
lines.push("Global:");
|
|
100
|
+
lines.push(" --curl\n Print curl command instead of executing");
|
|
101
|
+
lines.push(" --json\n Output response as JSON");
|
|
102
|
+
lines.push(" --server <url>\n Override the API server URL");
|
|
103
|
+
lines.push(
|
|
104
|
+
" --bearer-token <token>\n Provide auth token (or use 'login' command)",
|
|
105
|
+
);
|
|
106
|
+
lines.push(" -h, --help\n Show this help message");
|
|
107
|
+
lines.push("");
|
|
108
|
+
|
|
109
|
+
return lines.join("\n");
|
|
110
|
+
}
|
|
111
|
+
|
|
12
112
|
export type GeneratedCliContext = {
|
|
13
113
|
servers: ServerInfo[];
|
|
14
114
|
authSchemes: AuthScheme[];
|
|
@@ -78,11 +178,11 @@ export function addGeneratedCommands(
|
|
|
78
178
|
}
|
|
79
179
|
|
|
80
180
|
// Collect reserved flags: operation params + --curl
|
|
81
|
-
const
|
|
82
|
-
const reservedFlags = new Set([...
|
|
181
|
+
const operationFlagSet = new Set(action.flags.map((f) => f.flag));
|
|
182
|
+
const reservedFlags = new Set([...operationFlagSet, "--curl"]);
|
|
83
183
|
|
|
84
184
|
// Only --curl is a built-in flag (for debugging)
|
|
85
|
-
if (!
|
|
185
|
+
if (!operationFlagSet.has("--curl")) {
|
|
86
186
|
cmd.option("--curl", "Print curl command without sending");
|
|
87
187
|
}
|
|
88
188
|
|
|
@@ -106,6 +206,12 @@ export function addGeneratedCommands(
|
|
|
106
206
|
}
|
|
107
207
|
}
|
|
108
208
|
|
|
209
|
+
// Custom help output for better agent/human readability
|
|
210
|
+
cmd.configureHelp({
|
|
211
|
+
formatHelp: () =>
|
|
212
|
+
formatCustomHelp(cmd, action, action.flags, bodyFlagDefs),
|
|
213
|
+
});
|
|
214
|
+
|
|
109
215
|
// Commander passes positional args and then the Command instance as last arg.
|
|
110
216
|
cmd.action(async (...args) => {
|
|
111
217
|
const command = args[args.length - 1];
|
|
@@ -128,6 +234,7 @@ export function addGeneratedCommands(
|
|
|
128
234
|
specId: context.specId,
|
|
129
235
|
embeddedDefaults: context.embeddedDefaults,
|
|
130
236
|
bodyFlagDefs,
|
|
237
|
+
resourceName: resource.resource,
|
|
131
238
|
});
|
|
132
239
|
});
|
|
133
240
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isBun } from "../compat.ts";
|
|
2
|
+
|
|
3
|
+
const bunLiteral = "bun" as const;
|
|
2
4
|
|
|
3
5
|
export type SecretKey = {
|
|
4
6
|
service: string;
|
|
@@ -16,27 +18,66 @@ export function tokenSecretKey(specId: string, profile: string): SecretKey {
|
|
|
16
18
|
};
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Store a token securely.
|
|
23
|
+
* In Bun: uses the native secrets store (system keychain)
|
|
24
|
+
* In Node.js: secrets are not supported, warns user
|
|
25
|
+
*/
|
|
19
26
|
export async function setToken(
|
|
20
27
|
specId: string,
|
|
21
28
|
profile: string,
|
|
22
29
|
token: string,
|
|
23
30
|
): Promise<void> {
|
|
31
|
+
if (!isBun) {
|
|
32
|
+
console.warn(
|
|
33
|
+
"Warning: Secure token storage requires Bun. Token will not be persisted.",
|
|
34
|
+
);
|
|
35
|
+
console.warn(
|
|
36
|
+
"Use --bearer-token <token> flag instead when running with Node.js.",
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { secrets } = await import(bunLiteral);
|
|
24
42
|
const key = tokenSecretKey(specId, profile);
|
|
25
43
|
await secrets.set({ service: key.service, name: key.name, value: token });
|
|
26
44
|
}
|
|
27
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Retrieve a stored token.
|
|
48
|
+
* In Bun: retrieves from the native secrets store
|
|
49
|
+
* In Node.js: returns null (secrets not supported)
|
|
50
|
+
*/
|
|
28
51
|
export async function getToken(
|
|
29
52
|
specId: string,
|
|
30
53
|
profile: string,
|
|
31
54
|
): Promise<string | null> {
|
|
55
|
+
if (!isBun) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { secrets } = await import(bunLiteral);
|
|
32
60
|
const key = tokenSecretKey(specId, profile);
|
|
33
61
|
return await secrets.get({ service: key.service, name: key.name });
|
|
34
62
|
}
|
|
35
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Delete a stored token.
|
|
66
|
+
* In Bun: removes from the native secrets store
|
|
67
|
+
* In Node.js: returns false (secrets not supported)
|
|
68
|
+
*/
|
|
36
69
|
export async function deleteToken(
|
|
37
70
|
specId: string,
|
|
38
71
|
profile: string,
|
|
39
72
|
): Promise<boolean> {
|
|
73
|
+
if (!isBun) {
|
|
74
|
+
console.warn(
|
|
75
|
+
"Warning: Secure token storage requires Bun. No token to delete.",
|
|
76
|
+
);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { secrets } = await import(bunLiteral);
|
|
40
81
|
const key = tokenSecretKey(specId, profile);
|
|
41
82
|
return await secrets.delete({ service: key.service, name: key.name });
|
|
42
83
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
fileExists,
|
|
3
|
+
mkdirp,
|
|
4
|
+
parseYamlContent,
|
|
5
|
+
readFileText,
|
|
6
|
+
writeFileText,
|
|
7
|
+
} from "../compat.ts";
|
|
2
8
|
|
|
3
9
|
export type Profile = {
|
|
4
10
|
name: string;
|
|
@@ -32,21 +38,17 @@ export async function readProfiles(): Promise<ProfilesFile> {
|
|
|
32
38
|
const jsonPath = configPathJson();
|
|
33
39
|
const yamlPath = configPathYaml();
|
|
34
40
|
|
|
35
|
-
const
|
|
36
|
-
const
|
|
41
|
+
const jsonExists = await fileExists(jsonPath);
|
|
42
|
+
const yamlExists = await fileExists(yamlPath);
|
|
37
43
|
|
|
38
|
-
const
|
|
39
|
-
? jsonFile
|
|
40
|
-
: (await yamlFile.exists())
|
|
41
|
-
? yamlFile
|
|
42
|
-
: null;
|
|
44
|
+
const filePath = jsonExists ? jsonPath : yamlExists ? yamlPath : null;
|
|
43
45
|
|
|
44
|
-
if (!
|
|
46
|
+
if (!filePath) return { profiles: [] };
|
|
45
47
|
|
|
46
|
-
const text = await
|
|
48
|
+
const text = await readFileText(filePath);
|
|
47
49
|
let parsed: unknown;
|
|
48
50
|
try {
|
|
49
|
-
parsed =
|
|
51
|
+
parsed = parseYamlContent(text) as unknown;
|
|
50
52
|
} catch {
|
|
51
53
|
parsed = JSON.parse(text) as unknown;
|
|
52
54
|
}
|
|
@@ -70,8 +72,8 @@ export async function readProfiles(): Promise<ProfilesFile> {
|
|
|
70
72
|
|
|
71
73
|
export async function writeProfiles(data: ProfilesFile): Promise<void> {
|
|
72
74
|
const dir = configDir();
|
|
73
|
-
await
|
|
74
|
-
await
|
|
75
|
+
await mkdirp(dir);
|
|
76
|
+
await writeFileText(configPathJson(), JSON.stringify(data, null, 2));
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
export function getProfile(
|
|
@@ -26,8 +26,6 @@ export type RuntimeGlobals = {
|
|
|
26
26
|
username?: string;
|
|
27
27
|
password?: string;
|
|
28
28
|
apiKey?: string;
|
|
29
|
-
|
|
30
|
-
profile?: string;
|
|
31
29
|
};
|
|
32
30
|
|
|
33
31
|
function parseKeyValuePairs(
|
|
@@ -155,8 +153,10 @@ export type BuildRequestInput = {
|
|
|
155
153
|
export async function buildRequest(
|
|
156
154
|
input: BuildRequestInput,
|
|
157
155
|
): Promise<{ request: Request; curl: string }> {
|
|
156
|
+
// Always use the "default" profile for simplicity
|
|
157
|
+
const defaultProfileName = "default";
|
|
158
158
|
const profilesFile = await readProfiles();
|
|
159
|
-
const profile = getProfile(profilesFile,
|
|
159
|
+
const profile = getProfile(profilesFile, defaultProfileName);
|
|
160
160
|
const embedded = input.embeddedDefaults;
|
|
161
161
|
|
|
162
162
|
// Merge server vars: CLI flags override embedded defaults
|
|
@@ -275,9 +275,18 @@ export async function buildRequest(
|
|
|
275
275
|
|
|
276
276
|
const schema = input.action.requestBodySchema;
|
|
277
277
|
|
|
278
|
+
// Check if there are any required fields in the body
|
|
279
|
+
const requiredFields = bodyFlagDefs.filter((d) => d.required);
|
|
280
|
+
|
|
278
281
|
if (!hasBodyFlags) {
|
|
282
|
+
if (requiredFields.length > 0) {
|
|
283
|
+
// Error: user must provide required fields
|
|
284
|
+
const flagList = requiredFields.map((d) => `--${d.path.join(".")}`);
|
|
285
|
+
throw new Error(`Required: ${flagList.join(", ")}`);
|
|
286
|
+
}
|
|
287
|
+
// No required fields - send empty body if body is required, otherwise skip
|
|
279
288
|
if (input.action.requestBody.required) {
|
|
280
|
-
|
|
289
|
+
body = "{}";
|
|
281
290
|
}
|
|
282
291
|
} else {
|
|
283
292
|
if (!contentType?.includes("json")) {
|
|
@@ -292,9 +301,8 @@ export async function buildRequest(
|
|
|
292
301
|
);
|
|
293
302
|
const missing = findMissingRequired(input.flagValues, bodyFlagDefs);
|
|
294
303
|
if (missing.length > 0) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
);
|
|
304
|
+
const missingFlags = missing.map((m) => `--${m}`).join(", ");
|
|
305
|
+
throw new Error(`Missing required fields: ${missingFlags}`);
|
|
298
306
|
}
|
|
299
307
|
|
|
300
308
|
// Build nested object from dot-notation flags
|
|
@@ -311,6 +319,11 @@ export async function buildRequest(
|
|
|
311
319
|
}
|
|
312
320
|
}
|
|
313
321
|
|
|
322
|
+
// Check if user has a stored token (needed for auth scheme auto-selection)
|
|
323
|
+
const storedToken = profile?.name
|
|
324
|
+
? await getToken(input.specId, profile.name)
|
|
325
|
+
: null;
|
|
326
|
+
|
|
314
327
|
// Auth resolution priority: CLI flag > profile > embedded default
|
|
315
328
|
const resolvedAuthScheme = resolveAuthScheme(
|
|
316
329
|
input.authSchemes,
|
|
@@ -319,13 +332,11 @@ export async function buildRequest(
|
|
|
319
332
|
flagAuthScheme: input.globals.auth,
|
|
320
333
|
profileAuthScheme: profile?.authScheme,
|
|
321
334
|
embeddedAuthScheme: embedded?.auth,
|
|
335
|
+
hasStoredToken: Boolean(storedToken),
|
|
322
336
|
},
|
|
323
337
|
);
|
|
324
338
|
|
|
325
|
-
const tokenFromProfile =
|
|
326
|
-
profile?.name && resolvedAuthScheme
|
|
327
|
-
? await getToken(input.specId, profile.name)
|
|
328
|
-
: null;
|
|
339
|
+
const tokenFromProfile = resolvedAuthScheme ? storedToken : null;
|
|
329
340
|
|
|
330
341
|
const globalsWithProfileAuth: RuntimeGlobals = {
|
|
331
342
|
...input.globals,
|
package/src/cli/spec-loader.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
2
|
-
import { YAML } from "bun";
|
|
3
2
|
|
|
4
3
|
import { sha256Hex } from "./crypto.ts";
|
|
4
|
+
import { parseYamlContent } from "./runtime/compat.ts";
|
|
5
5
|
import { getSpecId } from "./spec-id.ts";
|
|
6
6
|
import { stableStringify } from "./stable-json.ts";
|
|
7
7
|
import type { LoadedSpec, OpenApiDoc, SpecSource } from "./types.ts";
|
|
@@ -21,7 +21,7 @@ function parseSpecText(text: string): unknown {
|
|
|
21
21
|
return JSON.parse(text);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
return
|
|
24
|
+
return parseYamlContent(text);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export async function loadSpec(options: LoadSpecOptions): Promise<LoadedSpec> {
|