@sundaeswap/sprinkles 0.6.0 → 0.7.0

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 (158) hide show
  1. package/dist/cjs/Sprinkle/__tests__/action-integration.test.js +590 -0
  2. package/dist/cjs/Sprinkle/__tests__/action-integration.test.js.map +1 -0
  3. package/dist/cjs/Sprinkle/__tests__/action-registry.test.js +193 -0
  4. package/dist/cjs/Sprinkle/__tests__/action-registry.test.js.map +1 -0
  5. package/dist/cjs/Sprinkle/__tests__/action-runner.test.js +304 -0
  6. package/dist/cjs/Sprinkle/__tests__/action-runner.test.js.map +1 -0
  7. package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js +1110 -0
  8. package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js.map +1 -0
  9. package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js +722 -0
  10. package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js.map +1 -0
  11. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +138 -0
  12. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  13. package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js +713 -0
  14. package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  15. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js +334 -0
  16. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  17. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js +749 -0
  18. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  19. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js +61 -0
  20. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  21. package/dist/cjs/Sprinkle/actions/builtin/index.js +117 -0
  22. package/dist/cjs/Sprinkle/actions/builtin/index.js.map +1 -0
  23. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js +202 -0
  24. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  25. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js +87 -0
  26. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  27. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js +345 -0
  28. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  29. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js +212 -0
  30. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  31. package/dist/cjs/Sprinkle/actions/cli-adapter.js +372 -0
  32. package/dist/cjs/Sprinkle/actions/cli-adapter.js.map +1 -0
  33. package/dist/cjs/Sprinkle/actions/index.js +127 -0
  34. package/dist/cjs/Sprinkle/actions/index.js.map +1 -0
  35. package/dist/cjs/Sprinkle/actions/mcp-adapter.js +415 -0
  36. package/dist/cjs/Sprinkle/actions/mcp-adapter.js.map +1 -0
  37. package/dist/cjs/Sprinkle/actions/registry.js +92 -0
  38. package/dist/cjs/Sprinkle/actions/registry.js.map +1 -0
  39. package/dist/cjs/Sprinkle/actions/runner.js +190 -0
  40. package/dist/cjs/Sprinkle/actions/runner.js.map +1 -0
  41. package/dist/cjs/Sprinkle/actions/tui-helpers.js +96 -0
  42. package/dist/cjs/Sprinkle/actions/tui-helpers.js.map +1 -0
  43. package/dist/cjs/Sprinkle/actions/types.js +68 -0
  44. package/dist/cjs/Sprinkle/actions/types.js.map +1 -0
  45. package/dist/cjs/Sprinkle/index.js +451 -4
  46. package/dist/cjs/Sprinkle/index.js.map +1 -1
  47. package/dist/cjs/Sprinkle/prompts.js +12 -7
  48. package/dist/cjs/Sprinkle/prompts.js.map +1 -1
  49. package/dist/cjs/Sprinkle/type-guards.js +7 -1
  50. package/dist/cjs/Sprinkle/type-guards.js.map +1 -1
  51. package/dist/esm/Sprinkle/__tests__/action-integration.test.js +588 -0
  52. package/dist/esm/Sprinkle/__tests__/action-integration.test.js.map +1 -0
  53. package/dist/esm/Sprinkle/__tests__/action-registry.test.js +192 -0
  54. package/dist/esm/Sprinkle/__tests__/action-registry.test.js.map +1 -0
  55. package/dist/esm/Sprinkle/__tests__/action-runner.test.js +302 -0
  56. package/dist/esm/Sprinkle/__tests__/action-runner.test.js.map +1 -0
  57. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js +1107 -0
  58. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js.map +1 -0
  59. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js +720 -0
  60. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js.map +1 -0
  61. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +138 -0
  62. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  63. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js +712 -0
  64. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  65. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js +332 -0
  66. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  67. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js +747 -0
  68. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  69. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js +55 -0
  70. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  71. package/dist/esm/Sprinkle/actions/builtin/index.js +32 -0
  72. package/dist/esm/Sprinkle/actions/builtin/index.js.map +1 -0
  73. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js +197 -0
  74. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  75. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js +81 -0
  76. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  77. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js +340 -0
  78. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  79. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js +207 -0
  80. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  81. package/dist/esm/Sprinkle/actions/cli-adapter.js +361 -0
  82. package/dist/esm/Sprinkle/actions/cli-adapter.js.map +1 -0
  83. package/dist/esm/Sprinkle/actions/index.js +12 -0
  84. package/dist/esm/Sprinkle/actions/index.js.map +1 -0
  85. package/dist/esm/Sprinkle/actions/mcp-adapter.js +407 -0
  86. package/dist/esm/Sprinkle/actions/mcp-adapter.js.map +1 -0
  87. package/dist/esm/Sprinkle/actions/registry.js +85 -0
  88. package/dist/esm/Sprinkle/actions/registry.js.map +1 -0
  89. package/dist/esm/Sprinkle/actions/runner.js +182 -0
  90. package/dist/esm/Sprinkle/actions/runner.js.map +1 -0
  91. package/dist/esm/Sprinkle/actions/tui-helpers.js +91 -0
  92. package/dist/esm/Sprinkle/actions/tui-helpers.js.map +1 -0
  93. package/dist/esm/Sprinkle/actions/types.js +61 -0
  94. package/dist/esm/Sprinkle/actions/types.js.map +1 -0
  95. package/dist/esm/Sprinkle/index.js +299 -4
  96. package/dist/esm/Sprinkle/index.js.map +1 -1
  97. package/dist/esm/Sprinkle/prompts.js +12 -7
  98. package/dist/esm/Sprinkle/prompts.js.map +1 -1
  99. package/dist/esm/Sprinkle/type-guards.js +3 -0
  100. package/dist/esm/Sprinkle/type-guards.js.map +1 -1
  101. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts +39 -0
  102. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts.map +1 -0
  103. package/dist/types/Sprinkle/actions/builtin/index.d.ts +26 -0
  104. package/dist/types/Sprinkle/actions/builtin/index.d.ts.map +1 -0
  105. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts +55 -0
  106. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts.map +1 -0
  107. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts +32 -0
  108. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts.map +1 -0
  109. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts +70 -0
  110. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts.map +1 -0
  111. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts +50 -0
  112. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts.map +1 -0
  113. package/dist/types/Sprinkle/actions/cli-adapter.d.ts +104 -0
  114. package/dist/types/Sprinkle/actions/cli-adapter.d.ts.map +1 -0
  115. package/dist/types/Sprinkle/actions/index.d.ts +12 -0
  116. package/dist/types/Sprinkle/actions/index.d.ts.map +1 -0
  117. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts +92 -0
  118. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts.map +1 -0
  119. package/dist/types/Sprinkle/actions/registry.d.ts +42 -0
  120. package/dist/types/Sprinkle/actions/registry.d.ts.map +1 -0
  121. package/dist/types/Sprinkle/actions/runner.d.ts +45 -0
  122. package/dist/types/Sprinkle/actions/runner.d.ts.map +1 -0
  123. package/dist/types/Sprinkle/actions/tui-helpers.d.ts +53 -0
  124. package/dist/types/Sprinkle/actions/tui-helpers.d.ts.map +1 -0
  125. package/dist/types/Sprinkle/actions/types.d.ts +76 -0
  126. package/dist/types/Sprinkle/actions/types.d.ts.map +1 -0
  127. package/dist/types/Sprinkle/index.d.ts +81 -1
  128. package/dist/types/Sprinkle/index.d.ts.map +1 -1
  129. package/dist/types/Sprinkle/prompts.d.ts.map +1 -1
  130. package/dist/types/Sprinkle/type-guards.d.ts +4 -1
  131. package/dist/types/Sprinkle/type-guards.d.ts.map +1 -1
  132. package/dist/types/tsconfig.build.tsbuildinfo +1 -1
  133. package/package.json +9 -2
  134. package/src/Sprinkle/__tests__/action-integration.test.ts +558 -0
  135. package/src/Sprinkle/__tests__/action-registry.test.ts +187 -0
  136. package/src/Sprinkle/__tests__/action-runner.test.ts +324 -0
  137. package/src/Sprinkle/__tests__/builtin-actions.test.ts +1022 -0
  138. package/src/Sprinkle/__tests__/cli-adapter.test.ts +715 -0
  139. package/src/Sprinkle/__tests__/fill-in-struct.test.ts +144 -0
  140. package/src/Sprinkle/__tests__/mcp-adapter.test.ts +718 -0
  141. package/src/Sprinkle/__tests__/tui-helpers.test.ts +325 -0
  142. package/src/Sprinkle/__tests__/wallet-transaction-actions.test.ts +695 -0
  143. package/src/Sprinkle/actions/builtin/blaze-helper.ts +89 -0
  144. package/src/Sprinkle/actions/builtin/index.ts +86 -0
  145. package/src/Sprinkle/actions/builtin/profile-actions.ts +229 -0
  146. package/src/Sprinkle/actions/builtin/settings-actions.ts +99 -0
  147. package/src/Sprinkle/actions/builtin/transaction-actions.ts +381 -0
  148. package/src/Sprinkle/actions/builtin/wallet-actions.ts +233 -0
  149. package/src/Sprinkle/actions/cli-adapter.ts +430 -0
  150. package/src/Sprinkle/actions/index.ts +32 -0
  151. package/src/Sprinkle/actions/mcp-adapter.ts +463 -0
  152. package/src/Sprinkle/actions/registry.ts +97 -0
  153. package/src/Sprinkle/actions/runner.ts +200 -0
  154. package/src/Sprinkle/actions/tui-helpers.ts +114 -0
  155. package/src/Sprinkle/actions/types.ts +91 -0
  156. package/src/Sprinkle/index.ts +395 -3
  157. package/src/Sprinkle/prompts.ts +118 -72
  158. package/src/Sprinkle/type-guards.ts +9 -0
@@ -0,0 +1,430 @@
1
+ /**
2
+ * CLI adapter for Sprinkles actions.
3
+ *
4
+ * Provides:
5
+ * - Case-conversion helpers (camelToKebab / kebabToCamel)
6
+ * - Schema-driven type coercion (coerceValue / parseArgvWithSchema)
7
+ * - Help text generation (generateActionHelp / generateAppHelp)
8
+ * - CLI orchestrator (runCli)
9
+ */
10
+
11
+ import type { TSchema } from "@sinclair/typebox";
12
+ import { bigIntReplacer } from "../encryption.js";
13
+ import {
14
+ isArray,
15
+ isBigInt,
16
+ isBoolean,
17
+ isInteger,
18
+ isNumber,
19
+ isObject,
20
+ isString,
21
+ isUnion,
22
+ hasDefault,
23
+ getDefault,
24
+ } from "../type-guards.js";
25
+ import type { AnyAction, IActionContext } from "./types.js";
26
+ import { executeAction } from "./runner.js";
27
+
28
+ // Re-import Sprinkle as a type only to avoid circular deps
29
+ import type { Sprinkle } from "../index.js";
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Case conversion helpers
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Convert a camelCase string to kebab-case.
37
+ * Examples:
38
+ * "myFlagName" -> "my-flag-name"
39
+ * "getBalance" -> "get-balance"
40
+ * "URL" -> "u-r-l" (each uppercase letter gets a dash)
41
+ */
42
+ export function camelToKebab(str: string): string {
43
+ if (!str) return str;
44
+ return str
45
+ .replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`)
46
+ .replace(/^-/, "");
47
+ }
48
+
49
+ /**
50
+ * Convert a kebab-case string to camelCase.
51
+ * Examples:
52
+ * "my-flag-name" -> "myFlagName"
53
+ * "get-balance" -> "getBalance"
54
+ */
55
+ export function kebabToCamel(str: string): string {
56
+ if (!str) return str;
57
+ return str.replace(/-([a-z0-9])/g, (_, char: string) => char.toUpperCase());
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Schema-driven type coercion
62
+ // ---------------------------------------------------------------------------
63
+
64
+ /**
65
+ * Coerce a raw CLI string value to the type expected by a TypeBox schema.
66
+ *
67
+ * Handles:
68
+ * - Number / Integer: parseFloat / parseInt
69
+ * - Boolean: "true"/"false" strings, or passthrough if already boolean
70
+ * - BigInt: BigInt() constructor
71
+ * - Array: already-array passthrough; single value wrapped in array
72
+ * - Object: JSON string parse
73
+ * - String: passthrough
74
+ * - Optional: unwrap and recurse on inner schema
75
+ * - Union: passthrough (caller should supply JSON; no schema-level heuristics)
76
+ */
77
+ export function coerceValue(raw: unknown, schema: TSchema): unknown {
78
+ // Note: TypeBox Optional<T> adds an [OptionalKind] marker but does not
79
+ // change the schema Kind. The guards below work correctly on Optional types.
80
+
81
+ // If the raw value is null or undefined, return as-is
82
+ if (raw === null || raw === undefined) return raw;
83
+
84
+ if (isNumber(schema)) {
85
+ if (typeof raw === "number") return raw;
86
+ if (typeof raw === "string") {
87
+ const n = parseFloat(raw);
88
+ return isNaN(n) ? raw : n;
89
+ }
90
+ return raw;
91
+ }
92
+
93
+ if (isInteger(schema)) {
94
+ if (typeof raw === "number") return Math.trunc(raw);
95
+ if (typeof raw === "string") {
96
+ const n = parseInt(raw, 10);
97
+ return isNaN(n) ? raw : n;
98
+ }
99
+ return raw;
100
+ }
101
+
102
+ if (isBoolean(schema)) {
103
+ if (typeof raw === "boolean") return raw;
104
+ if (raw === "true") return true;
105
+ if (raw === "false") return false;
106
+ return raw;
107
+ }
108
+
109
+ if (isBigInt(schema)) {
110
+ if (typeof raw === "bigint") return raw;
111
+ if (typeof raw === "string" || typeof raw === "number") {
112
+ try {
113
+ return BigInt(raw);
114
+ } catch {
115
+ return raw;
116
+ }
117
+ }
118
+ return raw;
119
+ }
120
+
121
+ if (isArray(schema)) {
122
+ const itemSchema = (schema as { items?: TSchema }).items;
123
+ if (Array.isArray(raw)) {
124
+ if (itemSchema) {
125
+ return raw.map((item) => coerceValue(item, itemSchema));
126
+ }
127
+ return raw;
128
+ }
129
+ // Single value: wrap in array
130
+ const singleValue = itemSchema ? coerceValue(raw, itemSchema) : raw;
131
+ return [singleValue];
132
+ }
133
+
134
+ if (isObject(schema)) {
135
+ // If it's a JSON string, parse it
136
+ if (typeof raw === "string") {
137
+ const trimmed = raw.trim();
138
+ if (trimmed.startsWith("{")) {
139
+ try {
140
+ return JSON.parse(trimmed);
141
+ } catch {
142
+ return raw;
143
+ }
144
+ }
145
+ }
146
+ return raw;
147
+ }
148
+
149
+ if (isString(schema)) {
150
+ if (typeof raw === "string") return raw;
151
+ return String(raw);
152
+ }
153
+
154
+ // Union / unknown / other: return as-is (caller provides JSON)
155
+ return raw;
156
+ }
157
+
158
+ /**
159
+ * Walk a TypeBox object schema and coerce each property value from raw CLI args.
160
+ *
161
+ * - Handles kebab-case -> camelCase property name mapping
162
+ * - Applies coerceValue for each property
163
+ * - Applies TypeBox defaults for omitted optional fields
164
+ *
165
+ * @param rawArgs - Key/value record from parseCliArgs (keys may be kebab-case)
166
+ * @param schema - A TypeBox TObject schema
167
+ * @returns A new record with coerced values
168
+ */
169
+ export function parseArgvWithSchema(
170
+ rawArgs: Record<string, unknown>,
171
+ schema: TSchema,
172
+ ): Record<string, unknown> {
173
+ if (!isObject(schema)) {
174
+ // Non-object schema: return args as-is (best-effort)
175
+ return rawArgs;
176
+ }
177
+
178
+ const result: Record<string, unknown> = {};
179
+ const properties = schema.properties as Record<string, TSchema>;
180
+
181
+ for (const [propName, propSchema] of Object.entries(properties)) {
182
+ // Look up both camelCase and kebab-case versions in rawArgs
183
+ const kebabName = camelToKebab(propName);
184
+
185
+ let rawValue: unknown = undefined;
186
+ if (propName in rawArgs) {
187
+ rawValue = rawArgs[propName];
188
+ } else if (kebabName !== propName && kebabName in rawArgs) {
189
+ rawValue = rawArgs[kebabName];
190
+ }
191
+
192
+ if (rawValue === undefined) {
193
+ // Apply default if defined on schema
194
+ if (hasDefault(propSchema)) {
195
+ result[propName] = getDefault(propSchema);
196
+ }
197
+ // Else omit: Value.Check will handle required/optional enforcement
198
+ continue;
199
+ }
200
+
201
+ result[propName] = coerceValue(rawValue, propSchema);
202
+ }
203
+
204
+ return result;
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Help text generation
209
+ // ---------------------------------------------------------------------------
210
+
211
+ /**
212
+ * Describe a single TypeBox schema in a human-readable CLI type label.
213
+ */
214
+ function describeType(schema: TSchema): string {
215
+ // Note: isOptional() only checks the OptionalKind marker; the other guards
216
+ // still work correctly on Optional-wrapped schemas.
217
+ if (isString(schema)) return "<string>";
218
+ if (isNumber(schema)) return "<number>";
219
+ if (isInteger(schema)) return "<integer>";
220
+ if (isBoolean(schema)) return "<boolean>";
221
+ if (isBigInt(schema)) return "<bigint>";
222
+ if (isArray(schema)) {
223
+ const itemSchema = (schema as { items?: TSchema }).items;
224
+ const itemType = itemSchema ? describeType(itemSchema) : "<value>";
225
+ return `${itemType}... (repeatable)`;
226
+ }
227
+ if (isObject(schema)) return "<json>";
228
+ if (isUnion(schema)) return "<json>";
229
+ return "<value>";
230
+ }
231
+
232
+ /**
233
+ * Generate action-specific help text.
234
+ *
235
+ * Format:
236
+ * ```
237
+ * Usage: <appName> <action-name> [options]
238
+ *
239
+ * Description
240
+ *
241
+ * Options:
242
+ * --flag-name <type> Description [default: x] (required/optional)
243
+ * ...
244
+ * ```
245
+ */
246
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
247
+ export function generateActionHelp(
248
+ action: AnyAction<any>,
249
+ appName?: string,
250
+ ): string {
251
+ const app = appName ?? "app";
252
+ const lines: string[] = [
253
+ `Usage: ${app} ${action.name} [options]`,
254
+ "",
255
+ action.description,
256
+ "",
257
+ "Options:",
258
+ ];
259
+
260
+ const schema = action.inputSchema;
261
+ if (isObject(schema)) {
262
+ const properties = schema.properties as Record<string, TSchema>;
263
+ const required = (schema.required as string[] | undefined) ?? [];
264
+
265
+ for (const [propName, propSchema] of Object.entries(properties)) {
266
+ const flagName = camelToKebab(propName);
267
+ const typeLabel = describeType(propSchema);
268
+ const isReq = required.includes(propName);
269
+ const defaultVal = hasDefault(propSchema) ? getDefault(propSchema) : undefined;
270
+ const description = (propSchema as { description?: string }).description ?? "";
271
+
272
+ const requiredLabel = isReq ? "(required)" : "(optional)";
273
+ const defaultLabel =
274
+ defaultVal !== undefined ? ` [default: ${String(defaultVal)}]` : "";
275
+
276
+ const descPart = description ? ` ${description}` : "";
277
+ lines.push(
278
+ ` --${flagName} ${typeLabel}${descPart}${defaultLabel} ${requiredLabel}`,
279
+ );
280
+ }
281
+ }
282
+
283
+ lines.push("");
284
+ lines.push(" --help Show this help message");
285
+
286
+ return lines.join("\n");
287
+ }
288
+
289
+ /**
290
+ * Generate application-level help listing all registered actions.
291
+ *
292
+ * Format:
293
+ * ```
294
+ * Usage: <appName> <action> [options]
295
+ *
296
+ * Available actions:
297
+ *
298
+ * category:
299
+ * action-name Description
300
+ *
301
+ * Global options:
302
+ * --help Show this help message
303
+ * --profile <n> Use profile by name
304
+ * ```
305
+ */
306
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ export function generateAppHelp(
308
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
+ actions: AnyAction<any>[],
310
+ appName?: string,
311
+ ): string {
312
+ const app = appName ?? "app";
313
+ const lines: string[] = [`Usage: ${app} <action> [options]`, ""];
314
+
315
+ if (actions.length === 0) {
316
+ lines.push("No actions registered.");
317
+ } else {
318
+ lines.push("Available actions:");
319
+
320
+ // Group by category
321
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
322
+ const byCategory = new Map<string, AnyAction<any>[]>();
323
+ for (const action of actions) {
324
+ const cat = action.category ?? "default";
325
+ const bucket = byCategory.get(cat);
326
+ if (bucket) {
327
+ bucket.push(action);
328
+ } else {
329
+ byCategory.set(cat, [action]);
330
+ }
331
+ }
332
+
333
+ for (const [category, categoryActions] of byCategory) {
334
+ if (category !== "default") {
335
+ lines.push("");
336
+ lines.push(` ${category}:`);
337
+ }
338
+ for (const action of categoryActions) {
339
+ lines.push(` ${action.name.padEnd(30)} ${action.description}`);
340
+ }
341
+ }
342
+ }
343
+
344
+ lines.push("");
345
+ lines.push("Global options:");
346
+ lines.push(" --help Show this help message");
347
+ lines.push(" --profile <n> Use profile by name");
348
+
349
+ return lines.join("\n");
350
+ }
351
+
352
+ // ---------------------------------------------------------------------------
353
+ // CLI orchestrator
354
+ // ---------------------------------------------------------------------------
355
+
356
+ /**
357
+ * Run an action from CLI arguments, writing JSON output to stdout/stderr.
358
+ *
359
+ * Handles:
360
+ * - Action not found: JSON error to stderr, process.exit(1)
361
+ * - Action help (`--help` in rawArgs): prints action help to stdout
362
+ * - Schema-driven argument coercion via parseArgvWithSchema
363
+ * - Success: `{"success":true,"data":...}` JSON to stdout
364
+ * - Failure: `{"success":false,"error":...}` JSON to stderr, process.exit(1)
365
+ *
366
+ * @param sprinkle - Fully-initialized Sprinkle instance
367
+ * @param actionName - The action to invoke
368
+ * @param rawArgs - Pre-parsed key/value args from parseCliArgs (profile already removed)
369
+ * @param context - The action execution context
370
+ * @param appName - Optional app name for help text
371
+ */
372
+ export async function runCli<S extends TSchema>(
373
+ sprinkle: Sprinkle<S>,
374
+ actionName: string,
375
+ rawArgs: Record<string, unknown>,
376
+ context: IActionContext<S>,
377
+ appName?: string,
378
+ ): Promise<void> {
379
+ const action = sprinkle.getAction(actionName);
380
+
381
+ if (!action) {
382
+ const available = sprinkle
383
+ .listActions()
384
+ .map((a) => a.name)
385
+ .join(", ");
386
+ const errorOutput = JSON.stringify(
387
+ {
388
+ success: false,
389
+ error: {
390
+ code: "ACTION_NOT_FOUND",
391
+ message: `Action "${actionName}" not found. Available actions: ${available || "(none)"}`,
392
+ },
393
+ },
394
+ bigIntReplacer,
395
+ );
396
+ console.error(errorOutput);
397
+ process.exit(1);
398
+ }
399
+
400
+ // Handle --help flag for action-specific help
401
+ if (rawArgs["help"] === true) {
402
+ console.log(generateActionHelp(action, appName));
403
+ return;
404
+ }
405
+
406
+ // Coerce raw string args using the action's input schema
407
+ const coercedInput = parseArgvWithSchema(rawArgs, action.inputSchema);
408
+
409
+ // Execute and output result
410
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
411
+ const result = await executeAction(action as any, coercedInput, context as any);
412
+
413
+ if (result.success) {
414
+ console.log(JSON.stringify({ success: true, data: result.data }, bigIntReplacer));
415
+ } else {
416
+ const errorOutput = JSON.stringify(
417
+ {
418
+ success: false,
419
+ error: {
420
+ code: result.error.code,
421
+ message: result.error.message,
422
+ details: result.error.details,
423
+ },
424
+ },
425
+ bigIntReplacer,
426
+ );
427
+ console.error(errorOutput);
428
+ process.exit(1);
429
+ }
430
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Barrel export for the actions subsystem.
3
+ */
4
+
5
+ export type { IAction, IActionContext, IActionResult, IActionSuccess, IActionFailure, AnyAction } from "./types.js";
6
+ export { ActionError } from "./types.js";
7
+
8
+ export { ActionRegistry } from "./registry.js";
9
+
10
+ export { executeAction, detectMode, parseCliArgs } from "./runner.js";
11
+
12
+ export {
13
+ camelToKebab,
14
+ kebabToCamel,
15
+ coerceValue,
16
+ parseArgvWithSchema,
17
+ generateActionHelp,
18
+ generateAppHelp,
19
+ runCli,
20
+ } from "./cli-adapter.js";
21
+
22
+ export {
23
+ typeboxToJsonSchema,
24
+ coerceMcpInput,
25
+ getMcpSdk,
26
+ createMcpServer,
27
+ runMcp,
28
+ } from "./mcp-adapter.js";
29
+
30
+ export { getBuiltinActions } from "./builtin/index.js";
31
+
32
+ export { promptAndExecute } from "./tui-helpers.js";