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