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.
Files changed (209) hide show
  1. package/cli.ts +13 -4
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +2331 -0
  5. package/dist/cli.js.map +53 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +2032 -0
  9. package/dist/index.js.map +48 -0
  10. package/dist/src/ai/tools.d.ts +139 -0
  11. package/dist/src/ai/tools.d.ts.map +1 -0
  12. package/dist/src/ai/tools.js +1656 -0
  13. package/dist/src/ai/tools.js.map +45 -0
  14. package/dist/src/cli/auth-requirements.d.ts +10 -0
  15. package/dist/src/cli/auth-requirements.d.ts.map +1 -0
  16. package/dist/src/cli/auth-requirements.js +66 -0
  17. package/dist/src/cli/auth-requirements.js.map +10 -0
  18. package/dist/src/cli/auth-schemes.d.ts +22 -0
  19. package/dist/src/cli/auth-schemes.d.ts.map +1 -0
  20. package/dist/src/cli/auth-schemes.js +116 -0
  21. package/dist/src/cli/auth-schemes.js.map +11 -0
  22. package/dist/src/cli/capabilities.d.ts +32 -0
  23. package/dist/src/cli/capabilities.d.ts.map +1 -0
  24. package/dist/src/cli/capabilities.js +45 -0
  25. package/dist/src/cli/capabilities.js.map +10 -0
  26. package/dist/src/cli/command-id.d.ts +8 -0
  27. package/dist/src/cli/command-id.d.ts.map +1 -0
  28. package/dist/src/cli/command-id.js +18 -0
  29. package/dist/src/cli/command-id.js.map +11 -0
  30. package/dist/src/cli/command-index.d.ts +6 -0
  31. package/dist/src/cli/command-index.d.ts.map +1 -0
  32. package/dist/src/cli/command-index.js +15 -0
  33. package/dist/src/cli/command-index.js.map +10 -0
  34. package/dist/src/cli/command-model.d.ts +40 -0
  35. package/dist/src/cli/command-model.d.ts.map +1 -0
  36. package/dist/src/cli/command-model.js +274 -0
  37. package/dist/src/cli/command-model.js.map +18 -0
  38. package/dist/src/cli/compile.d.ts +15 -0
  39. package/dist/src/cli/compile.d.ts.map +1 -0
  40. package/dist/src/cli/compile.js +146 -0
  41. package/dist/src/cli/compile.js.map +11 -0
  42. package/dist/src/cli/crypto.d.ts +2 -0
  43. package/dist/src/cli/crypto.d.ts.map +1 -0
  44. package/dist/src/cli/crypto.js +15 -0
  45. package/dist/src/cli/crypto.js.map +10 -0
  46. package/dist/src/cli/derive-name.d.ts +9 -0
  47. package/dist/src/cli/derive-name.d.ts.map +1 -0
  48. package/dist/src/cli/derive-name.js +70 -0
  49. package/dist/src/cli/derive-name.js.map +10 -0
  50. package/dist/src/cli/exec.d.ts +14 -0
  51. package/dist/src/cli/exec.d.ts.map +1 -0
  52. package/dist/src/cli/exec.js +2077 -0
  53. package/dist/src/cli/exec.js.map +49 -0
  54. package/dist/src/cli/main.d.ts +10 -0
  55. package/dist/src/cli/main.d.ts.map +1 -0
  56. package/dist/src/cli/main.js +2032 -0
  57. package/dist/src/cli/main.js.map +48 -0
  58. package/dist/src/cli/naming.d.ts +12 -0
  59. package/dist/src/cli/naming.d.ts.map +1 -0
  60. package/dist/src/cli/naming.js +216 -0
  61. package/dist/src/cli/naming.js.map +12 -0
  62. package/dist/src/cli/operations.d.ts +3 -0
  63. package/dist/src/cli/operations.d.ts.map +1 -0
  64. package/dist/src/cli/operations.js +103 -0
  65. package/dist/src/cli/operations.js.map +10 -0
  66. package/dist/src/cli/params.d.ts +19 -0
  67. package/dist/src/cli/params.d.ts.map +1 -0
  68. package/dist/src/cli/params.js +79 -0
  69. package/dist/src/cli/params.js.map +12 -0
  70. package/dist/src/cli/pluralize.d.ts +2 -0
  71. package/dist/src/cli/pluralize.d.ts.map +1 -0
  72. package/dist/src/cli/pluralize.js +43 -0
  73. package/dist/src/cli/pluralize.js.map +10 -0
  74. package/dist/src/cli/positional.d.ts +19 -0
  75. package/dist/src/cli/positional.d.ts.map +1 -0
  76. package/dist/src/cli/positional.js +39 -0
  77. package/dist/src/cli/positional.js.map +10 -0
  78. package/dist/src/cli/request-body.d.ts +20 -0
  79. package/dist/src/cli/request-body.d.ts.map +1 -0
  80. package/dist/src/cli/request-body.js +82 -0
  81. package/dist/src/cli/request-body.js.map +12 -0
  82. package/dist/src/cli/runtime/argv.d.ts +3 -0
  83. package/dist/src/cli/runtime/argv.d.ts.map +1 -0
  84. package/dist/src/cli/runtime/argv.js +22 -0
  85. package/dist/src/cli/runtime/argv.js.map +10 -0
  86. package/dist/src/cli/runtime/auth/resolve.d.ts +9 -0
  87. package/dist/src/cli/runtime/auth/resolve.d.ts.map +1 -0
  88. package/dist/src/cli/runtime/auth/resolve.js +38 -0
  89. package/dist/src/cli/runtime/auth/resolve.js.map +10 -0
  90. package/dist/src/cli/runtime/body-flags.d.ts +41 -0
  91. package/dist/src/cli/runtime/body-flags.d.ts.map +1 -0
  92. package/dist/src/cli/runtime/body-flags.js +86 -0
  93. package/dist/src/cli/runtime/body-flags.js.map +10 -0
  94. package/dist/src/cli/runtime/body.d.ts +15 -0
  95. package/dist/src/cli/runtime/body.d.ts.map +1 -0
  96. package/dist/src/cli/runtime/body.js +40 -0
  97. package/dist/src/cli/runtime/body.js.map +11 -0
  98. package/dist/src/cli/runtime/collect.d.ts +2 -0
  99. package/dist/src/cli/runtime/collect.d.ts.map +1 -0
  100. package/dist/src/cli/runtime/collect.js +9 -0
  101. package/dist/src/cli/runtime/collect.js.map +10 -0
  102. package/dist/src/cli/runtime/compat.d.ts +35 -0
  103. package/dist/src/cli/runtime/compat.d.ts.map +1 -0
  104. package/dist/src/cli/runtime/compat.js +62 -0
  105. package/dist/src/cli/runtime/compat.js.map +10 -0
  106. package/dist/src/cli/runtime/context.d.ts +16 -0
  107. package/dist/src/cli/runtime/context.d.ts.map +1 -0
  108. package/dist/src/cli/runtime/context.js +936 -0
  109. package/dist/src/cli/runtime/context.js.map +32 -0
  110. package/dist/src/cli/runtime/execute.d.ts +33 -0
  111. package/dist/src/cli/runtime/execute.d.ts.map +1 -0
  112. package/dist/src/cli/runtime/execute.js +670 -0
  113. package/dist/src/cli/runtime/execute.js.map +22 -0
  114. package/dist/src/cli/runtime/generated.d.ts +14 -0
  115. package/dist/src/cli/runtime/generated.d.ts.map +1 -0
  116. package/dist/src/cli/runtime/generated.js +869 -0
  117. package/dist/src/cli/runtime/generated.js.map +23 -0
  118. package/dist/src/cli/runtime/headers.d.ts +9 -0
  119. package/dist/src/cli/runtime/headers.d.ts.map +1 -0
  120. package/dist/src/cli/runtime/headers.js +36 -0
  121. package/dist/src/cli/runtime/headers.js.map +10 -0
  122. package/dist/src/cli/runtime/index.d.ts +4 -0
  123. package/dist/src/cli/runtime/index.d.ts.map +1 -0
  124. package/dist/src/cli/runtime/index.js +1808 -0
  125. package/dist/src/cli/runtime/index.js.map +46 -0
  126. package/dist/src/cli/runtime/profile/secrets.d.ts +25 -0
  127. package/dist/src/cli/runtime/profile/secrets.d.ts.map +1 -0
  128. package/dist/src/cli/runtime/profile/secrets.js +51 -0
  129. package/dist/src/cli/runtime/profile/secrets.js.map +11 -0
  130. package/dist/src/cli/runtime/profile/store.d.ts +15 -0
  131. package/dist/src/cli/runtime/profile/store.d.ts.map +1 -0
  132. package/dist/src/cli/runtime/profile/store.js +102 -0
  133. package/dist/src/cli/runtime/profile/store.js.map +11 -0
  134. package/dist/src/cli/runtime/request.d.ts +36 -0
  135. package/dist/src/cli/runtime/request.d.ts.map +1 -0
  136. package/dist/src/cli/runtime/request.js +571 -0
  137. package/dist/src/cli/runtime/request.js.map +21 -0
  138. package/dist/src/cli/runtime/server-url.d.ts +8 -0
  139. package/dist/src/cli/runtime/server-url.d.ts.map +1 -0
  140. package/dist/src/cli/runtime/server-url.js +55 -0
  141. package/dist/src/cli/runtime/server-url.js.map +11 -0
  142. package/dist/src/cli/runtime/template.d.ts +5 -0
  143. package/dist/src/cli/runtime/template.d.ts.map +1 -0
  144. package/dist/src/cli/runtime/template.js +29 -0
  145. package/dist/src/cli/runtime/template.js.map +10 -0
  146. package/dist/src/cli/runtime/validate/ajv.d.ts +3 -0
  147. package/dist/src/cli/runtime/validate/ajv.d.ts.map +1 -0
  148. package/dist/src/cli/runtime/validate/ajv.js +17 -0
  149. package/dist/src/cli/runtime/validate/ajv.js.map +10 -0
  150. package/dist/src/cli/runtime/validate/coerce.d.ts +4 -0
  151. package/dist/src/cli/runtime/validate/coerce.d.ts.map +1 -0
  152. package/dist/src/cli/runtime/validate/coerce.js +60 -0
  153. package/dist/src/cli/runtime/validate/coerce.js.map +10 -0
  154. package/dist/src/cli/runtime/validate/error.d.ts +3 -0
  155. package/dist/src/cli/runtime/validate/error.d.ts.map +1 -0
  156. package/dist/src/cli/runtime/validate/error.js +21 -0
  157. package/dist/src/cli/runtime/validate/error.js.map +10 -0
  158. package/dist/src/cli/runtime/validate/index.d.ts +5 -0
  159. package/dist/src/cli/runtime/validate/index.d.ts.map +1 -0
  160. package/dist/src/cli/runtime/validate/index.js +122 -0
  161. package/dist/src/cli/runtime/validate/index.js.map +13 -0
  162. package/dist/src/cli/runtime/validate/schema.d.ts +9 -0
  163. package/dist/src/cli/runtime/validate/schema.d.ts.map +1 -0
  164. package/dist/src/cli/runtime/validate/schema.js +36 -0
  165. package/dist/src/cli/runtime/validate/schema.js.map +10 -0
  166. package/dist/src/cli/schema-shape.d.ts +5 -0
  167. package/dist/src/cli/schema-shape.d.ts.map +1 -0
  168. package/dist/src/cli/schema-shape.js +41 -0
  169. package/dist/src/cli/schema-shape.js.map +10 -0
  170. package/dist/src/cli/schema.d.ts +30 -0
  171. package/dist/src/cli/schema.d.ts.map +1 -0
  172. package/dist/src/cli/schema.js +38 -0
  173. package/dist/src/cli/schema.js.map +10 -0
  174. package/dist/src/cli/server.d.ts +16 -0
  175. package/dist/src/cli/server.d.ts.map +1 -0
  176. package/dist/src/cli/server.js +64 -0
  177. package/dist/src/cli/server.js.map +11 -0
  178. package/dist/src/cli/spec-id.d.ts +3 -0
  179. package/dist/src/cli/spec-id.d.ts.map +1 -0
  180. package/dist/src/cli/spec-id.js +21 -0
  181. package/dist/src/cli/spec-id.js.map +11 -0
  182. package/dist/src/cli/spec-loader.d.ts +7 -0
  183. package/dist/src/cli/spec-loader.d.ts.map +1 -0
  184. package/dist/src/cli/spec-loader.js +110 -0
  185. package/dist/src/cli/spec-loader.js.map +15 -0
  186. package/dist/src/cli/stable-json.d.ts +4 -0
  187. package/dist/src/cli/stable-json.d.ts.map +1 -0
  188. package/dist/src/cli/stable-json.js +35 -0
  189. package/dist/src/cli/stable-json.js.map +10 -0
  190. package/dist/src/cli/strings.d.ts +3 -0
  191. package/dist/src/cli/strings.d.ts.map +1 -0
  192. package/dist/src/cli/strings.js +16 -0
  193. package/dist/src/cli/strings.js.map +10 -0
  194. package/dist/src/cli/types.d.ts +53 -0
  195. package/dist/src/cli/types.d.ts.map +1 -0
  196. package/dist/src/cli/types.js +9 -0
  197. package/dist/src/cli/types.js.map +10 -0
  198. package/package.json +31 -4
  199. package/src/ai/tools.ts +211 -0
  200. package/src/cli/main.ts +73 -163
  201. package/src/cli/runtime/auth/resolve.ts +20 -0
  202. package/src/cli/runtime/body.ts +3 -3
  203. package/src/cli/runtime/compat.ts +89 -0
  204. package/src/cli/runtime/execute.ts +98 -39
  205. package/src/cli/runtime/generated.ts +111 -4
  206. package/src/cli/runtime/profile/secrets.ts +42 -1
  207. package/src/cli/runtime/profile/store.ts +15 -13
  208. package/src/cli/runtime/request.ts +22 -11
  209. package/src/cli/spec-loader.ts +2 -2
@@ -1,4 +1,4 @@
1
- import { YAML } from "bun";
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 Bun.file(input.path).text();
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 YAML.parse(text);
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
- try {
20
- const { request, curl } = await buildRequest({
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 res = await fetch(request);
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 (contentType.includes("json")) {
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(`${JSON.stringify({ status, body })}\n`);
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 parsedJson !== "undefined") {
74
- process.stdout.write(`${JSON.stringify(parsedJson, null, 2)}\n`);
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(text);
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 message = err instanceof Error ? err.message : String(err);
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: message })}\n`);
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 operationFlags = new Set(action.flags.map((f) => f.flag));
82
- const reservedFlags = new Set([...operationFlags, "--curl"]);
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 (!operationFlags.has("--curl")) {
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 { secrets } from "bun";
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 { YAML } from "bun";
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 jsonFile = Bun.file(jsonPath);
36
- const yamlFile = Bun.file(yamlPath);
41
+ const jsonExists = await fileExists(jsonPath);
42
+ const yamlExists = await fileExists(yamlPath);
37
43
 
38
- const file = (await jsonFile.exists())
39
- ? jsonFile
40
- : (await yamlFile.exists())
41
- ? yamlFile
42
- : null;
44
+ const filePath = jsonExists ? jsonPath : yamlExists ? yamlPath : null;
43
45
 
44
- if (!file) return { profiles: [] };
46
+ if (!filePath) return { profiles: [] };
45
47
 
46
- const text = await file.text();
48
+ const text = await readFileText(filePath);
47
49
  let parsed: unknown;
48
50
  try {
49
- parsed = YAML.parse(text) as unknown;
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 Bun.$`mkdir -p ${dir}`;
74
- await Bun.write(configPathJson(), JSON.stringify(data, null, 2));
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, input.globals.profile);
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
- throw new Error("Missing required request body fields.");
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
- throw new Error(
296
- `Missing required body field '${missing[0]}'. Provide --${missing[0]}.`,
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,
@@ -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 YAML.parse(text);
24
+ return parseYamlContent(text);
25
25
  }
26
26
 
27
27
  export async function loadSpec(options: LoadSpecOptions): Promise<LoadedSpec> {