@sundaeswap/sprinkles 0.6.1 → 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 (153) 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__/mcp-adapter.test.js +713 -0
  12. package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  13. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js +334 -0
  14. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  15. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js +749 -0
  16. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  17. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js +61 -0
  18. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  19. package/dist/cjs/Sprinkle/actions/builtin/index.js +117 -0
  20. package/dist/cjs/Sprinkle/actions/builtin/index.js.map +1 -0
  21. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js +202 -0
  22. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  23. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js +87 -0
  24. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  25. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js +345 -0
  26. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  27. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js +212 -0
  28. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  29. package/dist/cjs/Sprinkle/actions/cli-adapter.js +372 -0
  30. package/dist/cjs/Sprinkle/actions/cli-adapter.js.map +1 -0
  31. package/dist/cjs/Sprinkle/actions/index.js +127 -0
  32. package/dist/cjs/Sprinkle/actions/index.js.map +1 -0
  33. package/dist/cjs/Sprinkle/actions/mcp-adapter.js +415 -0
  34. package/dist/cjs/Sprinkle/actions/mcp-adapter.js.map +1 -0
  35. package/dist/cjs/Sprinkle/actions/registry.js +92 -0
  36. package/dist/cjs/Sprinkle/actions/registry.js.map +1 -0
  37. package/dist/cjs/Sprinkle/actions/runner.js +190 -0
  38. package/dist/cjs/Sprinkle/actions/runner.js.map +1 -0
  39. package/dist/cjs/Sprinkle/actions/tui-helpers.js +96 -0
  40. package/dist/cjs/Sprinkle/actions/tui-helpers.js.map +1 -0
  41. package/dist/cjs/Sprinkle/actions/types.js +68 -0
  42. package/dist/cjs/Sprinkle/actions/types.js.map +1 -0
  43. package/dist/cjs/Sprinkle/index.js +412 -1
  44. package/dist/cjs/Sprinkle/index.js.map +1 -1
  45. package/dist/cjs/Sprinkle/prompts.js +12 -7
  46. package/dist/cjs/Sprinkle/prompts.js.map +1 -1
  47. package/dist/cjs/Sprinkle/type-guards.js +7 -1
  48. package/dist/cjs/Sprinkle/type-guards.js.map +1 -1
  49. package/dist/esm/Sprinkle/__tests__/action-integration.test.js +588 -0
  50. package/dist/esm/Sprinkle/__tests__/action-integration.test.js.map +1 -0
  51. package/dist/esm/Sprinkle/__tests__/action-registry.test.js +192 -0
  52. package/dist/esm/Sprinkle/__tests__/action-registry.test.js.map +1 -0
  53. package/dist/esm/Sprinkle/__tests__/action-runner.test.js +302 -0
  54. package/dist/esm/Sprinkle/__tests__/action-runner.test.js.map +1 -0
  55. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js +1107 -0
  56. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js.map +1 -0
  57. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js +720 -0
  58. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js.map +1 -0
  59. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js +712 -0
  60. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  61. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js +332 -0
  62. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  63. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js +747 -0
  64. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  65. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js +55 -0
  66. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  67. package/dist/esm/Sprinkle/actions/builtin/index.js +32 -0
  68. package/dist/esm/Sprinkle/actions/builtin/index.js.map +1 -0
  69. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js +197 -0
  70. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  71. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js +81 -0
  72. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  73. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js +340 -0
  74. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  75. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js +207 -0
  76. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  77. package/dist/esm/Sprinkle/actions/cli-adapter.js +361 -0
  78. package/dist/esm/Sprinkle/actions/cli-adapter.js.map +1 -0
  79. package/dist/esm/Sprinkle/actions/index.js +12 -0
  80. package/dist/esm/Sprinkle/actions/index.js.map +1 -0
  81. package/dist/esm/Sprinkle/actions/mcp-adapter.js +407 -0
  82. package/dist/esm/Sprinkle/actions/mcp-adapter.js.map +1 -0
  83. package/dist/esm/Sprinkle/actions/registry.js +85 -0
  84. package/dist/esm/Sprinkle/actions/registry.js.map +1 -0
  85. package/dist/esm/Sprinkle/actions/runner.js +182 -0
  86. package/dist/esm/Sprinkle/actions/runner.js.map +1 -0
  87. package/dist/esm/Sprinkle/actions/tui-helpers.js +91 -0
  88. package/dist/esm/Sprinkle/actions/tui-helpers.js.map +1 -0
  89. package/dist/esm/Sprinkle/actions/types.js +61 -0
  90. package/dist/esm/Sprinkle/actions/types.js.map +1 -0
  91. package/dist/esm/Sprinkle/index.js +260 -1
  92. package/dist/esm/Sprinkle/index.js.map +1 -1
  93. package/dist/esm/Sprinkle/prompts.js +12 -7
  94. package/dist/esm/Sprinkle/prompts.js.map +1 -1
  95. package/dist/esm/Sprinkle/type-guards.js +3 -0
  96. package/dist/esm/Sprinkle/type-guards.js.map +1 -1
  97. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts +39 -0
  98. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts.map +1 -0
  99. package/dist/types/Sprinkle/actions/builtin/index.d.ts +26 -0
  100. package/dist/types/Sprinkle/actions/builtin/index.d.ts.map +1 -0
  101. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts +55 -0
  102. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts.map +1 -0
  103. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts +32 -0
  104. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts.map +1 -0
  105. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts +70 -0
  106. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts.map +1 -0
  107. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts +50 -0
  108. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts.map +1 -0
  109. package/dist/types/Sprinkle/actions/cli-adapter.d.ts +104 -0
  110. package/dist/types/Sprinkle/actions/cli-adapter.d.ts.map +1 -0
  111. package/dist/types/Sprinkle/actions/index.d.ts +12 -0
  112. package/dist/types/Sprinkle/actions/index.d.ts.map +1 -0
  113. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts +92 -0
  114. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts.map +1 -0
  115. package/dist/types/Sprinkle/actions/registry.d.ts +42 -0
  116. package/dist/types/Sprinkle/actions/registry.d.ts.map +1 -0
  117. package/dist/types/Sprinkle/actions/runner.d.ts +45 -0
  118. package/dist/types/Sprinkle/actions/runner.d.ts.map +1 -0
  119. package/dist/types/Sprinkle/actions/tui-helpers.d.ts +53 -0
  120. package/dist/types/Sprinkle/actions/tui-helpers.d.ts.map +1 -0
  121. package/dist/types/Sprinkle/actions/types.d.ts +76 -0
  122. package/dist/types/Sprinkle/actions/types.d.ts.map +1 -0
  123. package/dist/types/Sprinkle/index.d.ts +81 -1
  124. package/dist/types/Sprinkle/index.d.ts.map +1 -1
  125. package/dist/types/Sprinkle/prompts.d.ts.map +1 -1
  126. package/dist/types/Sprinkle/type-guards.d.ts +4 -1
  127. package/dist/types/Sprinkle/type-guards.d.ts.map +1 -1
  128. package/dist/types/tsconfig.build.tsbuildinfo +1 -1
  129. package/package.json +9 -2
  130. package/src/Sprinkle/__tests__/action-integration.test.ts +558 -0
  131. package/src/Sprinkle/__tests__/action-registry.test.ts +187 -0
  132. package/src/Sprinkle/__tests__/action-runner.test.ts +324 -0
  133. package/src/Sprinkle/__tests__/builtin-actions.test.ts +1022 -0
  134. package/src/Sprinkle/__tests__/cli-adapter.test.ts +715 -0
  135. package/src/Sprinkle/__tests__/mcp-adapter.test.ts +718 -0
  136. package/src/Sprinkle/__tests__/tui-helpers.test.ts +325 -0
  137. package/src/Sprinkle/__tests__/wallet-transaction-actions.test.ts +695 -0
  138. package/src/Sprinkle/actions/builtin/blaze-helper.ts +89 -0
  139. package/src/Sprinkle/actions/builtin/index.ts +86 -0
  140. package/src/Sprinkle/actions/builtin/profile-actions.ts +229 -0
  141. package/src/Sprinkle/actions/builtin/settings-actions.ts +99 -0
  142. package/src/Sprinkle/actions/builtin/transaction-actions.ts +381 -0
  143. package/src/Sprinkle/actions/builtin/wallet-actions.ts +233 -0
  144. package/src/Sprinkle/actions/cli-adapter.ts +430 -0
  145. package/src/Sprinkle/actions/index.ts +32 -0
  146. package/src/Sprinkle/actions/mcp-adapter.ts +463 -0
  147. package/src/Sprinkle/actions/registry.ts +97 -0
  148. package/src/Sprinkle/actions/runner.ts +200 -0
  149. package/src/Sprinkle/actions/tui-helpers.ts +114 -0
  150. package/src/Sprinkle/actions/types.ts +91 -0
  151. package/src/Sprinkle/index.ts +351 -0
  152. package/src/Sprinkle/prompts.ts +118 -72
  153. package/src/Sprinkle/type-guards.ts +9 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Action runner utilities: input validation, execution, mode detection, and CLI arg parsing.
3
+ */
4
+
5
+ import { Value } from "@sinclair/typebox/value";
6
+ import { ActionError } from "./types.js";
7
+ /**
8
+ * Execute an action with raw (unvalidated) input.
9
+ *
10
+ * - Validates rawInput against action.inputSchema using Value.Check
11
+ * - Applies defaults and transforms via Value.Decode
12
+ * - Calls action.execute with decoded input
13
+ * - Wraps all errors in ActionError for consistent error handling
14
+ *
15
+ * @returns A discriminated IActionResult -- never throws
16
+ */
17
+ export async function executeAction(action, rawInput, context) {
18
+ // First check validity so we can collect all errors at once
19
+ if (!Value.Check(action.inputSchema, rawInput)) {
20
+ const errors = Array.from(Value.Errors(action.inputSchema, rawInput)).map(e => ({
21
+ path: e.path,
22
+ message: e.message,
23
+ value: e.value
24
+ }));
25
+ return {
26
+ success: false,
27
+ error: new ActionError(`Invalid input for action "${action.name}"`, "VALIDATION_ERROR", errors)
28
+ };
29
+ }
30
+
31
+ // Apply defaults and transforms
32
+ let decodedInput;
33
+ try {
34
+ decodedInput = Value.Decode(action.inputSchema, rawInput);
35
+ } catch (err) {
36
+ return {
37
+ success: false,
38
+ error: new ActionError(`Failed to decode input for action "${action.name}": ${err instanceof Error ? err.message : String(err)}`, "DECODE_ERROR", err)
39
+ };
40
+ }
41
+ try {
42
+ const output = await action.execute(decodedInput, context);
43
+ return {
44
+ success: true,
45
+ data: output
46
+ };
47
+ } catch (err) {
48
+ if (err instanceof ActionError) {
49
+ return {
50
+ success: false,
51
+ error: err
52
+ };
53
+ }
54
+ return {
55
+ success: false,
56
+ error: new ActionError(err instanceof Error ? err.message : String(err), "EXECUTION_ERROR", err)
57
+ };
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Detect the intended run mode from process argv.
63
+ *
64
+ * - No args or --interactive: "tui"
65
+ * - --mcp: "mcp"
66
+ * - --help or -h (with no positional arg): "help"
67
+ * - First positional arg (not starting with --): "cli"
68
+ * - Default fallback: "tui"
69
+ */
70
+ export function detectMode(argv) {
71
+ if (argv.length === 0) return "tui";
72
+
73
+ // Check for explicit flags first
74
+ if (argv.includes("--interactive")) return "tui";
75
+ if (argv.includes("--mcp")) return "mcp";
76
+
77
+ // Find first positional arg (not starting with -)
78
+ const firstPositional = argv.find(arg => !arg.startsWith("-"));
79
+ if (firstPositional !== undefined) return "cli";
80
+
81
+ // Flag-only invocations
82
+ if (argv.includes("--help") || argv.includes("-h")) return "help";
83
+ return "tui";
84
+ }
85
+
86
+ /**
87
+ * Parse CLI argv into an action name and key-value argument record.
88
+ *
89
+ * Expects:
90
+ * - First positional arg as the action name
91
+ * - Subsequent --flag value or --flag=value pairs as arguments
92
+ * - --no-flag for boolean negation
93
+ * - Values starting with { or [ are parsed as JSON
94
+ *
95
+ * Note: This is a preliminary implementation. Complex nested inputs
96
+ * should be provided as JSON strings (e.g. --input '{"key":"value"}').
97
+ *
98
+ * @throws Error if no action name is found in argv
99
+ */
100
+ export function parseCliArgs(argv) {
101
+ const args = {};
102
+
103
+ // Extract action name: first arg that is not a flag
104
+ const actionName = argv.find(arg => !arg.startsWith("-"));
105
+ if (!actionName) {
106
+ throw new Error("No action name provided. Usage: <action-name> [--flag value ...]");
107
+ }
108
+
109
+ // Helper to append a value, supporting repeated flags as arrays
110
+ const appendValue = (key, value) => {
111
+ const existing = args[key];
112
+ if (existing === undefined) {
113
+ args[key] = value;
114
+ } else if (Array.isArray(existing)) {
115
+ existing.push(value);
116
+ } else {
117
+ // Second occurrence of the same flag -- promote to array
118
+ args[key] = [existing, value];
119
+ }
120
+ };
121
+
122
+ // Parse flags after the action name
123
+ const flagArgs = argv.slice(argv.indexOf(actionName) + 1);
124
+ let i = 0;
125
+ while (i < flagArgs.length) {
126
+ const arg = flagArgs[i];
127
+ if (arg.startsWith("--no-")) {
128
+ // Boolean negation: --no-flag => flag: false
129
+ const key = arg.slice(5);
130
+ appendValue(key, false);
131
+ i++;
132
+ continue;
133
+ }
134
+ if (arg.startsWith("--")) {
135
+ const eqIdx = arg.indexOf("=");
136
+ if (eqIdx !== -1) {
137
+ // --flag=value syntax
138
+ const key = arg.slice(2, eqIdx);
139
+ const rawValue = arg.slice(eqIdx + 1);
140
+ appendValue(key, parseArgValue(rawValue));
141
+ i++;
142
+ } else {
143
+ // --flag value syntax
144
+ const key = arg.slice(2);
145
+ const nextArg = flagArgs[i + 1];
146
+ if (nextArg !== undefined && !nextArg.startsWith("-")) {
147
+ appendValue(key, parseArgValue(nextArg));
148
+ i += 2;
149
+ } else {
150
+ // Flag with no value: treat as boolean true
151
+ appendValue(key, true);
152
+ i++;
153
+ }
154
+ }
155
+ continue;
156
+ }
157
+
158
+ // Skip non-flag positional args (there should only be the action name)
159
+ i++;
160
+ }
161
+ return {
162
+ actionName,
163
+ args
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Parse a single CLI argument value.
169
+ * Detects JSON objects/arrays; otherwise returns the string as-is.
170
+ */
171
+ function parseArgValue(raw) {
172
+ const trimmed = raw.trim();
173
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
174
+ try {
175
+ return JSON.parse(trimmed);
176
+ } catch {
177
+ // Fall through: return as string if JSON parse fails
178
+ }
179
+ }
180
+ return raw;
181
+ }
182
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","names":["Value","ActionError","executeAction","action","rawInput","context","Check","inputSchema","errors","Array","from","Errors","map","e","path","message","value","success","error","name","decodedInput","Decode","err","Error","String","output","execute","data","detectMode","argv","length","includes","firstPositional","find","arg","startsWith","undefined","parseCliArgs","args","actionName","appendValue","key","existing","isArray","push","flagArgs","slice","indexOf","i","eqIdx","rawValue","parseArgValue","nextArg","raw","trimmed","trim","JSON","parse"],"sources":["../../../../src/Sprinkle/actions/runner.ts"],"sourcesContent":["/**\n * Action runner utilities: input validation, execution, mode detection, and CLI arg parsing.\n */\n\nimport { Value } from \"@sinclair/typebox/value\";\nimport type { TSchema } from \"@sinclair/typebox\";\nimport { ActionError } from \"./types.js\";\nimport type { IAction, IActionContext, IActionResult } from \"./types.js\";\n\n/**\n * Execute an action with raw (unvalidated) input.\n *\n * - Validates rawInput against action.inputSchema using Value.Check\n * - Applies defaults and transforms via Value.Decode\n * - Calls action.execute with decoded input\n * - Wraps all errors in ActionError for consistent error handling\n *\n * @returns A discriminated IActionResult -- never throws\n */\nexport async function executeAction<TInput, TOutput, S extends TSchema>(\n action: IAction<TInput, TOutput, S>,\n rawInput: unknown,\n context: IActionContext<S>,\n): Promise<IActionResult<TOutput>> {\n // First check validity so we can collect all errors at once\n if (!Value.Check(action.inputSchema, rawInput)) {\n const errors = Array.from(Value.Errors(action.inputSchema, rawInput)).map(\n (e) => ({ path: e.path, message: e.message, value: e.value }),\n );\n return {\n success: false,\n error: new ActionError(\n `Invalid input for action \"${action.name}\"`,\n \"VALIDATION_ERROR\",\n errors,\n ),\n };\n }\n\n // Apply defaults and transforms\n let decodedInput: TInput;\n try {\n decodedInput = Value.Decode(action.inputSchema, rawInput) as TInput;\n } catch (err) {\n return {\n success: false,\n error: new ActionError(\n `Failed to decode input for action \"${action.name}\": ${err instanceof Error ? err.message : String(err)}`,\n \"DECODE_ERROR\",\n err,\n ),\n };\n }\n\n try {\n const output = await action.execute(decodedInput, context);\n return { success: true, data: output };\n } catch (err) {\n if (err instanceof ActionError) {\n return { success: false, error: err };\n }\n return {\n success: false,\n error: new ActionError(\n err instanceof Error ? err.message : String(err),\n \"EXECUTION_ERROR\",\n err,\n ),\n };\n }\n}\n\n/**\n * Detect the intended run mode from process argv.\n *\n * - No args or --interactive: \"tui\"\n * - --mcp: \"mcp\"\n * - --help or -h (with no positional arg): \"help\"\n * - First positional arg (not starting with --): \"cli\"\n * - Default fallback: \"tui\"\n */\nexport function detectMode(\n argv: string[],\n): \"tui\" | \"cli\" | \"mcp\" | \"help\" {\n if (argv.length === 0) return \"tui\";\n\n // Check for explicit flags first\n if (argv.includes(\"--interactive\")) return \"tui\";\n if (argv.includes(\"--mcp\")) return \"mcp\";\n\n // Find first positional arg (not starting with -)\n const firstPositional = argv.find((arg) => !arg.startsWith(\"-\"));\n if (firstPositional !== undefined) return \"cli\";\n\n // Flag-only invocations\n if (argv.includes(\"--help\") || argv.includes(\"-h\")) return \"help\";\n\n return \"tui\";\n}\n\n/**\n * Parse CLI argv into an action name and key-value argument record.\n *\n * Expects:\n * - First positional arg as the action name\n * - Subsequent --flag value or --flag=value pairs as arguments\n * - --no-flag for boolean negation\n * - Values starting with { or [ are parsed as JSON\n *\n * Note: This is a preliminary implementation. Complex nested inputs\n * should be provided as JSON strings (e.g. --input '{\"key\":\"value\"}').\n *\n * @throws Error if no action name is found in argv\n */\nexport function parseCliArgs(\n argv: string[],\n): { actionName: string; args: Record<string, unknown> } {\n const args: Record<string, unknown> = {};\n\n // Extract action name: first arg that is not a flag\n const actionName = argv.find((arg) => !arg.startsWith(\"-\"));\n if (!actionName) {\n throw new Error(\n \"No action name provided. Usage: <action-name> [--flag value ...]\",\n );\n }\n\n // Helper to append a value, supporting repeated flags as arrays\n const appendValue = (key: string, value: unknown): void => {\n const existing = args[key];\n if (existing === undefined) {\n args[key] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n // Second occurrence of the same flag -- promote to array\n args[key] = [existing, value];\n }\n };\n\n // Parse flags after the action name\n const flagArgs = argv.slice(argv.indexOf(actionName) + 1);\n let i = 0;\n while (i < flagArgs.length) {\n const arg = flagArgs[i]!;\n\n if (arg.startsWith(\"--no-\")) {\n // Boolean negation: --no-flag => flag: false\n const key = arg.slice(5);\n appendValue(key, false);\n i++;\n continue;\n }\n\n if (arg.startsWith(\"--\")) {\n const eqIdx = arg.indexOf(\"=\");\n if (eqIdx !== -1) {\n // --flag=value syntax\n const key = arg.slice(2, eqIdx);\n const rawValue = arg.slice(eqIdx + 1);\n appendValue(key, parseArgValue(rawValue));\n i++;\n } else {\n // --flag value syntax\n const key = arg.slice(2);\n const nextArg = flagArgs[i + 1];\n if (nextArg !== undefined && !nextArg.startsWith(\"-\")) {\n appendValue(key, parseArgValue(nextArg));\n i += 2;\n } else {\n // Flag with no value: treat as boolean true\n appendValue(key, true);\n i++;\n }\n }\n continue;\n }\n\n // Skip non-flag positional args (there should only be the action name)\n i++;\n }\n\n return { actionName, args };\n}\n\n/**\n * Parse a single CLI argument value.\n * Detects JSON objects/arrays; otherwise returns the string as-is.\n */\nfunction parseArgValue(raw: string): unknown {\n const trimmed = raw.trim();\n if (trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\")) {\n try {\n return JSON.parse(trimmed);\n } catch {\n // Fall through: return as string if JSON parse fails\n }\n }\n return raw;\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * TUI helper utilities for integrating the action system with interactive prompts.
3
+ *
4
+ * Provides:
5
+ * - promptAndExecute: gather input via FillInStruct then run executeAction
6
+ *
7
+ * @remarks
8
+ * Functions in this module require a fully initialized Sprinkle instance with
9
+ * terminal access. They are intended for TUI mode only; in CLI or MCP mode use
10
+ * executeAction directly.
11
+ */
12
+
13
+ import { ActionError } from "./types.js";
14
+ import { executeAction } from "./runner.js";
15
+
16
+ // Import Sprinkle as a type only to avoid circular dependency
17
+
18
+ // Import UserCancelledError to detect user cancellations from FillInStruct
19
+ import { UserCancelledError } from "../types.js";
20
+
21
+ /**
22
+ * Interactively gather input for an action using FillInStruct, then execute
23
+ * the action and return the result.
24
+ *
25
+ * The function:
26
+ * 1. Calls `sprinkle.FillInStruct(action.inputSchema, defaults)` to collect
27
+ * input from the user via TUI prompts.
28
+ * 2. Builds an `IActionContext` from the sprinkle instance.
29
+ * 3. Calls `executeAction(action, input, context)` and returns the result.
30
+ *
31
+ * If the user cancels the prompt (`UserCancelledError` is thrown), the function
32
+ * returns an `IActionFailure` with code `USER_CANCELLED` rather than throwing.
33
+ *
34
+ * Two separate schema type parameters are used:
35
+ * - `S` is the concrete settings schema of the `Sprinkle` instance.
36
+ * - `SA` is the settings schema the action was typed against (often `TSchema`).
37
+ *
38
+ * This allows a `Sprinkle<TObject<...>>` to be passed alongside an action
39
+ * typed with the base `TSchema` constraint (the same pattern used by all
40
+ * built-in actions), while still preserving full type safety for TInput/TOutput.
41
+ *
42
+ * @param sprinkle - An initialized Sprinkle instance (TUI mode).
43
+ * @param action - The action to prompt for and execute.
44
+ * @param defaults - Optional partial defaults pre-filled in the prompt.
45
+ * @returns An `IActionResult` discriminated union (never throws).
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const result = await promptAndExecute(sprinkle, greetAction);
50
+ * if (result.success) {
51
+ * console.log(result.data.greeting);
52
+ * } else {
53
+ * if (result.error.code === "USER_CANCELLED") return; // user backed out
54
+ * console.error(result.error.message);
55
+ * }
56
+ * ```
57
+ */
58
+ export async function promptAndExecute(sprinkle, action,
59
+ // defaults is typed as Partial<TInput> -- the static input shape.
60
+ // Internally it is cast to `never` because FillInStruct expects TExact<U>
61
+ // which is derived from the inputSchema's TSchema generic, not TInput.
62
+ defaults) {
63
+ let rawInput;
64
+ try {
65
+ // FillInStruct<U> is generic over TSchema; we pass the action's inputSchema
66
+ // and cast defaults to satisfy the generic bound.
67
+ rawInput = await sprinkle.FillInStruct(action.inputSchema, defaults);
68
+ } catch (err) {
69
+ if (err instanceof UserCancelledError) {
70
+ return {
71
+ success: false,
72
+ error: new ActionError("User cancelled the input prompt.", "USER_CANCELLED", err)
73
+ };
74
+ }
75
+ // Any other error from FillInStruct is unexpected -- wrap it
76
+ return {
77
+ success: false,
78
+ error: new ActionError(`Prompt failed: ${err instanceof Error ? err.message : String(err)}`, "PROMPT_ERROR", err)
79
+ };
80
+ }
81
+
82
+ // Build the context. The action's SA context is satisfied by the concrete
83
+ // Sprinkle<S> instance via a cast -- the action only accesses sprinkle via
84
+ // the IActionContext interface which it should treat as opaque when SA = TSchema.
85
+ const context = {
86
+ sprinkle: sprinkle,
87
+ settings: sprinkle.settings
88
+ };
89
+ return executeAction(action, rawInput, context);
90
+ }
91
+ //# sourceMappingURL=tui-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-helpers.js","names":["ActionError","executeAction","UserCancelledError","promptAndExecute","sprinkle","action","defaults","rawInput","FillInStruct","inputSchema","err","success","error","Error","message","String","context","settings"],"sources":["../../../../src/Sprinkle/actions/tui-helpers.ts"],"sourcesContent":["/**\n * TUI helper utilities for integrating the action system with interactive prompts.\n *\n * Provides:\n * - promptAndExecute: gather input via FillInStruct then run executeAction\n *\n * @remarks\n * Functions in this module require a fully initialized Sprinkle instance with\n * terminal access. They are intended for TUI mode only; in CLI or MCP mode use\n * executeAction directly.\n */\n\nimport type { TSchema } from \"@sinclair/typebox\";\nimport { ActionError } from \"./types.js\";\nimport type { IAction, IActionContext, IActionResult } from \"./types.js\";\nimport { executeAction } from \"./runner.js\";\n\n// Import Sprinkle as a type only to avoid circular dependency\nimport type { Sprinkle } from \"../index.js\";\n\n// Import UserCancelledError to detect user cancellations from FillInStruct\nimport { UserCancelledError } from \"../types.js\";\n\n/**\n * Interactively gather input for an action using FillInStruct, then execute\n * the action and return the result.\n *\n * The function:\n * 1. Calls `sprinkle.FillInStruct(action.inputSchema, defaults)` to collect\n * input from the user via TUI prompts.\n * 2. Builds an `IActionContext` from the sprinkle instance.\n * 3. Calls `executeAction(action, input, context)` and returns the result.\n *\n * If the user cancels the prompt (`UserCancelledError` is thrown), the function\n * returns an `IActionFailure` with code `USER_CANCELLED` rather than throwing.\n *\n * Two separate schema type parameters are used:\n * - `S` is the concrete settings schema of the `Sprinkle` instance.\n * - `SA` is the settings schema the action was typed against (often `TSchema`).\n *\n * This allows a `Sprinkle<TObject<...>>` to be passed alongside an action\n * typed with the base `TSchema` constraint (the same pattern used by all\n * built-in actions), while still preserving full type safety for TInput/TOutput.\n *\n * @param sprinkle - An initialized Sprinkle instance (TUI mode).\n * @param action - The action to prompt for and execute.\n * @param defaults - Optional partial defaults pre-filled in the prompt.\n * @returns An `IActionResult` discriminated union (never throws).\n *\n * @example\n * ```ts\n * const result = await promptAndExecute(sprinkle, greetAction);\n * if (result.success) {\n * console.log(result.data.greeting);\n * } else {\n * if (result.error.code === \"USER_CANCELLED\") return; // user backed out\n * console.error(result.error.message);\n * }\n * ```\n */\nexport async function promptAndExecute<\n TInput,\n TOutput,\n S extends TSchema,\n SA extends TSchema,\n>(\n sprinkle: Sprinkle<S>,\n action: IAction<TInput, TOutput, SA>,\n // defaults is typed as Partial<TInput> -- the static input shape.\n // Internally it is cast to `never` because FillInStruct expects TExact<U>\n // which is derived from the inputSchema's TSchema generic, not TInput.\n defaults?: Partial<TInput>,\n): Promise<IActionResult<TOutput>> {\n let rawInput: unknown;\n\n try {\n // FillInStruct<U> is generic over TSchema; we pass the action's inputSchema\n // and cast defaults to satisfy the generic bound.\n rawInput = await sprinkle.FillInStruct(\n action.inputSchema,\n defaults as never,\n );\n } catch (err) {\n if (err instanceof UserCancelledError) {\n return {\n success: false,\n error: new ActionError(\n \"User cancelled the input prompt.\",\n \"USER_CANCELLED\",\n err,\n ),\n };\n }\n // Any other error from FillInStruct is unexpected -- wrap it\n return {\n success: false,\n error: new ActionError(\n `Prompt failed: ${err instanceof Error ? err.message : String(err)}`,\n \"PROMPT_ERROR\",\n err,\n ),\n };\n }\n\n // Build the context. The action's SA context is satisfied by the concrete\n // Sprinkle<S> instance via a cast -- the action only accesses sprinkle via\n // the IActionContext interface which it should treat as opaque when SA = TSchema.\n const context = {\n sprinkle: sprinkle as unknown as Sprinkle<SA>,\n settings: sprinkle.settings as unknown,\n } as IActionContext<SA>;\n\n return executeAction(action, rawInput, context);\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA,SAASA,WAAW,QAAQ,YAAY;AAExC,SAASC,aAAa,QAAQ,aAAa;;AAE3C;;AAGA;AACA,SAASC,kBAAkB,QAAQ,aAAa;;AAEhD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,gBAAgBA,CAMpCC,QAAqB,EACrBC,MAAoC;AACpC;AACA;AACA;AACAC,QAA0B,EACO;EACjC,IAAIC,QAAiB;EAErB,IAAI;IACF;IACA;IACAA,QAAQ,GAAG,MAAMH,QAAQ,CAACI,YAAY,CACpCH,MAAM,CAACI,WAAW,EAClBH,QACF,CAAC;EACH,CAAC,CAAC,OAAOI,GAAG,EAAE;IACZ,IAAIA,GAAG,YAAYR,kBAAkB,EAAE;MACrC,OAAO;QACLS,OAAO,EAAE,KAAK;QACdC,KAAK,EAAE,IAAIZ,WAAW,CACpB,kCAAkC,EAClC,gBAAgB,EAChBU,GACF;MACF,CAAC;IACH;IACA;IACA,OAAO;MACLC,OAAO,EAAE,KAAK;MACdC,KAAK,EAAE,IAAIZ,WAAW,CACpB,kBAAkBU,GAAG,YAAYG,KAAK,GAAGH,GAAG,CAACI,OAAO,GAAGC,MAAM,CAACL,GAAG,CAAC,EAAE,EACpE,cAAc,EACdA,GACF;IACF,CAAC;EACH;;EAEA;EACA;EACA;EACA,MAAMM,OAAO,GAAG;IACdZ,QAAQ,EAAEA,QAAmC;IAC7Ca,QAAQ,EAAEb,QAAQ,CAACa;EACrB,CAAuB;EAEvB,OAAOhB,aAAa,CAACI,MAAM,EAAEE,QAAQ,EAAES,OAAO,CAAC;AACjD","ignoreList":[]}
@@ -0,0 +1,61 @@
1
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
2
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
3
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4
+ /**
5
+ * Core action types for the action-centric architecture.
6
+ * Actions are pure functions that receive validated typed input
7
+ * and return typed output, with no interactive prompts.
8
+ */
9
+
10
+ // Import Sprinkle only as a type to avoid circular dependency
11
+
12
+ /**
13
+ * Utility type to extract the static type from a TypeBox schema.
14
+ */
15
+
16
+ /**
17
+ * Context passed to action execute functions.
18
+ * Provides access to the Sprinkle instance and typed settings shorthand.
19
+ */
20
+
21
+ /**
22
+ * Successful action result.
23
+ */
24
+
25
+ /**
26
+ * Failed action result.
27
+ */
28
+
29
+ /**
30
+ * Discriminated union result type for actions.
31
+ */
32
+
33
+ /**
34
+ * Structured error type for action failures.
35
+ * Carries a machine-readable code and optional details.
36
+ */
37
+ export class ActionError extends Error {
38
+ constructor(message, code, details) {
39
+ super(message);
40
+ _defineProperty(this, "code", void 0);
41
+ _defineProperty(this, "details", void 0);
42
+ this.name = "ActionError";
43
+ this.code = code;
44
+ this.details = details;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * A typed action with input/output schemas and an execute function.
50
+ *
51
+ * @template TInput - The static input type (derived from inputSchema)
52
+ * @template TOutput - The static output type (derived from outputSchema)
53
+ * @template S - The Sprinkle settings schema type
54
+ */
55
+
56
+ /**
57
+ * Convenience alias for an action with any input/output types.
58
+ * Used for registry storage where exact types are not required.
59
+ */
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":["ActionError","Error","constructor","message","code","details","_defineProperty","name"],"sources":["../../../../src/Sprinkle/actions/types.ts"],"sourcesContent":["/**\n * Core action types for the action-centric architecture.\n * Actions are pure functions that receive validated typed input\n * and return typed output, with no interactive prompts.\n */\n\nimport type { TSchema, Static } from \"@sinclair/typebox\";\n// Import Sprinkle only as a type to avoid circular dependency\nimport type { Sprinkle } from \"../index.js\";\n\n/**\n * Utility type to extract the static type from a TypeBox schema.\n */\ntype TExact<T> = T extends TSchema ? Static<T> : T;\n\n/**\n * Context passed to action execute functions.\n * Provides access to the Sprinkle instance and typed settings shorthand.\n */\nexport interface IActionContext<S extends TSchema> {\n /** The full Sprinkle instance (settings, wallet, provider helpers) */\n sprinkle: Sprinkle<S>;\n /** Typed shorthand for sprinkle.settings */\n settings: TExact<S>;\n}\n\n/**\n * Successful action result.\n */\nexport interface IActionSuccess<T> {\n success: true;\n data: T;\n}\n\n/**\n * Failed action result.\n */\nexport interface IActionFailure {\n success: false;\n error: ActionError;\n}\n\n/**\n * Discriminated union result type for actions.\n */\nexport type IActionResult<T> = IActionSuccess<T> | IActionFailure;\n\n/**\n * Structured error type for action failures.\n * Carries a machine-readable code and optional details.\n */\nexport class ActionError extends Error {\n code: string;\n details: unknown;\n\n constructor(message: string, code: string, details?: unknown) {\n super(message);\n this.name = \"ActionError\";\n this.code = code;\n this.details = details;\n }\n}\n\n/**\n * A typed action with input/output schemas and an execute function.\n *\n * @template TInput - The static input type (derived from inputSchema)\n * @template TOutput - The static output type (derived from outputSchema)\n * @template S - The Sprinkle settings schema type\n */\nexport interface IAction<TInput, TOutput, S extends TSchema> {\n /** Unique kebab-case identifier for this action */\n name: string;\n /** Human-readable description of what this action does */\n description: string;\n /** Optional grouping category for display/help */\n category?: string;\n /** TypeBox schema for validating and decoding input */\n inputSchema: TSchema;\n /** TypeBox schema describing the output shape */\n outputSchema: TSchema;\n /** Pure execution function -- no interactive prompts allowed */\n execute: (input: TInput, context: IActionContext<S>) => Promise<TOutput>;\n}\n\n/**\n * Convenience alias for an action with any input/output types.\n * Used for registry storage where exact types are not required.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyAction<S extends TSchema> = IAction<any, any, S>;\n"],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;;AAGA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;AACA;;AAQA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA,OAAO,MAAMA,WAAW,SAASC,KAAK,CAAC;EAIrCC,WAAWA,CAACC,OAAe,EAAEC,IAAY,EAAEC,OAAiB,EAAE;IAC5D,KAAK,CAACF,OAAO,CAAC;IAACG,eAAA;IAAAA,eAAA;IACf,IAAI,CAACC,IAAI,GAAG,aAAa;IACzB,IAAI,CAACH,IAAI,GAAGA,IAAI;IAChB,IAAI,CAACC,OAAO,GAAGA,OAAO;EACxB;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAgBA;AACA;AACA;AACA;AACA","ignoreList":[]}
@@ -20,7 +20,7 @@ import { UserCancelledError } from "./types.js";
20
20
  export { NetworkSchema, MultisigScriptModule, MultisigScript, ProviderSettingsSchema, WalletSettingsSchema } from "./schemas.js";
21
21
  // Import and re-export type guards
22
22
  import { isOptional, isImport, isArray, isBigInt, isLiteral, isObject, isRef, isString, isThis, isTuple, isUnion, isSensitive } from "./type-guards.js";
23
- export { isOptional, isImport, isArray, isBigInt, isLiteral, isObject, isRef, isString, isThis, isTuple, isUnion, isSensitive, isNull, isNullable, unwrapNullable, hasDefault, getDefault } from "./type-guards.js";
23
+ export { isOptional, isImport, isArray, isBigInt, isBoolean, isInteger, isLiteral, isNumber, isObject, isRef, isString, isThis, isTuple, isUnion, isSensitive, isNull, isNullable, unwrapNullable, hasDefault, getDefault } from "./type-guards.js";
24
24
 
25
25
  // Import schemas for use in this file
26
26
 
@@ -37,6 +37,10 @@ import { countSignatures, getRequiredSigners, getTxBodyHash, formatHash, mergeSi
37
37
  // Import menu modules
38
38
  import { promptObject, promptArray } from "./menus/index.js";
39
39
  import { formatPath } from "./utils/formatting.js";
40
+
41
+ // Import and re-export action system
42
+ import { ActionRegistry, ActionError, executeAction, detectMode, parseCliArgs, generateActionHelp, generateAppHelp, runCli, runMcp } from "./actions/index.js";
43
+ export { ActionRegistry, ActionError, executeAction, detectMode, parseCliArgs, generateActionHelp, camelToKebab, kebabToCamel, coerceValue, parseArgvWithSchema, generateAppHelp, runCli, typeboxToJsonSchema, coerceMcpInput, getMcpSdk, createMcpServer, runMcp, getBuiltinActions, promptAndExecute } from "./actions/index.js";
40
44
  export class Sprinkle {
41
45
  constructor(type, storagePath, options) {
42
46
  _defineProperty(this, "storagePath", void 0);
@@ -50,9 +54,11 @@ export class Sprinkle {
50
54
  createdAt: "",
51
55
  updatedAt: ""
52
56
  });
57
+ _defineProperty(this, "actionRegistry", void 0);
53
58
  this.type = type;
54
59
  this.storagePath = storagePath;
55
60
  this.options = options ?? {};
61
+ this.actionRegistry = new ActionRegistry();
56
62
  }
57
63
 
58
64
  // --- Current Profile Accessor ---
@@ -570,6 +576,87 @@ export class Sprinkle {
570
576
  getDisplaySettings() {
571
577
  return maskSensitiveFields(this.settings, this.type);
572
578
  }
579
+
580
+ // --- Non-interactive profile management (for CLI/MCP actions) ---
581
+
582
+ /**
583
+ * Look up a profile entry by its ID without loading it.
584
+ * Returns undefined if no profile with the given ID exists.
585
+ */
586
+ getProfileById(id) {
587
+ return this.scanProfiles().find(p => p.id === id);
588
+ }
589
+
590
+ /**
591
+ * Create a new profile file without interactive prompts.
592
+ * Does NOT switch the active profile.
593
+ *
594
+ * @throws ActionError with code DUPLICATE_PROFILE if a profile with the same name already exists.
595
+ * @returns The created IProfileEntry
596
+ */
597
+ async createProfileNonInteractive(name, description, initialSettings) {
598
+ const profiles = this.scanProfiles();
599
+ const nameLower = name.toLowerCase();
600
+ const duplicate = profiles.find(p => p.meta.name.toLowerCase() === nameLower);
601
+ if (duplicate) {
602
+ throw new ActionError(`A profile named "${name}" already exists.`, "DUPLICATE_PROFILE", {
603
+ existingId: duplicate.id
604
+ });
605
+ }
606
+ const profilesDir = Sprinkle.profilesDir(this.storagePath);
607
+ if (!fs.existsSync(profilesDir)) {
608
+ fs.mkdirSync(profilesDir, {
609
+ recursive: true
610
+ });
611
+ }
612
+ const baseId = Sprinkle.sanitizeProfileId(name);
613
+ const id = Sprinkle.findAvailableId(profilesDir, baseId);
614
+ const now = new Date().toISOString();
615
+ const meta = {
616
+ name,
617
+ description,
618
+ createdAt: now,
619
+ updatedAt: now
620
+ };
621
+ const profileData = {
622
+ meta,
623
+ settings: initialSettings ?? {},
624
+ defaults: {}
625
+ };
626
+ fs.writeFileSync(path.join(profilesDir, `${id}.json`), JSON.stringify(profileData, bigIntReplacer, 2), "utf-8");
627
+ return {
628
+ id,
629
+ meta
630
+ };
631
+ }
632
+
633
+ /**
634
+ * Delete a profile file by ID.
635
+ *
636
+ * @throws ActionError with code PROFILE_NOT_FOUND if no profile with the given ID exists.
637
+ * @throws ActionError with code CANNOT_DELETE_ONLY_PROFILE if this is the only profile.
638
+ * @throws ActionError with code CANNOT_DELETE_ACTIVE_PROFILE if this is the currently active profile.
639
+ */
640
+ deleteProfileById(id) {
641
+ const profiles = this.scanProfiles();
642
+ const profile = profiles.find(p => p.id === id);
643
+ if (!profile) {
644
+ throw new ActionError(`Profile "${id}" not found.`, "PROFILE_NOT_FOUND", {
645
+ id
646
+ });
647
+ }
648
+ if (profiles.length === 1) {
649
+ throw new ActionError("Cannot delete the only profile.", "CANNOT_DELETE_ONLY_PROFILE", {
650
+ id
651
+ });
652
+ }
653
+ if (id === this.profileId) {
654
+ throw new ActionError("Cannot delete the active profile. Switch to a different profile first.", "CANNOT_DELETE_ACTIVE_PROFILE", {
655
+ id
656
+ });
657
+ }
658
+ fs.unlinkSync(Sprinkle.profilePath(this.storagePath, id));
659
+ }
573
660
  async TxDialog(blaze, tx, opts) {
574
661
  let currentTx = tx;
575
662
  let expanded = false;
@@ -1112,5 +1199,177 @@ export class Sprinkle {
1112
1199
  }
1113
1200
  return def;
1114
1201
  }
1202
+
1203
+ // --- Action System ---
1204
+
1205
+ /**
1206
+ * Register an action on this Sprinkle instance.
1207
+ * Delegates to the internal ActionRegistry with name and schema validation.
1208
+ */
1209
+ registerAction(action) {
1210
+ this.actionRegistry.register(action);
1211
+ }
1212
+
1213
+ /**
1214
+ * Retrieve a registered action by name.
1215
+ * @returns The action, or undefined if not registered
1216
+ */
1217
+ getAction(name) {
1218
+ return this.actionRegistry.get(name);
1219
+ }
1220
+
1221
+ /**
1222
+ * List all registered actions.
1223
+ */
1224
+ listActions() {
1225
+ return this.actionRegistry.list();
1226
+ }
1227
+
1228
+ /**
1229
+ * List all registered actions grouped by category.
1230
+ * Uncategorized actions are grouped under "default".
1231
+ */
1232
+ listActionsByCategory() {
1233
+ return this.actionRegistry.listByCategory();
1234
+ }
1235
+
1236
+ /**
1237
+ * Execute a registered action by name with raw (unvalidated) input.
1238
+ * Input is validated against the action's inputSchema before execution.
1239
+ *
1240
+ * @throws Error if the action name is not registered
1241
+ */
1242
+ async runAction(name, input) {
1243
+ const action = this.actionRegistry.get(name);
1244
+ if (!action) {
1245
+ throw new Error(`Action "${name}" is not registered. Available actions: ${this.actionRegistry.list().map(a => a.name).join(", ") || "(none)"}`);
1246
+ }
1247
+ const context = {
1248
+ sprinkle: this,
1249
+ settings: this.settings
1250
+ };
1251
+ return executeAction(action, input, context);
1252
+ }
1253
+
1254
+ /**
1255
+ * Non-interactive profile initialization for CLI/MCP modes.
1256
+ * Resolves a profile without any user prompts.
1257
+ *
1258
+ * Resolution order:
1259
+ * 1. If profileName provided, load that profile directly
1260
+ * 2. If exactly one profile exists, auto-select it
1261
+ * 3. If multiple profiles exist and no name given, throw with available names
1262
+ * 4. If no profiles exist, throw instructing user to run in interactive mode
1263
+ */
1264
+ async initNonInteractive(profileName) {
1265
+ await this.migrateIfNeeded();
1266
+ if (profileName) {
1267
+ await this.loadProfile(profileName);
1268
+ return;
1269
+ }
1270
+ const profiles = this.scanProfiles();
1271
+ if (profiles.length === 0) {
1272
+ throw new Error("No profiles found. Run in interactive mode to create one.");
1273
+ }
1274
+ if (profiles.length === 1) {
1275
+ await this.loadProfile(profiles[0].id);
1276
+ return;
1277
+ }
1278
+
1279
+ // Multiple profiles without a --profile flag
1280
+ const names = profiles.map(p => `"${p.id}" (${p.meta.name})`).join(", ");
1281
+ throw new Error(`Multiple profiles found. Specify one with --profile <id>. Available profiles: ${names}`);
1282
+ }
1283
+
1284
+ /**
1285
+ * Static entry point that detects mode and runs accordingly.
1286
+ *
1287
+ * Modes:
1288
+ * - "tui" -- Interactive TUI menu (requires menu option)
1289
+ * - "cli" -- CLI action execution (parses argv for action name and args)
1290
+ * - "mcp" -- MCP protocol mode (not yet implemented)
1291
+ * - "help" -- Print available actions and exit
1292
+ */
1293
+ static async run(opts) {
1294
+ const argv = opts.argv ?? process.argv.slice(2);
1295
+ const mode = detectMode(argv);
1296
+ if (mode === "tui") {
1297
+ if (!opts.menu) {
1298
+ throw new Error("TUI mode requires a menu. Provide a menu option or pass --help to see available actions.");
1299
+ }
1300
+ const sprinkle = await Sprinkle.New(opts.type, opts.storagePath, opts.options);
1301
+ // Register any provided actions
1302
+ for (const action of opts.actions ?? []) {
1303
+ sprinkle.registerAction(action);
1304
+ }
1305
+ await sprinkle.showMenu(opts.menu);
1306
+ return;
1307
+ }
1308
+ if (mode === "help") {
1309
+ const sprinkle = new Sprinkle(opts.type, opts.storagePath, opts.options);
1310
+ for (const action of opts.actions ?? []) {
1311
+ sprinkle.registerAction(action);
1312
+ }
1313
+ console.log(generateAppHelp(sprinkle.listActions()));
1314
+ return;
1315
+ }
1316
+ if (mode === "cli") {
1317
+ const {
1318
+ actionName,
1319
+ args
1320
+ } = parseCliArgs(argv);
1321
+
1322
+ // Extract --profile flag from args if present
1323
+ const profileName = typeof args["profile"] === "string" ? args["profile"] : undefined;
1324
+ const sprinkle = new Sprinkle(opts.type, opts.storagePath, opts.options);
1325
+ for (const action of opts.actions ?? []) {
1326
+ sprinkle.registerAction(action);
1327
+ }
1328
+
1329
+ // Handle action-specific --help BEFORE profile initialization
1330
+ // (help should work without a profile)
1331
+ if (args["help"] === true) {
1332
+ const action = sprinkle.getAction(actionName);
1333
+ if (action) {
1334
+ console.log(generateActionHelp(action));
1335
+ } else {
1336
+ console.log(`Unknown action "${actionName}". Run --help to see available actions.`);
1337
+ }
1338
+ return;
1339
+ }
1340
+ await sprinkle.initNonInteractive(profileName);
1341
+
1342
+ // Remove internal flags from args before passing to action
1343
+ const actionArgs = {
1344
+ ...args
1345
+ };
1346
+ delete actionArgs["profile"];
1347
+ delete actionArgs["help"];
1348
+
1349
+ // Build context and delegate to runCli for proper error routing
1350
+ const context = {
1351
+ sprinkle,
1352
+ settings: sprinkle.settings
1353
+ };
1354
+ await runCli(sprinkle, actionName, actionArgs, context);
1355
+ return;
1356
+ }
1357
+ if (mode === "mcp") {
1358
+ // Derive a server name from the storage path basename, falling back to a
1359
+ // sensible default.
1360
+ const serverName = path.basename(opts.storagePath) || "sprinkle-mcp";
1361
+ const sprinkle = new Sprinkle(opts.type, opts.storagePath, opts.options);
1362
+ for (const action of opts.actions ?? []) {
1363
+ sprinkle.registerAction(action);
1364
+ }
1365
+
1366
+ // Read profile name from environment variable (MCP clients have no
1367
+ // interactive terminal to prompt for profile selection).
1368
+ const profileName = process.env["SPRINKLE_PROFILE"];
1369
+ await sprinkle.initNonInteractive(profileName);
1370
+ await runMcp(sprinkle, serverName);
1371
+ return;
1372
+ }
1373
+ }
1115
1374
  }
1116
1375
  //# sourceMappingURL=index.js.map