@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
@@ -17,10 +17,12 @@ export { UserCancelledError } from "./types.js";
17
17
  import { UserCancelledError } from "./types.js";
18
18
 
19
19
  // Re-export schemas from schemas.ts
20
- export { NetworkSchema, MultisigScriptModule, MultisigScript, ProviderSettingsSchema, WalletSettingsSchema } from "./schemas.js";
20
+ export { NetworkSchema, MultisigScriptModule, MultisigScript, NativeScriptInputSchema, NativeScriptsParam, ProviderSettingsSchema, WalletSettingsSchema } from "./schemas.js";
21
+ import { MultisigScript as MultisigScriptSchema } from "./schemas.js";
22
+
21
23
  // Import and re-export type guards
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";
24
+ import { isOptional, isImport, isArray, isBigInt, isBoolean, isLiteral, isObject, isRef, isString, isThis, isTuple, isUnion, isSensitive, hasDefault, getDefault } from "./type-guards.js";
25
+ 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
26
 
25
27
  // Import schemas for use in this file
26
28
 
@@ -37,12 +39,19 @@ import { countSignatures, getRequiredSigners, getTxBodyHash, formatHash, mergeSi
37
39
  // Import menu modules
38
40
  import { promptObject, promptArray } from "./menus/index.js";
39
41
  import { formatPath } from "./utils/formatting.js";
42
+
43
+ // Import and re-export action system
44
+ import { ActionRegistry, ActionError, executeAction, detectMode, parseCliArgs, generateActionHelp, generateAppHelp, runCli, runMcp, promptAndExecute } from "./actions/index.js";
45
+ import { toNativeScript } from "./actions/index.js";
46
+ export { ActionRegistry, ActionError, executeAction, detectMode, parseCliArgs, generateActionHelp, camelToKebab, kebabToCamel, coerceValue, parseArgvWithSchema, generateAppHelp, runCli, typeboxToJsonSchema, coerceMcpInput, getMcpSdk, createMcpServer, runMcp, getBuiltinActions, promptAndExecute } from "./actions/index.js";
47
+ import { mintToken, simpleSend, registerStakeScript } from "./actions/builtin/utility-actions.js";
40
48
  export class Sprinkle {
41
49
  constructor(type, storagePath, options) {
42
50
  _defineProperty(this, "storagePath", void 0);
43
51
  _defineProperty(this, "settings", {});
44
52
  _defineProperty(this, "type", void 0);
45
53
  _defineProperty(this, "defaults", {});
54
+ _defineProperty(this, "addressbook", {});
46
55
  _defineProperty(this, "options", void 0);
47
56
  _defineProperty(this, "profileId", "");
48
57
  _defineProperty(this, "profileMeta", {
@@ -50,9 +59,11 @@ export class Sprinkle {
50
59
  createdAt: "",
51
60
  updatedAt: ""
52
61
  });
62
+ _defineProperty(this, "actionRegistry", void 0);
53
63
  this.type = type;
54
64
  this.storagePath = storagePath;
55
65
  this.options = options ?? {};
66
+ this.actionRegistry = new ActionRegistry();
56
67
  }
57
68
 
58
69
  // --- Current Profile Accessor ---
@@ -123,6 +134,7 @@ export class Sprinkle {
123
134
  this.profileMeta = parsed.meta;
124
135
  this.settings = await this.decryptSettings(parsed.settings);
125
136
  this.defaults = parsed.defaults ?? {};
137
+ this.addressbook = parsed.addressbook ?? {};
126
138
  // Update active profile pointer
127
139
  fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), id, "utf-8");
128
140
  }
@@ -142,7 +154,8 @@ export class Sprinkle {
142
154
  const jsonContent = JSON.stringify({
143
155
  meta: this.profileMeta,
144
156
  settings: settingsToSave,
145
- defaults: this.defaults
157
+ defaults: this.defaults,
158
+ addressbook: this.addressbook
146
159
  }, bigIntReplacer, 2);
147
160
  fs.writeFileSync(filePath, jsonContent, "utf-8");
148
161
  }
@@ -219,7 +232,8 @@ export class Sprinkle {
219
232
  updatedAt: now
220
233
  },
221
234
  settings: parsed.settings,
222
- defaults: parsed.defaults ?? {}
235
+ defaults: parsed.defaults ?? {},
236
+ addressbook: parsed.addressbook ?? {}
223
237
  };
224
238
  fs.mkdirSync(profilesDir, {
225
239
  recursive: true
@@ -294,7 +308,8 @@ export class Sprinkle {
294
308
  updatedAt: now
295
309
  },
296
310
  settings: settingsToSave,
297
- defaults: this.defaults
311
+ defaults: this.defaults,
312
+ addressbook: this.addressbook
298
313
  }, bigIntReplacer, 2);
299
314
  fs.writeFileSync(path.join(profilesDir, `${id}.json`), jsonContent, "utf-8");
300
315
  console.log(`Profile "${name}" created as a copy.`);
@@ -388,6 +403,10 @@ export class Sprinkle {
388
403
  value: -1
389
404
  });
390
405
  } else {
406
+ choices.push({
407
+ name: "Utilities",
408
+ value: -6
409
+ });
391
410
  choices.push({
392
411
  name: "Settings & Profiles",
393
412
  value: -5
@@ -411,6 +430,58 @@ export class Sprinkle {
411
430
  return;
412
431
  }
413
432
  const selection = selectionResult;
433
+ if (selection === -6) {
434
+ const utilitiesMenu = {
435
+ title: "Utilities",
436
+ items: [{
437
+ title: "Mint Token",
438
+ action: async () => {
439
+ const result = await promptAndExecute(this, mintToken);
440
+ if (!result.success) {
441
+ if (result.error.code === "USER_CANCELLED") return;
442
+ console.error(`\nError (${result.error.code}): ${result.error.message}\n`);
443
+ return;
444
+ }
445
+ const data = result.data;
446
+ console.log(`\nPolicy: ${data.policyId}\nToken: ${data.tokenName}\nAmount: ${data.amount}\n`);
447
+ const blaze = await Sprinkle.GetBlaze(this.settings.network, this.settings.provider, this.settings.wallet);
448
+ const tx = Core.Transaction.fromCbor(Core.TxCBOR(data.txCbor));
449
+ await this.TxDialog(blaze, tx);
450
+ }
451
+ }, {
452
+ title: "Simple Send",
453
+ action: async () => {
454
+ const result = await promptAndExecute(this, simpleSend);
455
+ if (!result.success) {
456
+ if (result.error.code === "USER_CANCELLED") return;
457
+ console.error(`\nError (${result.error.code}): ${result.error.message}\n`);
458
+ return;
459
+ }
460
+ const data = result.data;
461
+ const blaze = await Sprinkle.GetBlaze(this.settings.network, this.settings.provider, this.settings.wallet);
462
+ const tx = Core.Transaction.fromCbor(Core.TxCBOR(data.txCbor));
463
+ await this.TxDialog(blaze, tx);
464
+ }
465
+ }, {
466
+ title: "Register Stake Script",
467
+ action: async () => {
468
+ const result = await promptAndExecute(this, registerStakeScript);
469
+ if (!result.success) {
470
+ if (result.error.code === "USER_CANCELLED") return;
471
+ console.error(`\nError (${result.error.code}): ${result.error.message}\n`);
472
+ return;
473
+ }
474
+ const data = result.data;
475
+ const blaze = await Sprinkle.GetBlaze(this.settings.network, this.settings.provider, this.settings.wallet);
476
+ const tx = Core.Transaction.fromCbor(Core.TxCBOR(data.txCbor));
477
+ await this.TxDialog(blaze, tx);
478
+ }
479
+ }]
480
+ };
481
+ await this._showMenu(utilitiesMenu, false, [...path, "Utilities"], true);
482
+ await this._showMenu(menu, main, path, true);
483
+ return;
484
+ }
414
485
  if (selection === -5) {
415
486
  const settingsMenu = {
416
487
  title: "Settings & Profiles",
@@ -477,6 +548,113 @@ export class Sprinkle {
477
548
  action: async () => {
478
549
  await this.deleteProfile();
479
550
  }
551
+ }, {
552
+ title: "Addressbook",
553
+ items: [{
554
+ title: "View entries",
555
+ action: async () => {
556
+ const entries = Object.entries(this.addressbook);
557
+ if (entries.length === 0) {
558
+ console.log("Addressbook is empty.");
559
+ } else {
560
+ for (const [name, ms] of entries) {
561
+ const json = JSON.stringify(ms, bigIntReplacer, 2);
562
+ let hashStr = "unknown";
563
+ try {
564
+ hashStr = toNativeScript(ms).hash();
565
+ } catch {/* skip */}
566
+ console.log(colors.bold(name) + " " + colors.dim(hashStr));
567
+ console.log(json);
568
+ console.log();
569
+ }
570
+ }
571
+ await selectWithClear({
572
+ message: "Press Enter to continue...",
573
+ choices: [{
574
+ name: "Continue",
575
+ value: "continue"
576
+ }]
577
+ });
578
+ }
579
+ }, {
580
+ title: "Add entry",
581
+ action: async () => {
582
+ const name = await inputCancellable({
583
+ message: "Entry name:",
584
+ validate: v => v.trim().length > 0 ? true : "Name cannot be empty"
585
+ });
586
+ if (name === null) return;
587
+ if (this.addressbook[name]) {
588
+ const overwrite = await confirmCancellable({
589
+ message: `Entry "${name}" already exists. Overwrite?`,
590
+ default: false
591
+ });
592
+ if (!overwrite) return;
593
+ }
594
+ try {
595
+ const script = await this.FillInStruct(MultisigScriptSchema);
596
+ this.addressbook[name] = script;
597
+ this.saveSettings();
598
+ console.log(`Added "${name}" to addressbook.`);
599
+ } catch (e) {
600
+ if (e instanceof UserCancelledError) return;
601
+ throw e;
602
+ }
603
+ }
604
+ }, {
605
+ title: "Edit entry",
606
+ action: async () => {
607
+ const entries = Object.keys(this.addressbook);
608
+ if (entries.length === 0) {
609
+ console.log("Addressbook is empty.");
610
+ return;
611
+ }
612
+ const selected = await selectCancellable({
613
+ message: "Select entry to edit:",
614
+ choices: entries.map(n => ({
615
+ name: n,
616
+ value: n
617
+ }))
618
+ });
619
+ if (selected === null) return;
620
+ const editName = selected;
621
+ try {
622
+ const updated = await this.EditStruct(MultisigScriptSchema, this.addressbook[editName]);
623
+ this.addressbook[editName] = updated;
624
+ this.saveSettings();
625
+ console.log(`Updated "${editName}".`);
626
+ } catch (e) {
627
+ if (e instanceof UserCancelledError) return;
628
+ throw e;
629
+ }
630
+ }
631
+ }, {
632
+ title: "Delete entry",
633
+ action: async () => {
634
+ const entries = Object.keys(this.addressbook);
635
+ if (entries.length === 0) {
636
+ console.log("Addressbook is empty.");
637
+ return;
638
+ }
639
+ const delSelected = await selectCancellable({
640
+ message: "Select entry to delete:",
641
+ choices: entries.map(n => ({
642
+ name: n,
643
+ value: n
644
+ }))
645
+ });
646
+ if (delSelected === null) return;
647
+ const delName = delSelected;
648
+ const confirmed = await confirmCancellable({
649
+ message: `Delete "${delName}"?`,
650
+ default: false
651
+ });
652
+ if (!confirmed) return;
653
+ delete this.addressbook[delName];
654
+ this.saveSettings();
655
+ console.log(`Deleted "${delName}".`);
656
+ }
657
+ }]
480
658
  }]
481
659
  };
482
660
  await this._showMenu(settingsMenu, false, [...path, "Settings & Profiles"], true);
@@ -570,6 +748,88 @@ export class Sprinkle {
570
748
  getDisplaySettings() {
571
749
  return maskSensitiveFields(this.settings, this.type);
572
750
  }
751
+
752
+ // --- Non-interactive profile management (for CLI/MCP actions) ---
753
+
754
+ /**
755
+ * Look up a profile entry by its ID without loading it.
756
+ * Returns undefined if no profile with the given ID exists.
757
+ */
758
+ getProfileById(id) {
759
+ return this.scanProfiles().find(p => p.id === id);
760
+ }
761
+
762
+ /**
763
+ * Create a new profile file without interactive prompts.
764
+ * Does NOT switch the active profile.
765
+ *
766
+ * @throws ActionError with code DUPLICATE_PROFILE if a profile with the same name already exists.
767
+ * @returns The created IProfileEntry
768
+ */
769
+ async createProfileNonInteractive(name, description, initialSettings) {
770
+ const profiles = this.scanProfiles();
771
+ const nameLower = name.toLowerCase();
772
+ const duplicate = profiles.find(p => p.meta.name.toLowerCase() === nameLower);
773
+ if (duplicate) {
774
+ throw new ActionError(`A profile named "${name}" already exists.`, "DUPLICATE_PROFILE", {
775
+ existingId: duplicate.id
776
+ });
777
+ }
778
+ const profilesDir = Sprinkle.profilesDir(this.storagePath);
779
+ if (!fs.existsSync(profilesDir)) {
780
+ fs.mkdirSync(profilesDir, {
781
+ recursive: true
782
+ });
783
+ }
784
+ const baseId = Sprinkle.sanitizeProfileId(name);
785
+ const id = Sprinkle.findAvailableId(profilesDir, baseId);
786
+ const now = new Date().toISOString();
787
+ const meta = {
788
+ name,
789
+ description,
790
+ createdAt: now,
791
+ updatedAt: now
792
+ };
793
+ const profileData = {
794
+ meta,
795
+ settings: initialSettings ?? {},
796
+ defaults: {},
797
+ addressbook: {}
798
+ };
799
+ fs.writeFileSync(path.join(profilesDir, `${id}.json`), JSON.stringify(profileData, bigIntReplacer, 2), "utf-8");
800
+ return {
801
+ id,
802
+ meta
803
+ };
804
+ }
805
+
806
+ /**
807
+ * Delete a profile file by ID.
808
+ *
809
+ * @throws ActionError with code PROFILE_NOT_FOUND if no profile with the given ID exists.
810
+ * @throws ActionError with code CANNOT_DELETE_ONLY_PROFILE if this is the only profile.
811
+ * @throws ActionError with code CANNOT_DELETE_ACTIVE_PROFILE if this is the currently active profile.
812
+ */
813
+ deleteProfileById(id) {
814
+ const profiles = this.scanProfiles();
815
+ const profile = profiles.find(p => p.id === id);
816
+ if (!profile) {
817
+ throw new ActionError(`Profile "${id}" not found.`, "PROFILE_NOT_FOUND", {
818
+ id
819
+ });
820
+ }
821
+ if (profiles.length === 1) {
822
+ throw new ActionError("Cannot delete the only profile.", "CANNOT_DELETE_ONLY_PROFILE", {
823
+ id
824
+ });
825
+ }
826
+ if (id === this.profileId) {
827
+ throw new ActionError("Cannot delete the active profile. Switch to a different profile first.", "CANNOT_DELETE_ACTIVE_PROFILE", {
828
+ id
829
+ });
830
+ }
831
+ fs.unlinkSync(Sprinkle.profilePath(this.storagePath, id));
832
+ }
573
833
  async TxDialog(blaze, tx, opts) {
574
834
  let currentTx = tx;
575
835
  let expanded = false;
@@ -879,7 +1139,67 @@ export class Sprinkle {
879
1139
  if (!resolvedType) {
880
1140
  throw new Error(`Could not resolve type ${type["$ref"]} at ${path.join(".")}`);
881
1141
  }
882
- return this._fillInStruct(resolvedType, path, defs, def);
1142
+
1143
+ // Addressbook integration for MultisigScript fields
1144
+ if (type["$ref"] === "MultisigScript" && Object.keys(this.addressbook).length > 0) {
1145
+ const sourceChoice = await selectWithClear({
1146
+ message: "Select source for native script:",
1147
+ choices: [{
1148
+ name: "Enter manually",
1149
+ value: "manual"
1150
+ }, {
1151
+ name: "Select from addressbook",
1152
+ value: "addressbook"
1153
+ }]
1154
+ });
1155
+ if (sourceChoice === null) throw new UserCancelledError();
1156
+ if (sourceChoice === "addressbook") {
1157
+ const entries = Object.entries(this.addressbook);
1158
+ const abChoices = entries.map(([name, ms]) => {
1159
+ let hashStr = "";
1160
+ try {
1161
+ hashStr = " " + colors.dim(toNativeScript(ms).hash());
1162
+ } catch {/* skip */}
1163
+ return {
1164
+ name: name + hashStr,
1165
+ value: name
1166
+ };
1167
+ });
1168
+ const selected = await selectWithClear({
1169
+ message: "Select addressbook entry:",
1170
+ choices: abChoices
1171
+ });
1172
+ if (selected === null) throw new UserCancelledError();
1173
+ return this.addressbook[selected];
1174
+ }
1175
+ }
1176
+ const result = await this._fillInStruct(resolvedType, path, defs, def);
1177
+
1178
+ // Offer to save manually entered MultisigScript to addressbook
1179
+ if (type["$ref"] === "MultisigScript" && result !== undefined) {
1180
+ const shouldSave = await selectWithClear({
1181
+ message: "Save this script to the addressbook?",
1182
+ choices: [{
1183
+ name: "No",
1184
+ value: false
1185
+ }, {
1186
+ name: "Yes",
1187
+ value: true
1188
+ }]
1189
+ });
1190
+ if (shouldSave) {
1191
+ const entryName = await inputCancellable({
1192
+ message: "Addressbook entry name:",
1193
+ validate: v => v.trim().length > 0 ? true : "Name cannot be empty"
1194
+ });
1195
+ if (entryName !== null) {
1196
+ this.addressbook[entryName] = result;
1197
+ this.saveSettings();
1198
+ console.log(`Saved "${entryName}" to addressbook.`);
1199
+ }
1200
+ }
1201
+ }
1202
+ return result;
883
1203
  }
884
1204
  if (isOptional(type)) {
885
1205
  const pathDisplay = formatPath(path) || "value";
@@ -1024,6 +1344,24 @@ export class Sprinkle {
1024
1344
  }
1025
1345
  return BigInt(answer);
1026
1346
  }
1347
+ if (isBoolean(type)) {
1348
+ const pathDisplay = formatPath(path) || "value";
1349
+ const answer = await selectWithClear({
1350
+ message: Sprinkle.ExtractMessage(type, `${pathDisplay}?`),
1351
+ choices: [{
1352
+ name: "Yes",
1353
+ value: true
1354
+ }, {
1355
+ name: "No",
1356
+ value: false
1357
+ }],
1358
+ default: def !== undefined ? def : hasDefault(type) ? getDefault(type) : undefined
1359
+ });
1360
+ if (answer === null) {
1361
+ throw new UserCancelledError();
1362
+ }
1363
+ return answer;
1364
+ }
1027
1365
  if (isLiteral(type)) {
1028
1366
  return type.const;
1029
1367
  }
@@ -1112,5 +1450,177 @@ export class Sprinkle {
1112
1450
  }
1113
1451
  return def;
1114
1452
  }
1453
+
1454
+ // --- Action System ---
1455
+
1456
+ /**
1457
+ * Register an action on this Sprinkle instance.
1458
+ * Delegates to the internal ActionRegistry with name and schema validation.
1459
+ */
1460
+ registerAction(action) {
1461
+ this.actionRegistry.register(action);
1462
+ }
1463
+
1464
+ /**
1465
+ * Retrieve a registered action by name.
1466
+ * @returns The action, or undefined if not registered
1467
+ */
1468
+ getAction(name) {
1469
+ return this.actionRegistry.get(name);
1470
+ }
1471
+
1472
+ /**
1473
+ * List all registered actions.
1474
+ */
1475
+ listActions() {
1476
+ return this.actionRegistry.list();
1477
+ }
1478
+
1479
+ /**
1480
+ * List all registered actions grouped by category.
1481
+ * Uncategorized actions are grouped under "default".
1482
+ */
1483
+ listActionsByCategory() {
1484
+ return this.actionRegistry.listByCategory();
1485
+ }
1486
+
1487
+ /**
1488
+ * Execute a registered action by name with raw (unvalidated) input.
1489
+ * Input is validated against the action's inputSchema before execution.
1490
+ *
1491
+ * @throws Error if the action name is not registered
1492
+ */
1493
+ async runAction(name, input) {
1494
+ const action = this.actionRegistry.get(name);
1495
+ if (!action) {
1496
+ throw new Error(`Action "${name}" is not registered. Available actions: ${this.actionRegistry.list().map(a => a.name).join(", ") || "(none)"}`);
1497
+ }
1498
+ const context = {
1499
+ sprinkle: this,
1500
+ settings: this.settings
1501
+ };
1502
+ return executeAction(action, input, context);
1503
+ }
1504
+
1505
+ /**
1506
+ * Non-interactive profile initialization for CLI/MCP modes.
1507
+ * Resolves a profile without any user prompts.
1508
+ *
1509
+ * Resolution order:
1510
+ * 1. If profileName provided, load that profile directly
1511
+ * 2. If exactly one profile exists, auto-select it
1512
+ * 3. If multiple profiles exist and no name given, throw with available names
1513
+ * 4. If no profiles exist, throw instructing user to run in interactive mode
1514
+ */
1515
+ async initNonInteractive(profileName) {
1516
+ await this.migrateIfNeeded();
1517
+ if (profileName) {
1518
+ await this.loadProfile(profileName);
1519
+ return;
1520
+ }
1521
+ const profiles = this.scanProfiles();
1522
+ if (profiles.length === 0) {
1523
+ throw new Error("No profiles found. Run in interactive mode to create one.");
1524
+ }
1525
+ if (profiles.length === 1) {
1526
+ await this.loadProfile(profiles[0].id);
1527
+ return;
1528
+ }
1529
+
1530
+ // Multiple profiles without a --profile flag
1531
+ const names = profiles.map(p => `"${p.id}" (${p.meta.name})`).join(", ");
1532
+ throw new Error(`Multiple profiles found. Specify one with --profile <id>. Available profiles: ${names}`);
1533
+ }
1534
+
1535
+ /**
1536
+ * Static entry point that detects mode and runs accordingly.
1537
+ *
1538
+ * Modes:
1539
+ * - "tui" -- Interactive TUI menu (requires menu option)
1540
+ * - "cli" -- CLI action execution (parses argv for action name and args)
1541
+ * - "mcp" -- MCP protocol mode (not yet implemented)
1542
+ * - "help" -- Print available actions and exit
1543
+ */
1544
+ static async run(opts) {
1545
+ const argv = opts.argv ?? process.argv.slice(2);
1546
+ const mode = detectMode(argv);
1547
+ if (mode === "tui") {
1548
+ if (!opts.menu) {
1549
+ throw new Error("TUI mode requires a menu. Provide a menu option or pass --help to see available actions.");
1550
+ }
1551
+ const sprinkle = await Sprinkle.New(opts.type, opts.storagePath, opts.options);
1552
+ // Register any provided actions
1553
+ for (const action of opts.actions ?? []) {
1554
+ sprinkle.registerAction(action);
1555
+ }
1556
+ await sprinkle.showMenu(opts.menu);
1557
+ return;
1558
+ }
1559
+ if (mode === "help") {
1560
+ const sprinkle = new Sprinkle(opts.type, opts.storagePath, opts.options);
1561
+ for (const action of opts.actions ?? []) {
1562
+ sprinkle.registerAction(action);
1563
+ }
1564
+ console.log(generateAppHelp(sprinkle.listActions()));
1565
+ return;
1566
+ }
1567
+ if (mode === "cli") {
1568
+ const {
1569
+ actionName,
1570
+ args
1571
+ } = parseCliArgs(argv);
1572
+
1573
+ // Extract --profile flag from args if present
1574
+ const profileName = typeof args["profile"] === "string" ? args["profile"] : undefined;
1575
+ const sprinkle = new Sprinkle(opts.type, opts.storagePath, opts.options);
1576
+ for (const action of opts.actions ?? []) {
1577
+ sprinkle.registerAction(action);
1578
+ }
1579
+
1580
+ // Handle action-specific --help BEFORE profile initialization
1581
+ // (help should work without a profile)
1582
+ if (args["help"] === true) {
1583
+ const action = sprinkle.getAction(actionName);
1584
+ if (action) {
1585
+ console.log(generateActionHelp(action));
1586
+ } else {
1587
+ console.log(`Unknown action "${actionName}". Run --help to see available actions.`);
1588
+ }
1589
+ return;
1590
+ }
1591
+ await sprinkle.initNonInteractive(profileName);
1592
+
1593
+ // Remove internal flags from args before passing to action
1594
+ const actionArgs = {
1595
+ ...args
1596
+ };
1597
+ delete actionArgs["profile"];
1598
+ delete actionArgs["help"];
1599
+
1600
+ // Build context and delegate to runCli for proper error routing
1601
+ const context = {
1602
+ sprinkle,
1603
+ settings: sprinkle.settings
1604
+ };
1605
+ await runCli(sprinkle, actionName, actionArgs, context);
1606
+ return;
1607
+ }
1608
+ if (mode === "mcp") {
1609
+ // Derive a server name from the storage path basename, falling back to a
1610
+ // sensible default.
1611
+ const serverName = path.basename(opts.storagePath) || "sprinkle-mcp";
1612
+ const sprinkle = new Sprinkle(opts.type, opts.storagePath, opts.options);
1613
+ for (const action of opts.actions ?? []) {
1614
+ sprinkle.registerAction(action);
1615
+ }
1616
+
1617
+ // Read profile name from environment variable (MCP clients have no
1618
+ // interactive terminal to prompt for profile selection).
1619
+ const profileName = process.env["SPRINKLE_PROFILE"];
1620
+ await sprinkle.initNonInteractive(profileName);
1621
+ await runMcp(sprinkle, serverName);
1622
+ return;
1623
+ }
1624
+ }
1115
1625
  }
1116
1626
  //# sourceMappingURL=index.js.map