@hasna/treasury 0.1.2

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 (110) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +85 -0
  3. package/dist/adapters/fixtures.d.ts +31 -0
  4. package/dist/adapters/fixtures.d.ts.map +1 -0
  5. package/dist/adapters/types.d.ts +42 -0
  6. package/dist/adapters/types.d.ts.map +1 -0
  7. package/dist/api/index.d.ts +37 -0
  8. package/dist/api/index.d.ts.map +1 -0
  9. package/dist/cli/context.d.ts +9 -0
  10. package/dist/cli/context.d.ts.map +1 -0
  11. package/dist/cli/dashboard.d.ts +5 -0
  12. package/dist/cli/dashboard.d.ts.map +1 -0
  13. package/dist/cli/index.d.ts +3 -0
  14. package/dist/cli/index.d.ts.map +1 -0
  15. package/dist/cli/index.js +4353 -0
  16. package/dist/cli/namespaces.d.ts +7 -0
  17. package/dist/cli/namespaces.d.ts.map +1 -0
  18. package/dist/config.d.ts +42 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/core/app-home.d.ts +9 -0
  21. package/dist/core/app-home.d.ts.map +1 -0
  22. package/dist/core/errors.d.ts +8 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/db/audit.d.ts +16 -0
  25. package/dist/db/audit.d.ts.map +1 -0
  26. package/dist/db/backup.d.ts +14 -0
  27. package/dist/db/backup.d.ts.map +1 -0
  28. package/dist/db/database.d.ts +40 -0
  29. package/dist/db/database.d.ts.map +1 -0
  30. package/dist/db/migration-plan.d.ts +14 -0
  31. package/dist/db/migration-plan.d.ts.map +1 -0
  32. package/dist/db/schema.d.ts +8 -0
  33. package/dist/db/schema.d.ts.map +1 -0
  34. package/dist/generated/storage-kit/health.d.ts +20 -0
  35. package/dist/generated/storage-kit/health.d.ts.map +1 -0
  36. package/dist/generated/storage-kit/index.d.ts +8 -0
  37. package/dist/generated/storage-kit/index.d.ts.map +1 -0
  38. package/dist/generated/storage-kit/migrations.d.ts +48 -0
  39. package/dist/generated/storage-kit/migrations.d.ts.map +1 -0
  40. package/dist/generated/storage-kit/mode.d.ts +48 -0
  41. package/dist/generated/storage-kit/mode.d.ts.map +1 -0
  42. package/dist/generated/storage-kit/pool.d.ts +34 -0
  43. package/dist/generated/storage-kit/pool.d.ts.map +1 -0
  44. package/dist/generated/storage-kit/query.d.ts +36 -0
  45. package/dist/generated/storage-kit/query.d.ts.map +1 -0
  46. package/dist/generated/storage-kit/tls.d.ts +26 -0
  47. package/dist/generated/storage-kit/tls.d.ts.map +1 -0
  48. package/dist/index.d.ts +22 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +23773 -0
  51. package/dist/mcp/compact.d.ts +12 -0
  52. package/dist/mcp/compact.d.ts.map +1 -0
  53. package/dist/mcp/http.d.ts +38 -0
  54. package/dist/mcp/http.d.ts.map +1 -0
  55. package/dist/mcp/index.d.ts +32 -0
  56. package/dist/mcp/index.d.ts.map +1 -0
  57. package/dist/mcp/index.js +6243 -0
  58. package/dist/mcp/tools/domain.d.ts +10 -0
  59. package/dist/mcp/tools/domain.d.ts.map +1 -0
  60. package/dist/mcp/tools/standard.d.ts +4 -0
  61. package/dist/mcp/tools/standard.d.ts.map +1 -0
  62. package/dist/mcp/tools/storage.d.ts +4 -0
  63. package/dist/mcp/tools/storage.d.ts.map +1 -0
  64. package/dist/release/package-smoke.d.ts +11 -0
  65. package/dist/release/package-smoke.d.ts.map +1 -0
  66. package/dist/server/app.d.ts +19 -0
  67. package/dist/server/app.d.ts.map +1 -0
  68. package/dist/server/auth-constants.d.ts +22 -0
  69. package/dist/server/auth-constants.d.ts.map +1 -0
  70. package/dist/server/auth.d.ts +147 -0
  71. package/dist/server/auth.d.ts.map +1 -0
  72. package/dist/server/health.d.ts +23 -0
  73. package/dist/server/health.d.ts.map +1 -0
  74. package/dist/server/index.d.ts +11 -0
  75. package/dist/server/index.d.ts.map +1 -0
  76. package/dist/server/index.js +2094 -0
  77. package/dist/server/list-query.d.ts +7 -0
  78. package/dist/server/list-query.d.ts.map +1 -0
  79. package/dist/services/authorization-constants.d.ts +14 -0
  80. package/dist/services/authorization-constants.d.ts.map +1 -0
  81. package/dist/services/authorization.d.ts +95 -0
  82. package/dist/services/authorization.d.ts.map +1 -0
  83. package/dist/services/balances.d.ts +33 -0
  84. package/dist/services/balances.d.ts.map +1 -0
  85. package/dist/services/context.d.ts +24 -0
  86. package/dist/services/context.d.ts.map +1 -0
  87. package/dist/services/entities.d.ts +14 -0
  88. package/dist/services/entities.d.ts.map +1 -0
  89. package/dist/services/forecast.d.ts +10 -0
  90. package/dist/services/forecast.d.ts.map +1 -0
  91. package/dist/services/fx.d.ts +33 -0
  92. package/dist/services/fx.d.ts.map +1 -0
  93. package/dist/services/ingest.d.ts +19 -0
  94. package/dist/services/ingest.d.ts.map +1 -0
  95. package/dist/services/registry.d.ts +33 -0
  96. package/dist/services/registry.d.ts.map +1 -0
  97. package/dist/services/runway.d.ts +28 -0
  98. package/dist/services/runway.d.ts.map +1 -0
  99. package/dist/services/storage.d.ts +35 -0
  100. package/dist/services/storage.d.ts.map +1 -0
  101. package/dist/services/sweeps.d.ts +26 -0
  102. package/dist/services/sweeps.d.ts.map +1 -0
  103. package/dist/types/index.d.ts +133 -0
  104. package/dist/types/index.d.ts.map +1 -0
  105. package/dist/version.d.ts +3 -0
  106. package/dist/version.d.ts.map +1 -0
  107. package/docker-compose.yml +53 -0
  108. package/hasna.contract.json +20 -0
  109. package/openapi.json +615 -0
  110. package/package.json +80 -0
@@ -0,0 +1,4353 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
13
+ var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
21
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
22
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
29
+ if (canCache)
30
+ cache.set(mod, to);
31
+ return to;
32
+ };
33
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
38
+ var __export = (target, all) => {
39
+ for (var name in all)
40
+ __defProp(target, name, {
41
+ get: all[name],
42
+ enumerable: true,
43
+ configurable: true,
44
+ set: __exportSetter.bind(all, name)
45
+ });
46
+ };
47
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
+ var __require = import.meta.require;
49
+
50
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
51
+ var require_error = __commonJS((exports) => {
52
+ class CommanderError extends Error {
53
+ constructor(exitCode, code, message) {
54
+ super(message);
55
+ Error.captureStackTrace(this, this.constructor);
56
+ this.name = this.constructor.name;
57
+ this.code = code;
58
+ this.exitCode = exitCode;
59
+ this.nestedError = undefined;
60
+ }
61
+ }
62
+
63
+ class InvalidArgumentError extends CommanderError {
64
+ constructor(message) {
65
+ super(1, "commander.invalidArgument", message);
66
+ Error.captureStackTrace(this, this.constructor);
67
+ this.name = this.constructor.name;
68
+ }
69
+ }
70
+ exports.CommanderError = CommanderError;
71
+ exports.InvalidArgumentError = InvalidArgumentError;
72
+ });
73
+
74
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/argument.js
75
+ var require_argument = __commonJS((exports) => {
76
+ var { InvalidArgumentError } = require_error();
77
+
78
+ class Argument {
79
+ constructor(name, description) {
80
+ this.description = description || "";
81
+ this.variadic = false;
82
+ this.parseArg = undefined;
83
+ this.defaultValue = undefined;
84
+ this.defaultValueDescription = undefined;
85
+ this.argChoices = undefined;
86
+ switch (name[0]) {
87
+ case "<":
88
+ this.required = true;
89
+ this._name = name.slice(1, -1);
90
+ break;
91
+ case "[":
92
+ this.required = false;
93
+ this._name = name.slice(1, -1);
94
+ break;
95
+ default:
96
+ this.required = true;
97
+ this._name = name;
98
+ break;
99
+ }
100
+ if (this._name.length > 3 && this._name.slice(-3) === "...") {
101
+ this.variadic = true;
102
+ this._name = this._name.slice(0, -3);
103
+ }
104
+ }
105
+ name() {
106
+ return this._name;
107
+ }
108
+ _concatValue(value, previous) {
109
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
110
+ return [value];
111
+ }
112
+ return previous.concat(value);
113
+ }
114
+ default(value, description) {
115
+ this.defaultValue = value;
116
+ this.defaultValueDescription = description;
117
+ return this;
118
+ }
119
+ argParser(fn) {
120
+ this.parseArg = fn;
121
+ return this;
122
+ }
123
+ choices(values) {
124
+ this.argChoices = values.slice();
125
+ this.parseArg = (arg, previous) => {
126
+ if (!this.argChoices.includes(arg)) {
127
+ throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
128
+ }
129
+ if (this.variadic) {
130
+ return this._concatValue(arg, previous);
131
+ }
132
+ return arg;
133
+ };
134
+ return this;
135
+ }
136
+ argRequired() {
137
+ this.required = true;
138
+ return this;
139
+ }
140
+ argOptional() {
141
+ this.required = false;
142
+ return this;
143
+ }
144
+ }
145
+ function humanReadableArgName(arg) {
146
+ const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
147
+ return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
148
+ }
149
+ exports.Argument = Argument;
150
+ exports.humanReadableArgName = humanReadableArgName;
151
+ });
152
+
153
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/help.js
154
+ var require_help = __commonJS((exports) => {
155
+ var { humanReadableArgName } = require_argument();
156
+
157
+ class Help {
158
+ constructor() {
159
+ this.helpWidth = undefined;
160
+ this.minWidthToWrap = 40;
161
+ this.sortSubcommands = false;
162
+ this.sortOptions = false;
163
+ this.showGlobalOptions = false;
164
+ }
165
+ prepareContext(contextOptions) {
166
+ this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
167
+ }
168
+ visibleCommands(cmd) {
169
+ const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden);
170
+ const helpCommand = cmd._getHelpCommand();
171
+ if (helpCommand && !helpCommand._hidden) {
172
+ visibleCommands.push(helpCommand);
173
+ }
174
+ if (this.sortSubcommands) {
175
+ visibleCommands.sort((a, b) => {
176
+ return a.name().localeCompare(b.name());
177
+ });
178
+ }
179
+ return visibleCommands;
180
+ }
181
+ compareOptions(a, b) {
182
+ const getSortKey = (option) => {
183
+ return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, "");
184
+ };
185
+ return getSortKey(a).localeCompare(getSortKey(b));
186
+ }
187
+ visibleOptions(cmd) {
188
+ const visibleOptions = cmd.options.filter((option) => !option.hidden);
189
+ const helpOption = cmd._getHelpOption();
190
+ if (helpOption && !helpOption.hidden) {
191
+ const removeShort = helpOption.short && cmd._findOption(helpOption.short);
192
+ const removeLong = helpOption.long && cmd._findOption(helpOption.long);
193
+ if (!removeShort && !removeLong) {
194
+ visibleOptions.push(helpOption);
195
+ } else if (helpOption.long && !removeLong) {
196
+ visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
197
+ } else if (helpOption.short && !removeShort) {
198
+ visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
199
+ }
200
+ }
201
+ if (this.sortOptions) {
202
+ visibleOptions.sort(this.compareOptions);
203
+ }
204
+ return visibleOptions;
205
+ }
206
+ visibleGlobalOptions(cmd) {
207
+ if (!this.showGlobalOptions)
208
+ return [];
209
+ const globalOptions = [];
210
+ for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
211
+ const visibleOptions = ancestorCmd.options.filter((option) => !option.hidden);
212
+ globalOptions.push(...visibleOptions);
213
+ }
214
+ if (this.sortOptions) {
215
+ globalOptions.sort(this.compareOptions);
216
+ }
217
+ return globalOptions;
218
+ }
219
+ visibleArguments(cmd) {
220
+ if (cmd._argsDescription) {
221
+ cmd.registeredArguments.forEach((argument) => {
222
+ argument.description = argument.description || cmd._argsDescription[argument.name()] || "";
223
+ });
224
+ }
225
+ if (cmd.registeredArguments.find((argument) => argument.description)) {
226
+ return cmd.registeredArguments;
227
+ }
228
+ return [];
229
+ }
230
+ subcommandTerm(cmd) {
231
+ const args = cmd.registeredArguments.map((arg) => humanReadableArgName(arg)).join(" ");
232
+ return cmd._name + (cmd._aliases[0] ? "|" + cmd._aliases[0] : "") + (cmd.options.length ? " [options]" : "") + (args ? " " + args : "");
233
+ }
234
+ optionTerm(option) {
235
+ return option.flags;
236
+ }
237
+ argumentTerm(argument) {
238
+ return argument.name();
239
+ }
240
+ longestSubcommandTermLength(cmd, helper) {
241
+ return helper.visibleCommands(cmd).reduce((max, command) => {
242
+ return Math.max(max, this.displayWidth(helper.styleSubcommandTerm(helper.subcommandTerm(command))));
243
+ }, 0);
244
+ }
245
+ longestOptionTermLength(cmd, helper) {
246
+ return helper.visibleOptions(cmd).reduce((max, option) => {
247
+ return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
248
+ }, 0);
249
+ }
250
+ longestGlobalOptionTermLength(cmd, helper) {
251
+ return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
252
+ return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
253
+ }, 0);
254
+ }
255
+ longestArgumentTermLength(cmd, helper) {
256
+ return helper.visibleArguments(cmd).reduce((max, argument) => {
257
+ return Math.max(max, this.displayWidth(helper.styleArgumentTerm(helper.argumentTerm(argument))));
258
+ }, 0);
259
+ }
260
+ commandUsage(cmd) {
261
+ let cmdName = cmd._name;
262
+ if (cmd._aliases[0]) {
263
+ cmdName = cmdName + "|" + cmd._aliases[0];
264
+ }
265
+ let ancestorCmdNames = "";
266
+ for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
267
+ ancestorCmdNames = ancestorCmd.name() + " " + ancestorCmdNames;
268
+ }
269
+ return ancestorCmdNames + cmdName + " " + cmd.usage();
270
+ }
271
+ commandDescription(cmd) {
272
+ return cmd.description();
273
+ }
274
+ subcommandDescription(cmd) {
275
+ return cmd.summary() || cmd.description();
276
+ }
277
+ optionDescription(option) {
278
+ const extraInfo = [];
279
+ if (option.argChoices) {
280
+ extraInfo.push(`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
281
+ }
282
+ if (option.defaultValue !== undefined) {
283
+ const showDefault = option.required || option.optional || option.isBoolean() && typeof option.defaultValue === "boolean";
284
+ if (showDefault) {
285
+ extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
286
+ }
287
+ }
288
+ if (option.presetArg !== undefined && option.optional) {
289
+ extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
290
+ }
291
+ if (option.envVar !== undefined) {
292
+ extraInfo.push(`env: ${option.envVar}`);
293
+ }
294
+ if (extraInfo.length > 0) {
295
+ return `${option.description} (${extraInfo.join(", ")})`;
296
+ }
297
+ return option.description;
298
+ }
299
+ argumentDescription(argument) {
300
+ const extraInfo = [];
301
+ if (argument.argChoices) {
302
+ extraInfo.push(`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
303
+ }
304
+ if (argument.defaultValue !== undefined) {
305
+ extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
306
+ }
307
+ if (extraInfo.length > 0) {
308
+ const extraDescription = `(${extraInfo.join(", ")})`;
309
+ if (argument.description) {
310
+ return `${argument.description} ${extraDescription}`;
311
+ }
312
+ return extraDescription;
313
+ }
314
+ return argument.description;
315
+ }
316
+ formatHelp(cmd, helper) {
317
+ const termWidth = helper.padWidth(cmd, helper);
318
+ const helpWidth = helper.helpWidth ?? 80;
319
+ function callFormatItem(term, description) {
320
+ return helper.formatItem(term, termWidth, description, helper);
321
+ }
322
+ let output = [
323
+ `${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`,
324
+ ""
325
+ ];
326
+ const commandDescription = helper.commandDescription(cmd);
327
+ if (commandDescription.length > 0) {
328
+ output = output.concat([
329
+ helper.boxWrap(helper.styleCommandDescription(commandDescription), helpWidth),
330
+ ""
331
+ ]);
332
+ }
333
+ const argumentList = helper.visibleArguments(cmd).map((argument) => {
334
+ return callFormatItem(helper.styleArgumentTerm(helper.argumentTerm(argument)), helper.styleArgumentDescription(helper.argumentDescription(argument)));
335
+ });
336
+ if (argumentList.length > 0) {
337
+ output = output.concat([
338
+ helper.styleTitle("Arguments:"),
339
+ ...argumentList,
340
+ ""
341
+ ]);
342
+ }
343
+ const optionList = helper.visibleOptions(cmd).map((option) => {
344
+ return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
345
+ });
346
+ if (optionList.length > 0) {
347
+ output = output.concat([
348
+ helper.styleTitle("Options:"),
349
+ ...optionList,
350
+ ""
351
+ ]);
352
+ }
353
+ if (helper.showGlobalOptions) {
354
+ const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
355
+ return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
356
+ });
357
+ if (globalOptionList.length > 0) {
358
+ output = output.concat([
359
+ helper.styleTitle("Global Options:"),
360
+ ...globalOptionList,
361
+ ""
362
+ ]);
363
+ }
364
+ }
365
+ const commandList = helper.visibleCommands(cmd).map((cmd2) => {
366
+ return callFormatItem(helper.styleSubcommandTerm(helper.subcommandTerm(cmd2)), helper.styleSubcommandDescription(helper.subcommandDescription(cmd2)));
367
+ });
368
+ if (commandList.length > 0) {
369
+ output = output.concat([
370
+ helper.styleTitle("Commands:"),
371
+ ...commandList,
372
+ ""
373
+ ]);
374
+ }
375
+ return output.join(`
376
+ `);
377
+ }
378
+ displayWidth(str) {
379
+ return stripColor(str).length;
380
+ }
381
+ styleTitle(str) {
382
+ return str;
383
+ }
384
+ styleUsage(str) {
385
+ return str.split(" ").map((word) => {
386
+ if (word === "[options]")
387
+ return this.styleOptionText(word);
388
+ if (word === "[command]")
389
+ return this.styleSubcommandText(word);
390
+ if (word[0] === "[" || word[0] === "<")
391
+ return this.styleArgumentText(word);
392
+ return this.styleCommandText(word);
393
+ }).join(" ");
394
+ }
395
+ styleCommandDescription(str) {
396
+ return this.styleDescriptionText(str);
397
+ }
398
+ styleOptionDescription(str) {
399
+ return this.styleDescriptionText(str);
400
+ }
401
+ styleSubcommandDescription(str) {
402
+ return this.styleDescriptionText(str);
403
+ }
404
+ styleArgumentDescription(str) {
405
+ return this.styleDescriptionText(str);
406
+ }
407
+ styleDescriptionText(str) {
408
+ return str;
409
+ }
410
+ styleOptionTerm(str) {
411
+ return this.styleOptionText(str);
412
+ }
413
+ styleSubcommandTerm(str) {
414
+ return str.split(" ").map((word) => {
415
+ if (word === "[options]")
416
+ return this.styleOptionText(word);
417
+ if (word[0] === "[" || word[0] === "<")
418
+ return this.styleArgumentText(word);
419
+ return this.styleSubcommandText(word);
420
+ }).join(" ");
421
+ }
422
+ styleArgumentTerm(str) {
423
+ return this.styleArgumentText(str);
424
+ }
425
+ styleOptionText(str) {
426
+ return str;
427
+ }
428
+ styleArgumentText(str) {
429
+ return str;
430
+ }
431
+ styleSubcommandText(str) {
432
+ return str;
433
+ }
434
+ styleCommandText(str) {
435
+ return str;
436
+ }
437
+ padWidth(cmd, helper) {
438
+ return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
439
+ }
440
+ preformatted(str) {
441
+ return /\n[^\S\r\n]/.test(str);
442
+ }
443
+ formatItem(term, termWidth, description, helper) {
444
+ const itemIndent = 2;
445
+ const itemIndentStr = " ".repeat(itemIndent);
446
+ if (!description)
447
+ return itemIndentStr + term;
448
+ const paddedTerm = term.padEnd(termWidth + term.length - helper.displayWidth(term));
449
+ const spacerWidth = 2;
450
+ const helpWidth = this.helpWidth ?? 80;
451
+ const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
452
+ let formattedDescription;
453
+ if (remainingWidth < this.minWidthToWrap || helper.preformatted(description)) {
454
+ formattedDescription = description;
455
+ } else {
456
+ const wrappedDescription = helper.boxWrap(description, remainingWidth);
457
+ formattedDescription = wrappedDescription.replace(/\n/g, `
458
+ ` + " ".repeat(termWidth + spacerWidth));
459
+ }
460
+ return itemIndentStr + paddedTerm + " ".repeat(spacerWidth) + formattedDescription.replace(/\n/g, `
461
+ ${itemIndentStr}`);
462
+ }
463
+ boxWrap(str, width) {
464
+ if (width < this.minWidthToWrap)
465
+ return str;
466
+ const rawLines = str.split(/\r\n|\n/);
467
+ const chunkPattern = /[\s]*[^\s]+/g;
468
+ const wrappedLines = [];
469
+ rawLines.forEach((line) => {
470
+ const chunks = line.match(chunkPattern);
471
+ if (chunks === null) {
472
+ wrappedLines.push("");
473
+ return;
474
+ }
475
+ let sumChunks = [chunks.shift()];
476
+ let sumWidth = this.displayWidth(sumChunks[0]);
477
+ chunks.forEach((chunk) => {
478
+ const visibleWidth = this.displayWidth(chunk);
479
+ if (sumWidth + visibleWidth <= width) {
480
+ sumChunks.push(chunk);
481
+ sumWidth += visibleWidth;
482
+ return;
483
+ }
484
+ wrappedLines.push(sumChunks.join(""));
485
+ const nextChunk = chunk.trimStart();
486
+ sumChunks = [nextChunk];
487
+ sumWidth = this.displayWidth(nextChunk);
488
+ });
489
+ wrappedLines.push(sumChunks.join(""));
490
+ });
491
+ return wrappedLines.join(`
492
+ `);
493
+ }
494
+ }
495
+ function stripColor(str) {
496
+ const sgrPattern = /\x1b\[\d*(;\d*)*m/g;
497
+ return str.replace(sgrPattern, "");
498
+ }
499
+ exports.Help = Help;
500
+ exports.stripColor = stripColor;
501
+ });
502
+
503
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/option.js
504
+ var require_option = __commonJS((exports) => {
505
+ var { InvalidArgumentError } = require_error();
506
+
507
+ class Option {
508
+ constructor(flags, description) {
509
+ this.flags = flags;
510
+ this.description = description || "";
511
+ this.required = flags.includes("<");
512
+ this.optional = flags.includes("[");
513
+ this.variadic = /\w\.\.\.[>\]]$/.test(flags);
514
+ this.mandatory = false;
515
+ const optionFlags = splitOptionFlags(flags);
516
+ this.short = optionFlags.shortFlag;
517
+ this.long = optionFlags.longFlag;
518
+ this.negate = false;
519
+ if (this.long) {
520
+ this.negate = this.long.startsWith("--no-");
521
+ }
522
+ this.defaultValue = undefined;
523
+ this.defaultValueDescription = undefined;
524
+ this.presetArg = undefined;
525
+ this.envVar = undefined;
526
+ this.parseArg = undefined;
527
+ this.hidden = false;
528
+ this.argChoices = undefined;
529
+ this.conflictsWith = [];
530
+ this.implied = undefined;
531
+ }
532
+ default(value, description) {
533
+ this.defaultValue = value;
534
+ this.defaultValueDescription = description;
535
+ return this;
536
+ }
537
+ preset(arg) {
538
+ this.presetArg = arg;
539
+ return this;
540
+ }
541
+ conflicts(names) {
542
+ this.conflictsWith = this.conflictsWith.concat(names);
543
+ return this;
544
+ }
545
+ implies(impliedOptionValues) {
546
+ let newImplied = impliedOptionValues;
547
+ if (typeof impliedOptionValues === "string") {
548
+ newImplied = { [impliedOptionValues]: true };
549
+ }
550
+ this.implied = Object.assign(this.implied || {}, newImplied);
551
+ return this;
552
+ }
553
+ env(name) {
554
+ this.envVar = name;
555
+ return this;
556
+ }
557
+ argParser(fn) {
558
+ this.parseArg = fn;
559
+ return this;
560
+ }
561
+ makeOptionMandatory(mandatory = true) {
562
+ this.mandatory = !!mandatory;
563
+ return this;
564
+ }
565
+ hideHelp(hide = true) {
566
+ this.hidden = !!hide;
567
+ return this;
568
+ }
569
+ _concatValue(value, previous) {
570
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
571
+ return [value];
572
+ }
573
+ return previous.concat(value);
574
+ }
575
+ choices(values) {
576
+ this.argChoices = values.slice();
577
+ this.parseArg = (arg, previous) => {
578
+ if (!this.argChoices.includes(arg)) {
579
+ throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
580
+ }
581
+ if (this.variadic) {
582
+ return this._concatValue(arg, previous);
583
+ }
584
+ return arg;
585
+ };
586
+ return this;
587
+ }
588
+ name() {
589
+ if (this.long) {
590
+ return this.long.replace(/^--/, "");
591
+ }
592
+ return this.short.replace(/^-/, "");
593
+ }
594
+ attributeName() {
595
+ if (this.negate) {
596
+ return camelcase(this.name().replace(/^no-/, ""));
597
+ }
598
+ return camelcase(this.name());
599
+ }
600
+ is(arg) {
601
+ return this.short === arg || this.long === arg;
602
+ }
603
+ isBoolean() {
604
+ return !this.required && !this.optional && !this.negate;
605
+ }
606
+ }
607
+
608
+ class DualOptions {
609
+ constructor(options) {
610
+ this.positiveOptions = new Map;
611
+ this.negativeOptions = new Map;
612
+ this.dualOptions = new Set;
613
+ options.forEach((option) => {
614
+ if (option.negate) {
615
+ this.negativeOptions.set(option.attributeName(), option);
616
+ } else {
617
+ this.positiveOptions.set(option.attributeName(), option);
618
+ }
619
+ });
620
+ this.negativeOptions.forEach((value, key) => {
621
+ if (this.positiveOptions.has(key)) {
622
+ this.dualOptions.add(key);
623
+ }
624
+ });
625
+ }
626
+ valueFromOption(value, option) {
627
+ const optionKey = option.attributeName();
628
+ if (!this.dualOptions.has(optionKey))
629
+ return true;
630
+ const preset = this.negativeOptions.get(optionKey).presetArg;
631
+ const negativeValue = preset !== undefined ? preset : false;
632
+ return option.negate === (negativeValue === value);
633
+ }
634
+ }
635
+ function camelcase(str) {
636
+ return str.split("-").reduce((str2, word) => {
637
+ return str2 + word[0].toUpperCase() + word.slice(1);
638
+ });
639
+ }
640
+ function splitOptionFlags(flags) {
641
+ let shortFlag;
642
+ let longFlag;
643
+ const shortFlagExp = /^-[^-]$/;
644
+ const longFlagExp = /^--[^-]/;
645
+ const flagParts = flags.split(/[ |,]+/).concat("guard");
646
+ if (shortFlagExp.test(flagParts[0]))
647
+ shortFlag = flagParts.shift();
648
+ if (longFlagExp.test(flagParts[0]))
649
+ longFlag = flagParts.shift();
650
+ if (!shortFlag && shortFlagExp.test(flagParts[0]))
651
+ shortFlag = flagParts.shift();
652
+ if (!shortFlag && longFlagExp.test(flagParts[0])) {
653
+ shortFlag = longFlag;
654
+ longFlag = flagParts.shift();
655
+ }
656
+ if (flagParts[0].startsWith("-")) {
657
+ const unsupportedFlag = flagParts[0];
658
+ const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
659
+ if (/^-[^-][^-]/.test(unsupportedFlag))
660
+ throw new Error(`${baseError}
661
+ - a short flag is a single dash and a single character
662
+ - either use a single dash and a single character (for a short flag)
663
+ - or use a double dash for a long option (and can have two, like '--ws, --workspace')`);
664
+ if (shortFlagExp.test(unsupportedFlag))
665
+ throw new Error(`${baseError}
666
+ - too many short flags`);
667
+ if (longFlagExp.test(unsupportedFlag))
668
+ throw new Error(`${baseError}
669
+ - too many long flags`);
670
+ throw new Error(`${baseError}
671
+ - unrecognised flag format`);
672
+ }
673
+ if (shortFlag === undefined && longFlag === undefined)
674
+ throw new Error(`option creation failed due to no flags found in '${flags}'.`);
675
+ return { shortFlag, longFlag };
676
+ }
677
+ exports.Option = Option;
678
+ exports.DualOptions = DualOptions;
679
+ });
680
+
681
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
682
+ var require_suggestSimilar = __commonJS((exports) => {
683
+ var maxDistance = 3;
684
+ function editDistance(a, b) {
685
+ if (Math.abs(a.length - b.length) > maxDistance)
686
+ return Math.max(a.length, b.length);
687
+ const d = [];
688
+ for (let i = 0;i <= a.length; i++) {
689
+ d[i] = [i];
690
+ }
691
+ for (let j = 0;j <= b.length; j++) {
692
+ d[0][j] = j;
693
+ }
694
+ for (let j = 1;j <= b.length; j++) {
695
+ for (let i = 1;i <= a.length; i++) {
696
+ let cost = 1;
697
+ if (a[i - 1] === b[j - 1]) {
698
+ cost = 0;
699
+ } else {
700
+ cost = 1;
701
+ }
702
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
703
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
704
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
705
+ }
706
+ }
707
+ }
708
+ return d[a.length][b.length];
709
+ }
710
+ function suggestSimilar(word, candidates) {
711
+ if (!candidates || candidates.length === 0)
712
+ return "";
713
+ candidates = Array.from(new Set(candidates));
714
+ const searchingOptions = word.startsWith("--");
715
+ if (searchingOptions) {
716
+ word = word.slice(2);
717
+ candidates = candidates.map((candidate) => candidate.slice(2));
718
+ }
719
+ let similar = [];
720
+ let bestDistance = maxDistance;
721
+ const minSimilarity = 0.4;
722
+ candidates.forEach((candidate) => {
723
+ if (candidate.length <= 1)
724
+ return;
725
+ const distance = editDistance(word, candidate);
726
+ const length = Math.max(word.length, candidate.length);
727
+ const similarity = (length - distance) / length;
728
+ if (similarity > minSimilarity) {
729
+ if (distance < bestDistance) {
730
+ bestDistance = distance;
731
+ similar = [candidate];
732
+ } else if (distance === bestDistance) {
733
+ similar.push(candidate);
734
+ }
735
+ }
736
+ });
737
+ similar.sort((a, b) => a.localeCompare(b));
738
+ if (searchingOptions) {
739
+ similar = similar.map((candidate) => `--${candidate}`);
740
+ }
741
+ if (similar.length > 1) {
742
+ return `
743
+ (Did you mean one of ${similar.join(", ")}?)`;
744
+ }
745
+ if (similar.length === 1) {
746
+ return `
747
+ (Did you mean ${similar[0]}?)`;
748
+ }
749
+ return "";
750
+ }
751
+ exports.suggestSimilar = suggestSimilar;
752
+ });
753
+
754
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/command.js
755
+ var require_command = __commonJS((exports) => {
756
+ var EventEmitter = __require("events").EventEmitter;
757
+ var childProcess = __require("child_process");
758
+ var path = __require("path");
759
+ var fs = __require("fs");
760
+ var process2 = __require("process");
761
+ var { Argument, humanReadableArgName } = require_argument();
762
+ var { CommanderError } = require_error();
763
+ var { Help, stripColor } = require_help();
764
+ var { Option, DualOptions } = require_option();
765
+ var { suggestSimilar } = require_suggestSimilar();
766
+
767
+ class Command extends EventEmitter {
768
+ constructor(name) {
769
+ super();
770
+ this.commands = [];
771
+ this.options = [];
772
+ this.parent = null;
773
+ this._allowUnknownOption = false;
774
+ this._allowExcessArguments = false;
775
+ this.registeredArguments = [];
776
+ this._args = this.registeredArguments;
777
+ this.args = [];
778
+ this.rawArgs = [];
779
+ this.processedArgs = [];
780
+ this._scriptPath = null;
781
+ this._name = name || "";
782
+ this._optionValues = {};
783
+ this._optionValueSources = {};
784
+ this._storeOptionsAsProperties = false;
785
+ this._actionHandler = null;
786
+ this._executableHandler = false;
787
+ this._executableFile = null;
788
+ this._executableDir = null;
789
+ this._defaultCommandName = null;
790
+ this._exitCallback = null;
791
+ this._aliases = [];
792
+ this._combineFlagAndOptionalValue = true;
793
+ this._description = "";
794
+ this._summary = "";
795
+ this._argsDescription = undefined;
796
+ this._enablePositionalOptions = false;
797
+ this._passThroughOptions = false;
798
+ this._lifeCycleHooks = {};
799
+ this._showHelpAfterError = false;
800
+ this._showSuggestionAfterError = true;
801
+ this._savedState = null;
802
+ this._outputConfiguration = {
803
+ writeOut: (str) => process2.stdout.write(str),
804
+ writeErr: (str) => process2.stderr.write(str),
805
+ outputError: (str, write) => write(str),
806
+ getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : undefined,
807
+ getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : undefined,
808
+ getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
809
+ getErrHasColors: () => useColor() ?? (process2.stderr.isTTY && process2.stderr.hasColors?.()),
810
+ stripColor: (str) => stripColor(str)
811
+ };
812
+ this._hidden = false;
813
+ this._helpOption = undefined;
814
+ this._addImplicitHelpCommand = undefined;
815
+ this._helpCommand = undefined;
816
+ this._helpConfiguration = {};
817
+ }
818
+ copyInheritedSettings(sourceCommand) {
819
+ this._outputConfiguration = sourceCommand._outputConfiguration;
820
+ this._helpOption = sourceCommand._helpOption;
821
+ this._helpCommand = sourceCommand._helpCommand;
822
+ this._helpConfiguration = sourceCommand._helpConfiguration;
823
+ this._exitCallback = sourceCommand._exitCallback;
824
+ this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
825
+ this._combineFlagAndOptionalValue = sourceCommand._combineFlagAndOptionalValue;
826
+ this._allowExcessArguments = sourceCommand._allowExcessArguments;
827
+ this._enablePositionalOptions = sourceCommand._enablePositionalOptions;
828
+ this._showHelpAfterError = sourceCommand._showHelpAfterError;
829
+ this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;
830
+ return this;
831
+ }
832
+ _getCommandAndAncestors() {
833
+ const result = [];
834
+ for (let command = this;command; command = command.parent) {
835
+ result.push(command);
836
+ }
837
+ return result;
838
+ }
839
+ command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
840
+ let desc = actionOptsOrExecDesc;
841
+ let opts = execOpts;
842
+ if (typeof desc === "object" && desc !== null) {
843
+ opts = desc;
844
+ desc = null;
845
+ }
846
+ opts = opts || {};
847
+ const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
848
+ const cmd = this.createCommand(name);
849
+ if (desc) {
850
+ cmd.description(desc);
851
+ cmd._executableHandler = true;
852
+ }
853
+ if (opts.isDefault)
854
+ this._defaultCommandName = cmd._name;
855
+ cmd._hidden = !!(opts.noHelp || opts.hidden);
856
+ cmd._executableFile = opts.executableFile || null;
857
+ if (args)
858
+ cmd.arguments(args);
859
+ this._registerCommand(cmd);
860
+ cmd.parent = this;
861
+ cmd.copyInheritedSettings(this);
862
+ if (desc)
863
+ return this;
864
+ return cmd;
865
+ }
866
+ createCommand(name) {
867
+ return new Command(name);
868
+ }
869
+ createHelp() {
870
+ return Object.assign(new Help, this.configureHelp());
871
+ }
872
+ configureHelp(configuration) {
873
+ if (configuration === undefined)
874
+ return this._helpConfiguration;
875
+ this._helpConfiguration = configuration;
876
+ return this;
877
+ }
878
+ configureOutput(configuration) {
879
+ if (configuration === undefined)
880
+ return this._outputConfiguration;
881
+ Object.assign(this._outputConfiguration, configuration);
882
+ return this;
883
+ }
884
+ showHelpAfterError(displayHelp = true) {
885
+ if (typeof displayHelp !== "string")
886
+ displayHelp = !!displayHelp;
887
+ this._showHelpAfterError = displayHelp;
888
+ return this;
889
+ }
890
+ showSuggestionAfterError(displaySuggestion = true) {
891
+ this._showSuggestionAfterError = !!displaySuggestion;
892
+ return this;
893
+ }
894
+ addCommand(cmd, opts) {
895
+ if (!cmd._name) {
896
+ throw new Error(`Command passed to .addCommand() must have a name
897
+ - specify the name in Command constructor or using .name()`);
898
+ }
899
+ opts = opts || {};
900
+ if (opts.isDefault)
901
+ this._defaultCommandName = cmd._name;
902
+ if (opts.noHelp || opts.hidden)
903
+ cmd._hidden = true;
904
+ this._registerCommand(cmd);
905
+ cmd.parent = this;
906
+ cmd._checkForBrokenPassThrough();
907
+ return this;
908
+ }
909
+ createArgument(name, description) {
910
+ return new Argument(name, description);
911
+ }
912
+ argument(name, description, fn, defaultValue) {
913
+ const argument = this.createArgument(name, description);
914
+ if (typeof fn === "function") {
915
+ argument.default(defaultValue).argParser(fn);
916
+ } else {
917
+ argument.default(fn);
918
+ }
919
+ this.addArgument(argument);
920
+ return this;
921
+ }
922
+ arguments(names) {
923
+ names.trim().split(/ +/).forEach((detail) => {
924
+ this.argument(detail);
925
+ });
926
+ return this;
927
+ }
928
+ addArgument(argument) {
929
+ const previousArgument = this.registeredArguments.slice(-1)[0];
930
+ if (previousArgument && previousArgument.variadic) {
931
+ throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`);
932
+ }
933
+ if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) {
934
+ throw new Error(`a default value for a required argument is never used: '${argument.name()}'`);
935
+ }
936
+ this.registeredArguments.push(argument);
937
+ return this;
938
+ }
939
+ helpCommand(enableOrNameAndArgs, description) {
940
+ if (typeof enableOrNameAndArgs === "boolean") {
941
+ this._addImplicitHelpCommand = enableOrNameAndArgs;
942
+ return this;
943
+ }
944
+ enableOrNameAndArgs = enableOrNameAndArgs ?? "help [command]";
945
+ const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
946
+ const helpDescription = description ?? "display help for command";
947
+ const helpCommand = this.createCommand(helpName);
948
+ helpCommand.helpOption(false);
949
+ if (helpArgs)
950
+ helpCommand.arguments(helpArgs);
951
+ if (helpDescription)
952
+ helpCommand.description(helpDescription);
953
+ this._addImplicitHelpCommand = true;
954
+ this._helpCommand = helpCommand;
955
+ return this;
956
+ }
957
+ addHelpCommand(helpCommand, deprecatedDescription) {
958
+ if (typeof helpCommand !== "object") {
959
+ this.helpCommand(helpCommand, deprecatedDescription);
960
+ return this;
961
+ }
962
+ this._addImplicitHelpCommand = true;
963
+ this._helpCommand = helpCommand;
964
+ return this;
965
+ }
966
+ _getHelpCommand() {
967
+ const hasImplicitHelpCommand = this._addImplicitHelpCommand ?? (this.commands.length && !this._actionHandler && !this._findCommand("help"));
968
+ if (hasImplicitHelpCommand) {
969
+ if (this._helpCommand === undefined) {
970
+ this.helpCommand(undefined, undefined);
971
+ }
972
+ return this._helpCommand;
973
+ }
974
+ return null;
975
+ }
976
+ hook(event, listener) {
977
+ const allowedValues = ["preSubcommand", "preAction", "postAction"];
978
+ if (!allowedValues.includes(event)) {
979
+ throw new Error(`Unexpected value for event passed to hook : '${event}'.
980
+ Expecting one of '${allowedValues.join("', '")}'`);
981
+ }
982
+ if (this._lifeCycleHooks[event]) {
983
+ this._lifeCycleHooks[event].push(listener);
984
+ } else {
985
+ this._lifeCycleHooks[event] = [listener];
986
+ }
987
+ return this;
988
+ }
989
+ exitOverride(fn) {
990
+ if (fn) {
991
+ this._exitCallback = fn;
992
+ } else {
993
+ this._exitCallback = (err) => {
994
+ if (err.code !== "commander.executeSubCommandAsync") {
995
+ throw err;
996
+ }
997
+ };
998
+ }
999
+ return this;
1000
+ }
1001
+ _exit(exitCode, code, message) {
1002
+ if (this._exitCallback) {
1003
+ this._exitCallback(new CommanderError(exitCode, code, message));
1004
+ }
1005
+ process2.exit(exitCode);
1006
+ }
1007
+ action(fn) {
1008
+ const listener = (args) => {
1009
+ const expectedArgsCount = this.registeredArguments.length;
1010
+ const actionArgs = args.slice(0, expectedArgsCount);
1011
+ if (this._storeOptionsAsProperties) {
1012
+ actionArgs[expectedArgsCount] = this;
1013
+ } else {
1014
+ actionArgs[expectedArgsCount] = this.opts();
1015
+ }
1016
+ actionArgs.push(this);
1017
+ return fn.apply(this, actionArgs);
1018
+ };
1019
+ this._actionHandler = listener;
1020
+ return this;
1021
+ }
1022
+ createOption(flags, description) {
1023
+ return new Option(flags, description);
1024
+ }
1025
+ _callParseArg(target, value, previous, invalidArgumentMessage) {
1026
+ try {
1027
+ return target.parseArg(value, previous);
1028
+ } catch (err) {
1029
+ if (err.code === "commander.invalidArgument") {
1030
+ const message = `${invalidArgumentMessage} ${err.message}`;
1031
+ this.error(message, { exitCode: err.exitCode, code: err.code });
1032
+ }
1033
+ throw err;
1034
+ }
1035
+ }
1036
+ _registerOption(option) {
1037
+ const matchingOption = option.short && this._findOption(option.short) || option.long && this._findOption(option.long);
1038
+ if (matchingOption) {
1039
+ const matchingFlag = option.long && this._findOption(option.long) ? option.long : option.short;
1040
+ throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
1041
+ - already used by option '${matchingOption.flags}'`);
1042
+ }
1043
+ this.options.push(option);
1044
+ }
1045
+ _registerCommand(command) {
1046
+ const knownBy = (cmd) => {
1047
+ return [cmd.name()].concat(cmd.aliases());
1048
+ };
1049
+ const alreadyUsed = knownBy(command).find((name) => this._findCommand(name));
1050
+ if (alreadyUsed) {
1051
+ const existingCmd = knownBy(this._findCommand(alreadyUsed)).join("|");
1052
+ const newCmd = knownBy(command).join("|");
1053
+ throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
1054
+ }
1055
+ this.commands.push(command);
1056
+ }
1057
+ addOption(option) {
1058
+ this._registerOption(option);
1059
+ const oname = option.name();
1060
+ const name = option.attributeName();
1061
+ if (option.negate) {
1062
+ const positiveLongFlag = option.long.replace(/^--no-/, "--");
1063
+ if (!this._findOption(positiveLongFlag)) {
1064
+ this.setOptionValueWithSource(name, option.defaultValue === undefined ? true : option.defaultValue, "default");
1065
+ }
1066
+ } else if (option.defaultValue !== undefined) {
1067
+ this.setOptionValueWithSource(name, option.defaultValue, "default");
1068
+ }
1069
+ const handleOptionValue = (val, invalidValueMessage, valueSource) => {
1070
+ if (val == null && option.presetArg !== undefined) {
1071
+ val = option.presetArg;
1072
+ }
1073
+ const oldValue = this.getOptionValue(name);
1074
+ if (val !== null && option.parseArg) {
1075
+ val = this._callParseArg(option, val, oldValue, invalidValueMessage);
1076
+ } else if (val !== null && option.variadic) {
1077
+ val = option._concatValue(val, oldValue);
1078
+ }
1079
+ if (val == null) {
1080
+ if (option.negate) {
1081
+ val = false;
1082
+ } else if (option.isBoolean() || option.optional) {
1083
+ val = true;
1084
+ } else {
1085
+ val = "";
1086
+ }
1087
+ }
1088
+ this.setOptionValueWithSource(name, val, valueSource);
1089
+ };
1090
+ this.on("option:" + oname, (val) => {
1091
+ const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
1092
+ handleOptionValue(val, invalidValueMessage, "cli");
1093
+ });
1094
+ if (option.envVar) {
1095
+ this.on("optionEnv:" + oname, (val) => {
1096
+ const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
1097
+ handleOptionValue(val, invalidValueMessage, "env");
1098
+ });
1099
+ }
1100
+ return this;
1101
+ }
1102
+ _optionEx(config, flags, description, fn, defaultValue) {
1103
+ if (typeof flags === "object" && flags instanceof Option) {
1104
+ throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");
1105
+ }
1106
+ const option = this.createOption(flags, description);
1107
+ option.makeOptionMandatory(!!config.mandatory);
1108
+ if (typeof fn === "function") {
1109
+ option.default(defaultValue).argParser(fn);
1110
+ } else if (fn instanceof RegExp) {
1111
+ const regex = fn;
1112
+ fn = (val, def) => {
1113
+ const m = regex.exec(val);
1114
+ return m ? m[0] : def;
1115
+ };
1116
+ option.default(defaultValue).argParser(fn);
1117
+ } else {
1118
+ option.default(fn);
1119
+ }
1120
+ return this.addOption(option);
1121
+ }
1122
+ option(flags, description, parseArg, defaultValue) {
1123
+ return this._optionEx({}, flags, description, parseArg, defaultValue);
1124
+ }
1125
+ requiredOption(flags, description, parseArg, defaultValue) {
1126
+ return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
1127
+ }
1128
+ combineFlagAndOptionalValue(combine = true) {
1129
+ this._combineFlagAndOptionalValue = !!combine;
1130
+ return this;
1131
+ }
1132
+ allowUnknownOption(allowUnknown = true) {
1133
+ this._allowUnknownOption = !!allowUnknown;
1134
+ return this;
1135
+ }
1136
+ allowExcessArguments(allowExcess = true) {
1137
+ this._allowExcessArguments = !!allowExcess;
1138
+ return this;
1139
+ }
1140
+ enablePositionalOptions(positional = true) {
1141
+ this._enablePositionalOptions = !!positional;
1142
+ return this;
1143
+ }
1144
+ passThroughOptions(passThrough = true) {
1145
+ this._passThroughOptions = !!passThrough;
1146
+ this._checkForBrokenPassThrough();
1147
+ return this;
1148
+ }
1149
+ _checkForBrokenPassThrough() {
1150
+ if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) {
1151
+ throw new Error(`passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`);
1152
+ }
1153
+ }
1154
+ storeOptionsAsProperties(storeAsProperties = true) {
1155
+ if (this.options.length) {
1156
+ throw new Error("call .storeOptionsAsProperties() before adding options");
1157
+ }
1158
+ if (Object.keys(this._optionValues).length) {
1159
+ throw new Error("call .storeOptionsAsProperties() before setting option values");
1160
+ }
1161
+ this._storeOptionsAsProperties = !!storeAsProperties;
1162
+ return this;
1163
+ }
1164
+ getOptionValue(key) {
1165
+ if (this._storeOptionsAsProperties) {
1166
+ return this[key];
1167
+ }
1168
+ return this._optionValues[key];
1169
+ }
1170
+ setOptionValue(key, value) {
1171
+ return this.setOptionValueWithSource(key, value, undefined);
1172
+ }
1173
+ setOptionValueWithSource(key, value, source) {
1174
+ if (this._storeOptionsAsProperties) {
1175
+ this[key] = value;
1176
+ } else {
1177
+ this._optionValues[key] = value;
1178
+ }
1179
+ this._optionValueSources[key] = source;
1180
+ return this;
1181
+ }
1182
+ getOptionValueSource(key) {
1183
+ return this._optionValueSources[key];
1184
+ }
1185
+ getOptionValueSourceWithGlobals(key) {
1186
+ let source;
1187
+ this._getCommandAndAncestors().forEach((cmd) => {
1188
+ if (cmd.getOptionValueSource(key) !== undefined) {
1189
+ source = cmd.getOptionValueSource(key);
1190
+ }
1191
+ });
1192
+ return source;
1193
+ }
1194
+ _prepareUserArgs(argv, parseOptions) {
1195
+ if (argv !== undefined && !Array.isArray(argv)) {
1196
+ throw new Error("first parameter to parse must be array or undefined");
1197
+ }
1198
+ parseOptions = parseOptions || {};
1199
+ if (argv === undefined && parseOptions.from === undefined) {
1200
+ if (process2.versions?.electron) {
1201
+ parseOptions.from = "electron";
1202
+ }
1203
+ const execArgv = process2.execArgv ?? [];
1204
+ if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
1205
+ parseOptions.from = "eval";
1206
+ }
1207
+ }
1208
+ if (argv === undefined) {
1209
+ argv = process2.argv;
1210
+ }
1211
+ this.rawArgs = argv.slice();
1212
+ let userArgs;
1213
+ switch (parseOptions.from) {
1214
+ case undefined:
1215
+ case "node":
1216
+ this._scriptPath = argv[1];
1217
+ userArgs = argv.slice(2);
1218
+ break;
1219
+ case "electron":
1220
+ if (process2.defaultApp) {
1221
+ this._scriptPath = argv[1];
1222
+ userArgs = argv.slice(2);
1223
+ } else {
1224
+ userArgs = argv.slice(1);
1225
+ }
1226
+ break;
1227
+ case "user":
1228
+ userArgs = argv.slice(0);
1229
+ break;
1230
+ case "eval":
1231
+ userArgs = argv.slice(1);
1232
+ break;
1233
+ default:
1234
+ throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
1235
+ }
1236
+ if (!this._name && this._scriptPath)
1237
+ this.nameFromFilename(this._scriptPath);
1238
+ this._name = this._name || "program";
1239
+ return userArgs;
1240
+ }
1241
+ parse(argv, parseOptions) {
1242
+ this._prepareForParse();
1243
+ const userArgs = this._prepareUserArgs(argv, parseOptions);
1244
+ this._parseCommand([], userArgs);
1245
+ return this;
1246
+ }
1247
+ async parseAsync(argv, parseOptions) {
1248
+ this._prepareForParse();
1249
+ const userArgs = this._prepareUserArgs(argv, parseOptions);
1250
+ await this._parseCommand([], userArgs);
1251
+ return this;
1252
+ }
1253
+ _prepareForParse() {
1254
+ if (this._savedState === null) {
1255
+ this.saveStateBeforeParse();
1256
+ } else {
1257
+ this.restoreStateBeforeParse();
1258
+ }
1259
+ }
1260
+ saveStateBeforeParse() {
1261
+ this._savedState = {
1262
+ _name: this._name,
1263
+ _optionValues: { ...this._optionValues },
1264
+ _optionValueSources: { ...this._optionValueSources }
1265
+ };
1266
+ }
1267
+ restoreStateBeforeParse() {
1268
+ if (this._storeOptionsAsProperties)
1269
+ throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
1270
+ - either make a new Command for each call to parse, or stop storing options as properties`);
1271
+ this._name = this._savedState._name;
1272
+ this._scriptPath = null;
1273
+ this.rawArgs = [];
1274
+ this._optionValues = { ...this._savedState._optionValues };
1275
+ this._optionValueSources = { ...this._savedState._optionValueSources };
1276
+ this.args = [];
1277
+ this.processedArgs = [];
1278
+ }
1279
+ _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
1280
+ if (fs.existsSync(executableFile))
1281
+ return;
1282
+ const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
1283
+ const executableMissing = `'${executableFile}' does not exist
1284
+ - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1285
+ - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
1286
+ - ${executableDirMessage}`;
1287
+ throw new Error(executableMissing);
1288
+ }
1289
+ _executeSubCommand(subcommand, args) {
1290
+ args = args.slice();
1291
+ let launchWithNode = false;
1292
+ const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1293
+ function findFile(baseDir, baseName) {
1294
+ const localBin = path.resolve(baseDir, baseName);
1295
+ if (fs.existsSync(localBin))
1296
+ return localBin;
1297
+ if (sourceExt.includes(path.extname(baseName)))
1298
+ return;
1299
+ const foundExt = sourceExt.find((ext) => fs.existsSync(`${localBin}${ext}`));
1300
+ if (foundExt)
1301
+ return `${localBin}${foundExt}`;
1302
+ return;
1303
+ }
1304
+ this._checkForMissingMandatoryOptions();
1305
+ this._checkForConflictingOptions();
1306
+ let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
1307
+ let executableDir = this._executableDir || "";
1308
+ if (this._scriptPath) {
1309
+ let resolvedScriptPath;
1310
+ try {
1311
+ resolvedScriptPath = fs.realpathSync(this._scriptPath);
1312
+ } catch {
1313
+ resolvedScriptPath = this._scriptPath;
1314
+ }
1315
+ executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
1316
+ }
1317
+ if (executableDir) {
1318
+ let localFile = findFile(executableDir, executableFile);
1319
+ if (!localFile && !subcommand._executableFile && this._scriptPath) {
1320
+ const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
1321
+ if (legacyName !== this._name) {
1322
+ localFile = findFile(executableDir, `${legacyName}-${subcommand._name}`);
1323
+ }
1324
+ }
1325
+ executableFile = localFile || executableFile;
1326
+ }
1327
+ launchWithNode = sourceExt.includes(path.extname(executableFile));
1328
+ let proc;
1329
+ if (process2.platform !== "win32") {
1330
+ if (launchWithNode) {
1331
+ args.unshift(executableFile);
1332
+ args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1333
+ proc = childProcess.spawn(process2.argv[0], args, { stdio: "inherit" });
1334
+ } else {
1335
+ proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
1336
+ }
1337
+ } else {
1338
+ this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
1339
+ args.unshift(executableFile);
1340
+ args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1341
+ proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
1342
+ }
1343
+ if (!proc.killed) {
1344
+ const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
1345
+ signals.forEach((signal) => {
1346
+ process2.on(signal, () => {
1347
+ if (proc.killed === false && proc.exitCode === null) {
1348
+ proc.kill(signal);
1349
+ }
1350
+ });
1351
+ });
1352
+ }
1353
+ const exitCallback = this._exitCallback;
1354
+ proc.on("close", (code) => {
1355
+ code = code ?? 1;
1356
+ if (!exitCallback) {
1357
+ process2.exit(code);
1358
+ } else {
1359
+ exitCallback(new CommanderError(code, "commander.executeSubCommandAsync", "(close)"));
1360
+ }
1361
+ });
1362
+ proc.on("error", (err) => {
1363
+ if (err.code === "ENOENT") {
1364
+ this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
1365
+ } else if (err.code === "EACCES") {
1366
+ throw new Error(`'${executableFile}' not executable`);
1367
+ }
1368
+ if (!exitCallback) {
1369
+ process2.exit(1);
1370
+ } else {
1371
+ const wrappedError = new CommanderError(1, "commander.executeSubCommandAsync", "(error)");
1372
+ wrappedError.nestedError = err;
1373
+ exitCallback(wrappedError);
1374
+ }
1375
+ });
1376
+ this.runningCommand = proc;
1377
+ }
1378
+ _dispatchSubcommand(commandName, operands, unknown) {
1379
+ const subCommand = this._findCommand(commandName);
1380
+ if (!subCommand)
1381
+ this.help({ error: true });
1382
+ subCommand._prepareForParse();
1383
+ let promiseChain;
1384
+ promiseChain = this._chainOrCallSubCommandHook(promiseChain, subCommand, "preSubcommand");
1385
+ promiseChain = this._chainOrCall(promiseChain, () => {
1386
+ if (subCommand._executableHandler) {
1387
+ this._executeSubCommand(subCommand, operands.concat(unknown));
1388
+ } else {
1389
+ return subCommand._parseCommand(operands, unknown);
1390
+ }
1391
+ });
1392
+ return promiseChain;
1393
+ }
1394
+ _dispatchHelpCommand(subcommandName) {
1395
+ if (!subcommandName) {
1396
+ this.help();
1397
+ }
1398
+ const subCommand = this._findCommand(subcommandName);
1399
+ if (subCommand && !subCommand._executableHandler) {
1400
+ subCommand.help();
1401
+ }
1402
+ return this._dispatchSubcommand(subcommandName, [], [this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? "--help"]);
1403
+ }
1404
+ _checkNumberOfArguments() {
1405
+ this.registeredArguments.forEach((arg, i) => {
1406
+ if (arg.required && this.args[i] == null) {
1407
+ this.missingArgument(arg.name());
1408
+ }
1409
+ });
1410
+ if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) {
1411
+ return;
1412
+ }
1413
+ if (this.args.length > this.registeredArguments.length) {
1414
+ this._excessArguments(this.args);
1415
+ }
1416
+ }
1417
+ _processArguments() {
1418
+ const myParseArg = (argument, value, previous) => {
1419
+ let parsedValue = value;
1420
+ if (value !== null && argument.parseArg) {
1421
+ const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`;
1422
+ parsedValue = this._callParseArg(argument, value, previous, invalidValueMessage);
1423
+ }
1424
+ return parsedValue;
1425
+ };
1426
+ this._checkNumberOfArguments();
1427
+ const processedArgs = [];
1428
+ this.registeredArguments.forEach((declaredArg, index) => {
1429
+ let value = declaredArg.defaultValue;
1430
+ if (declaredArg.variadic) {
1431
+ if (index < this.args.length) {
1432
+ value = this.args.slice(index);
1433
+ if (declaredArg.parseArg) {
1434
+ value = value.reduce((processed, v) => {
1435
+ return myParseArg(declaredArg, v, processed);
1436
+ }, declaredArg.defaultValue);
1437
+ }
1438
+ } else if (value === undefined) {
1439
+ value = [];
1440
+ }
1441
+ } else if (index < this.args.length) {
1442
+ value = this.args[index];
1443
+ if (declaredArg.parseArg) {
1444
+ value = myParseArg(declaredArg, value, declaredArg.defaultValue);
1445
+ }
1446
+ }
1447
+ processedArgs[index] = value;
1448
+ });
1449
+ this.processedArgs = processedArgs;
1450
+ }
1451
+ _chainOrCall(promise, fn) {
1452
+ if (promise && promise.then && typeof promise.then === "function") {
1453
+ return promise.then(() => fn());
1454
+ }
1455
+ return fn();
1456
+ }
1457
+ _chainOrCallHooks(promise, event) {
1458
+ let result = promise;
1459
+ const hooks = [];
1460
+ this._getCommandAndAncestors().reverse().filter((cmd) => cmd._lifeCycleHooks[event] !== undefined).forEach((hookedCommand) => {
1461
+ hookedCommand._lifeCycleHooks[event].forEach((callback) => {
1462
+ hooks.push({ hookedCommand, callback });
1463
+ });
1464
+ });
1465
+ if (event === "postAction") {
1466
+ hooks.reverse();
1467
+ }
1468
+ hooks.forEach((hookDetail) => {
1469
+ result = this._chainOrCall(result, () => {
1470
+ return hookDetail.callback(hookDetail.hookedCommand, this);
1471
+ });
1472
+ });
1473
+ return result;
1474
+ }
1475
+ _chainOrCallSubCommandHook(promise, subCommand, event) {
1476
+ let result = promise;
1477
+ if (this._lifeCycleHooks[event] !== undefined) {
1478
+ this._lifeCycleHooks[event].forEach((hook) => {
1479
+ result = this._chainOrCall(result, () => {
1480
+ return hook(this, subCommand);
1481
+ });
1482
+ });
1483
+ }
1484
+ return result;
1485
+ }
1486
+ _parseCommand(operands, unknown) {
1487
+ const parsed = this.parseOptions(unknown);
1488
+ this._parseOptionsEnv();
1489
+ this._parseOptionsImplied();
1490
+ operands = operands.concat(parsed.operands);
1491
+ unknown = parsed.unknown;
1492
+ this.args = operands.concat(unknown);
1493
+ if (operands && this._findCommand(operands[0])) {
1494
+ return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
1495
+ }
1496
+ if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
1497
+ return this._dispatchHelpCommand(operands[1]);
1498
+ }
1499
+ if (this._defaultCommandName) {
1500
+ this._outputHelpIfRequested(unknown);
1501
+ return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
1502
+ }
1503
+ if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
1504
+ this.help({ error: true });
1505
+ }
1506
+ this._outputHelpIfRequested(parsed.unknown);
1507
+ this._checkForMissingMandatoryOptions();
1508
+ this._checkForConflictingOptions();
1509
+ const checkForUnknownOptions = () => {
1510
+ if (parsed.unknown.length > 0) {
1511
+ this.unknownOption(parsed.unknown[0]);
1512
+ }
1513
+ };
1514
+ const commandEvent = `command:${this.name()}`;
1515
+ if (this._actionHandler) {
1516
+ checkForUnknownOptions();
1517
+ this._processArguments();
1518
+ let promiseChain;
1519
+ promiseChain = this._chainOrCallHooks(promiseChain, "preAction");
1520
+ promiseChain = this._chainOrCall(promiseChain, () => this._actionHandler(this.processedArgs));
1521
+ if (this.parent) {
1522
+ promiseChain = this._chainOrCall(promiseChain, () => {
1523
+ this.parent.emit(commandEvent, operands, unknown);
1524
+ });
1525
+ }
1526
+ promiseChain = this._chainOrCallHooks(promiseChain, "postAction");
1527
+ return promiseChain;
1528
+ }
1529
+ if (this.parent && this.parent.listenerCount(commandEvent)) {
1530
+ checkForUnknownOptions();
1531
+ this._processArguments();
1532
+ this.parent.emit(commandEvent, operands, unknown);
1533
+ } else if (operands.length) {
1534
+ if (this._findCommand("*")) {
1535
+ return this._dispatchSubcommand("*", operands, unknown);
1536
+ }
1537
+ if (this.listenerCount("command:*")) {
1538
+ this.emit("command:*", operands, unknown);
1539
+ } else if (this.commands.length) {
1540
+ this.unknownCommand();
1541
+ } else {
1542
+ checkForUnknownOptions();
1543
+ this._processArguments();
1544
+ }
1545
+ } else if (this.commands.length) {
1546
+ checkForUnknownOptions();
1547
+ this.help({ error: true });
1548
+ } else {
1549
+ checkForUnknownOptions();
1550
+ this._processArguments();
1551
+ }
1552
+ }
1553
+ _findCommand(name) {
1554
+ if (!name)
1555
+ return;
1556
+ return this.commands.find((cmd) => cmd._name === name || cmd._aliases.includes(name));
1557
+ }
1558
+ _findOption(arg) {
1559
+ return this.options.find((option) => option.is(arg));
1560
+ }
1561
+ _checkForMissingMandatoryOptions() {
1562
+ this._getCommandAndAncestors().forEach((cmd) => {
1563
+ cmd.options.forEach((anOption) => {
1564
+ if (anOption.mandatory && cmd.getOptionValue(anOption.attributeName()) === undefined) {
1565
+ cmd.missingMandatoryOptionValue(anOption);
1566
+ }
1567
+ });
1568
+ });
1569
+ }
1570
+ _checkForConflictingLocalOptions() {
1571
+ const definedNonDefaultOptions = this.options.filter((option) => {
1572
+ const optionKey = option.attributeName();
1573
+ if (this.getOptionValue(optionKey) === undefined) {
1574
+ return false;
1575
+ }
1576
+ return this.getOptionValueSource(optionKey) !== "default";
1577
+ });
1578
+ const optionsWithConflicting = definedNonDefaultOptions.filter((option) => option.conflictsWith.length > 0);
1579
+ optionsWithConflicting.forEach((option) => {
1580
+ const conflictingAndDefined = definedNonDefaultOptions.find((defined) => option.conflictsWith.includes(defined.attributeName()));
1581
+ if (conflictingAndDefined) {
1582
+ this._conflictingOption(option, conflictingAndDefined);
1583
+ }
1584
+ });
1585
+ }
1586
+ _checkForConflictingOptions() {
1587
+ this._getCommandAndAncestors().forEach((cmd) => {
1588
+ cmd._checkForConflictingLocalOptions();
1589
+ });
1590
+ }
1591
+ parseOptions(argv) {
1592
+ const operands = [];
1593
+ const unknown = [];
1594
+ let dest = operands;
1595
+ const args = argv.slice();
1596
+ function maybeOption(arg) {
1597
+ return arg.length > 1 && arg[0] === "-";
1598
+ }
1599
+ let activeVariadicOption = null;
1600
+ while (args.length) {
1601
+ const arg = args.shift();
1602
+ if (arg === "--") {
1603
+ if (dest === unknown)
1604
+ dest.push(arg);
1605
+ dest.push(...args);
1606
+ break;
1607
+ }
1608
+ if (activeVariadicOption && !maybeOption(arg)) {
1609
+ this.emit(`option:${activeVariadicOption.name()}`, arg);
1610
+ continue;
1611
+ }
1612
+ activeVariadicOption = null;
1613
+ if (maybeOption(arg)) {
1614
+ const option = this._findOption(arg);
1615
+ if (option) {
1616
+ if (option.required) {
1617
+ const value = args.shift();
1618
+ if (value === undefined)
1619
+ this.optionMissingArgument(option);
1620
+ this.emit(`option:${option.name()}`, value);
1621
+ } else if (option.optional) {
1622
+ let value = null;
1623
+ if (args.length > 0 && !maybeOption(args[0])) {
1624
+ value = args.shift();
1625
+ }
1626
+ this.emit(`option:${option.name()}`, value);
1627
+ } else {
1628
+ this.emit(`option:${option.name()}`);
1629
+ }
1630
+ activeVariadicOption = option.variadic ? option : null;
1631
+ continue;
1632
+ }
1633
+ }
1634
+ if (arg.length > 2 && arg[0] === "-" && arg[1] !== "-") {
1635
+ const option = this._findOption(`-${arg[1]}`);
1636
+ if (option) {
1637
+ if (option.required || option.optional && this._combineFlagAndOptionalValue) {
1638
+ this.emit(`option:${option.name()}`, arg.slice(2));
1639
+ } else {
1640
+ this.emit(`option:${option.name()}`);
1641
+ args.unshift(`-${arg.slice(2)}`);
1642
+ }
1643
+ continue;
1644
+ }
1645
+ }
1646
+ if (/^--[^=]+=/.test(arg)) {
1647
+ const index = arg.indexOf("=");
1648
+ const option = this._findOption(arg.slice(0, index));
1649
+ if (option && (option.required || option.optional)) {
1650
+ this.emit(`option:${option.name()}`, arg.slice(index + 1));
1651
+ continue;
1652
+ }
1653
+ }
1654
+ if (maybeOption(arg)) {
1655
+ dest = unknown;
1656
+ }
1657
+ if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
1658
+ if (this._findCommand(arg)) {
1659
+ operands.push(arg);
1660
+ if (args.length > 0)
1661
+ unknown.push(...args);
1662
+ break;
1663
+ } else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
1664
+ operands.push(arg);
1665
+ if (args.length > 0)
1666
+ operands.push(...args);
1667
+ break;
1668
+ } else if (this._defaultCommandName) {
1669
+ unknown.push(arg);
1670
+ if (args.length > 0)
1671
+ unknown.push(...args);
1672
+ break;
1673
+ }
1674
+ }
1675
+ if (this._passThroughOptions) {
1676
+ dest.push(arg);
1677
+ if (args.length > 0)
1678
+ dest.push(...args);
1679
+ break;
1680
+ }
1681
+ dest.push(arg);
1682
+ }
1683
+ return { operands, unknown };
1684
+ }
1685
+ opts() {
1686
+ if (this._storeOptionsAsProperties) {
1687
+ const result = {};
1688
+ const len = this.options.length;
1689
+ for (let i = 0;i < len; i++) {
1690
+ const key = this.options[i].attributeName();
1691
+ result[key] = key === this._versionOptionName ? this._version : this[key];
1692
+ }
1693
+ return result;
1694
+ }
1695
+ return this._optionValues;
1696
+ }
1697
+ optsWithGlobals() {
1698
+ return this._getCommandAndAncestors().reduce((combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()), {});
1699
+ }
1700
+ error(message, errorOptions) {
1701
+ this._outputConfiguration.outputError(`${message}
1702
+ `, this._outputConfiguration.writeErr);
1703
+ if (typeof this._showHelpAfterError === "string") {
1704
+ this._outputConfiguration.writeErr(`${this._showHelpAfterError}
1705
+ `);
1706
+ } else if (this._showHelpAfterError) {
1707
+ this._outputConfiguration.writeErr(`
1708
+ `);
1709
+ this.outputHelp({ error: true });
1710
+ }
1711
+ const config = errorOptions || {};
1712
+ const exitCode = config.exitCode || 1;
1713
+ const code = config.code || "commander.error";
1714
+ this._exit(exitCode, code, message);
1715
+ }
1716
+ _parseOptionsEnv() {
1717
+ this.options.forEach((option) => {
1718
+ if (option.envVar && option.envVar in process2.env) {
1719
+ const optionKey = option.attributeName();
1720
+ if (this.getOptionValue(optionKey) === undefined || ["default", "config", "env"].includes(this.getOptionValueSource(optionKey))) {
1721
+ if (option.required || option.optional) {
1722
+ this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
1723
+ } else {
1724
+ this.emit(`optionEnv:${option.name()}`);
1725
+ }
1726
+ }
1727
+ }
1728
+ });
1729
+ }
1730
+ _parseOptionsImplied() {
1731
+ const dualHelper = new DualOptions(this.options);
1732
+ const hasCustomOptionValue = (optionKey) => {
1733
+ return this.getOptionValue(optionKey) !== undefined && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
1734
+ };
1735
+ this.options.filter((option) => option.implied !== undefined && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(this.getOptionValue(option.attributeName()), option)).forEach((option) => {
1736
+ Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => {
1737
+ this.setOptionValueWithSource(impliedKey, option.implied[impliedKey], "implied");
1738
+ });
1739
+ });
1740
+ }
1741
+ missingArgument(name) {
1742
+ const message = `error: missing required argument '${name}'`;
1743
+ this.error(message, { code: "commander.missingArgument" });
1744
+ }
1745
+ optionMissingArgument(option) {
1746
+ const message = `error: option '${option.flags}' argument missing`;
1747
+ this.error(message, { code: "commander.optionMissingArgument" });
1748
+ }
1749
+ missingMandatoryOptionValue(option) {
1750
+ const message = `error: required option '${option.flags}' not specified`;
1751
+ this.error(message, { code: "commander.missingMandatoryOptionValue" });
1752
+ }
1753
+ _conflictingOption(option, conflictingOption) {
1754
+ const findBestOptionFromValue = (option2) => {
1755
+ const optionKey = option2.attributeName();
1756
+ const optionValue = this.getOptionValue(optionKey);
1757
+ const negativeOption = this.options.find((target) => target.negate && optionKey === target.attributeName());
1758
+ const positiveOption = this.options.find((target) => !target.negate && optionKey === target.attributeName());
1759
+ if (negativeOption && (negativeOption.presetArg === undefined && optionValue === false || negativeOption.presetArg !== undefined && optionValue === negativeOption.presetArg)) {
1760
+ return negativeOption;
1761
+ }
1762
+ return positiveOption || option2;
1763
+ };
1764
+ const getErrorMessage = (option2) => {
1765
+ const bestOption = findBestOptionFromValue(option2);
1766
+ const optionKey = bestOption.attributeName();
1767
+ const source = this.getOptionValueSource(optionKey);
1768
+ if (source === "env") {
1769
+ return `environment variable '${bestOption.envVar}'`;
1770
+ }
1771
+ return `option '${bestOption.flags}'`;
1772
+ };
1773
+ const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
1774
+ this.error(message, { code: "commander.conflictingOption" });
1775
+ }
1776
+ unknownOption(flag) {
1777
+ if (this._allowUnknownOption)
1778
+ return;
1779
+ let suggestion = "";
1780
+ if (flag.startsWith("--") && this._showSuggestionAfterError) {
1781
+ let candidateFlags = [];
1782
+ let command = this;
1783
+ do {
1784
+ const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long);
1785
+ candidateFlags = candidateFlags.concat(moreFlags);
1786
+ command = command.parent;
1787
+ } while (command && !command._enablePositionalOptions);
1788
+ suggestion = suggestSimilar(flag, candidateFlags);
1789
+ }
1790
+ const message = `error: unknown option '${flag}'${suggestion}`;
1791
+ this.error(message, { code: "commander.unknownOption" });
1792
+ }
1793
+ _excessArguments(receivedArgs) {
1794
+ if (this._allowExcessArguments)
1795
+ return;
1796
+ const expected = this.registeredArguments.length;
1797
+ const s = expected === 1 ? "" : "s";
1798
+ const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
1799
+ const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
1800
+ this.error(message, { code: "commander.excessArguments" });
1801
+ }
1802
+ unknownCommand() {
1803
+ const unknownName = this.args[0];
1804
+ let suggestion = "";
1805
+ if (this._showSuggestionAfterError) {
1806
+ const candidateNames = [];
1807
+ this.createHelp().visibleCommands(this).forEach((command) => {
1808
+ candidateNames.push(command.name());
1809
+ if (command.alias())
1810
+ candidateNames.push(command.alias());
1811
+ });
1812
+ suggestion = suggestSimilar(unknownName, candidateNames);
1813
+ }
1814
+ const message = `error: unknown command '${unknownName}'${suggestion}`;
1815
+ this.error(message, { code: "commander.unknownCommand" });
1816
+ }
1817
+ version(str, flags, description) {
1818
+ if (str === undefined)
1819
+ return this._version;
1820
+ this._version = str;
1821
+ flags = flags || "-V, --version";
1822
+ description = description || "output the version number";
1823
+ const versionOption = this.createOption(flags, description);
1824
+ this._versionOptionName = versionOption.attributeName();
1825
+ this._registerOption(versionOption);
1826
+ this.on("option:" + versionOption.name(), () => {
1827
+ this._outputConfiguration.writeOut(`${str}
1828
+ `);
1829
+ this._exit(0, "commander.version", str);
1830
+ });
1831
+ return this;
1832
+ }
1833
+ description(str, argsDescription) {
1834
+ if (str === undefined && argsDescription === undefined)
1835
+ return this._description;
1836
+ this._description = str;
1837
+ if (argsDescription) {
1838
+ this._argsDescription = argsDescription;
1839
+ }
1840
+ return this;
1841
+ }
1842
+ summary(str) {
1843
+ if (str === undefined)
1844
+ return this._summary;
1845
+ this._summary = str;
1846
+ return this;
1847
+ }
1848
+ alias(alias) {
1849
+ if (alias === undefined)
1850
+ return this._aliases[0];
1851
+ let command = this;
1852
+ if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
1853
+ command = this.commands[this.commands.length - 1];
1854
+ }
1855
+ if (alias === command._name)
1856
+ throw new Error("Command alias can't be the same as its name");
1857
+ const matchingCommand = this.parent?._findCommand(alias);
1858
+ if (matchingCommand) {
1859
+ const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|");
1860
+ throw new Error(`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`);
1861
+ }
1862
+ command._aliases.push(alias);
1863
+ return this;
1864
+ }
1865
+ aliases(aliases) {
1866
+ if (aliases === undefined)
1867
+ return this._aliases;
1868
+ aliases.forEach((alias) => this.alias(alias));
1869
+ return this;
1870
+ }
1871
+ usage(str) {
1872
+ if (str === undefined) {
1873
+ if (this._usage)
1874
+ return this._usage;
1875
+ const args = this.registeredArguments.map((arg) => {
1876
+ return humanReadableArgName(arg);
1877
+ });
1878
+ return [].concat(this.options.length || this._helpOption !== null ? "[options]" : [], this.commands.length ? "[command]" : [], this.registeredArguments.length ? args : []).join(" ");
1879
+ }
1880
+ this._usage = str;
1881
+ return this;
1882
+ }
1883
+ name(str) {
1884
+ if (str === undefined)
1885
+ return this._name;
1886
+ this._name = str;
1887
+ return this;
1888
+ }
1889
+ nameFromFilename(filename) {
1890
+ this._name = path.basename(filename, path.extname(filename));
1891
+ return this;
1892
+ }
1893
+ executableDir(path2) {
1894
+ if (path2 === undefined)
1895
+ return this._executableDir;
1896
+ this._executableDir = path2;
1897
+ return this;
1898
+ }
1899
+ helpInformation(contextOptions) {
1900
+ const helper = this.createHelp();
1901
+ const context = this._getOutputContext(contextOptions);
1902
+ helper.prepareContext({
1903
+ error: context.error,
1904
+ helpWidth: context.helpWidth,
1905
+ outputHasColors: context.hasColors
1906
+ });
1907
+ const text = helper.formatHelp(this, helper);
1908
+ if (context.hasColors)
1909
+ return text;
1910
+ return this._outputConfiguration.stripColor(text);
1911
+ }
1912
+ _getOutputContext(contextOptions) {
1913
+ contextOptions = contextOptions || {};
1914
+ const error = !!contextOptions.error;
1915
+ let baseWrite;
1916
+ let hasColors;
1917
+ let helpWidth;
1918
+ if (error) {
1919
+ baseWrite = (str) => this._outputConfiguration.writeErr(str);
1920
+ hasColors = this._outputConfiguration.getErrHasColors();
1921
+ helpWidth = this._outputConfiguration.getErrHelpWidth();
1922
+ } else {
1923
+ baseWrite = (str) => this._outputConfiguration.writeOut(str);
1924
+ hasColors = this._outputConfiguration.getOutHasColors();
1925
+ helpWidth = this._outputConfiguration.getOutHelpWidth();
1926
+ }
1927
+ const write = (str) => {
1928
+ if (!hasColors)
1929
+ str = this._outputConfiguration.stripColor(str);
1930
+ return baseWrite(str);
1931
+ };
1932
+ return { error, write, hasColors, helpWidth };
1933
+ }
1934
+ outputHelp(contextOptions) {
1935
+ let deprecatedCallback;
1936
+ if (typeof contextOptions === "function") {
1937
+ deprecatedCallback = contextOptions;
1938
+ contextOptions = undefined;
1939
+ }
1940
+ const outputContext = this._getOutputContext(contextOptions);
1941
+ const eventContext = {
1942
+ error: outputContext.error,
1943
+ write: outputContext.write,
1944
+ command: this
1945
+ };
1946
+ this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
1947
+ this.emit("beforeHelp", eventContext);
1948
+ let helpInformation = this.helpInformation({ error: outputContext.error });
1949
+ if (deprecatedCallback) {
1950
+ helpInformation = deprecatedCallback(helpInformation);
1951
+ if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
1952
+ throw new Error("outputHelp callback must return a string or a Buffer");
1953
+ }
1954
+ }
1955
+ outputContext.write(helpInformation);
1956
+ if (this._getHelpOption()?.long) {
1957
+ this.emit(this._getHelpOption().long);
1958
+ }
1959
+ this.emit("afterHelp", eventContext);
1960
+ this._getCommandAndAncestors().forEach((command) => command.emit("afterAllHelp", eventContext));
1961
+ }
1962
+ helpOption(flags, description) {
1963
+ if (typeof flags === "boolean") {
1964
+ if (flags) {
1965
+ this._helpOption = this._helpOption ?? undefined;
1966
+ } else {
1967
+ this._helpOption = null;
1968
+ }
1969
+ return this;
1970
+ }
1971
+ flags = flags ?? "-h, --help";
1972
+ description = description ?? "display help for command";
1973
+ this._helpOption = this.createOption(flags, description);
1974
+ return this;
1975
+ }
1976
+ _getHelpOption() {
1977
+ if (this._helpOption === undefined) {
1978
+ this.helpOption(undefined, undefined);
1979
+ }
1980
+ return this._helpOption;
1981
+ }
1982
+ addHelpOption(option) {
1983
+ this._helpOption = option;
1984
+ return this;
1985
+ }
1986
+ help(contextOptions) {
1987
+ this.outputHelp(contextOptions);
1988
+ let exitCode = Number(process2.exitCode ?? 0);
1989
+ if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
1990
+ exitCode = 1;
1991
+ }
1992
+ this._exit(exitCode, "commander.help", "(outputHelp)");
1993
+ }
1994
+ addHelpText(position, text) {
1995
+ const allowedValues = ["beforeAll", "before", "after", "afterAll"];
1996
+ if (!allowedValues.includes(position)) {
1997
+ throw new Error(`Unexpected value for position to addHelpText.
1998
+ Expecting one of '${allowedValues.join("', '")}'`);
1999
+ }
2000
+ const helpEvent = `${position}Help`;
2001
+ this.on(helpEvent, (context) => {
2002
+ let helpStr;
2003
+ if (typeof text === "function") {
2004
+ helpStr = text({ error: context.error, command: context.command });
2005
+ } else {
2006
+ helpStr = text;
2007
+ }
2008
+ if (helpStr) {
2009
+ context.write(`${helpStr}
2010
+ `);
2011
+ }
2012
+ });
2013
+ return this;
2014
+ }
2015
+ _outputHelpIfRequested(args) {
2016
+ const helpOption = this._getHelpOption();
2017
+ const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
2018
+ if (helpRequested) {
2019
+ this.outputHelp();
2020
+ this._exit(0, "commander.helpDisplayed", "(outputHelp)");
2021
+ }
2022
+ }
2023
+ }
2024
+ function incrementNodeInspectorPort(args) {
2025
+ return args.map((arg) => {
2026
+ if (!arg.startsWith("--inspect")) {
2027
+ return arg;
2028
+ }
2029
+ let debugOption;
2030
+ let debugHost = "127.0.0.1";
2031
+ let debugPort = "9229";
2032
+ let match;
2033
+ if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
2034
+ debugOption = match[1];
2035
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
2036
+ debugOption = match[1];
2037
+ if (/^\d+$/.test(match[3])) {
2038
+ debugPort = match[3];
2039
+ } else {
2040
+ debugHost = match[3];
2041
+ }
2042
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
2043
+ debugOption = match[1];
2044
+ debugHost = match[3];
2045
+ debugPort = match[4];
2046
+ }
2047
+ if (debugOption && debugPort !== "0") {
2048
+ return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
2049
+ }
2050
+ return arg;
2051
+ });
2052
+ }
2053
+ function useColor() {
2054
+ if (process2.env.NO_COLOR || process2.env.FORCE_COLOR === "0" || process2.env.FORCE_COLOR === "false")
2055
+ return false;
2056
+ if (process2.env.FORCE_COLOR || process2.env.CLICOLOR_FORCE !== undefined)
2057
+ return true;
2058
+ return;
2059
+ }
2060
+ exports.Command = Command;
2061
+ exports.useColor = useColor;
2062
+ });
2063
+
2064
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/index.js
2065
+ var require_commander = __commonJS((exports) => {
2066
+ var { Argument } = require_argument();
2067
+ var { Command } = require_command();
2068
+ var { CommanderError, InvalidArgumentError } = require_error();
2069
+ var { Help } = require_help();
2070
+ var { Option } = require_option();
2071
+ exports.program = new Command;
2072
+ exports.createCommand = (name) => new Command(name);
2073
+ exports.createOption = (flags, description) => new Option(flags, description);
2074
+ exports.createArgument = (name, description) => new Argument(name, description);
2075
+ exports.Command = Command;
2076
+ exports.Option = Option;
2077
+ exports.Argument = Argument;
2078
+ exports.Help = Help;
2079
+ exports.CommanderError = CommanderError;
2080
+ exports.InvalidArgumentError = InvalidArgumentError;
2081
+ exports.InvalidOptionArgumentError = InvalidArgumentError;
2082
+ });
2083
+
2084
+ // src/config.ts
2085
+ import { existsSync, readFileSync } from "fs";
2086
+ import { homedir } from "os";
2087
+ import { join } from "path";
2088
+ function firstEnv(env, keys) {
2089
+ for (const key of keys) {
2090
+ const value = env[key]?.trim();
2091
+ if (value)
2092
+ return value;
2093
+ }
2094
+ return;
2095
+ }
2096
+ function resolveStorageMode(env = process.env) {
2097
+ const raw = firstEnv(env, MODE_KEYS);
2098
+ const dsnPresent = databaseUrlPresent(env);
2099
+ let mode;
2100
+ if (!raw) {
2101
+ mode = "local";
2102
+ } else {
2103
+ const normalized = raw.toLowerCase().replace(/-/g, "_");
2104
+ if (normalized === "local")
2105
+ mode = "local";
2106
+ else if (normalized === "cloud" || DEPRECATED_CLOUD_ALIASES.has(normalized))
2107
+ mode = "cloud";
2108
+ else
2109
+ throw new Error(`Unknown storage mode: ${raw}. Use local or cloud.`);
2110
+ }
2111
+ if (mode === "local" && dsnPresent) {
2112
+ throw new Error(`Fail-closed misconfig: a ${ENV_TOKEN} DATABASE_URL is present but storage mode is 'local'. ` + `Set HASNA_${ENV_TOKEN}_STORAGE_MODE=cloud (PURE REMOTE) or unset the DATABASE_URL. ` + `Refusing to silently write money/audit data to SQLite while a cloud DB is configured.`);
2113
+ }
2114
+ if (mode === "cloud" && !dsnPresent) {
2115
+ console.warn(`[treasury] cloud mode needs HASNA_${ENV_TOKEN}_DATABASE_URL (or *_FILE); ` + `PURE REMOTE reads/writes go to cloud Postgres.`);
2116
+ }
2117
+ return mode;
2118
+ }
2119
+ function databaseUrlPresent(env = process.env) {
2120
+ return firstEnv(env, DB_URL_KEYS) !== undefined || firstEnv(env, DB_URL_FILE_KEYS) !== undefined;
2121
+ }
2122
+ function resolveDatabaseUrl(env = process.env) {
2123
+ const filePath = firstEnv(env, DB_URL_FILE_KEYS);
2124
+ if (filePath && existsSync(filePath))
2125
+ return readFileSync(filePath, "utf8").trim();
2126
+ return firstEnv(env, DB_URL_KEYS) ?? null;
2127
+ }
2128
+ function scrubDatabaseUrl(env = process.env) {
2129
+ for (const key of DB_URL_KEYS)
2130
+ delete env[key];
2131
+ }
2132
+ function defaultSqlitePath() {
2133
+ return join(homedir(), ".hasna", APP_NAME, `${APP_NAME}.db`);
2134
+ }
2135
+ function resolveDbPath(env = process.env) {
2136
+ return firstEnv(env, DB_PATH_KEYS) ?? defaultSqlitePath();
2137
+ }
2138
+ var APP_NAME = "treasury", ENV_TOKEN = "TREASURY", DEPRECATED_CLOUD_ALIASES, MODE_KEYS, DB_URL_KEYS, DB_URL_FILE_KEYS, DB_PATH_KEYS;
2139
+ var init_config = __esm(() => {
2140
+ DEPRECATED_CLOUD_ALIASES = new Set(["remote", "hybrid", "self_hosted"]);
2141
+ MODE_KEYS = [`HASNA_${ENV_TOKEN}_STORAGE_MODE`, `${ENV_TOKEN}_STORAGE_MODE`];
2142
+ DB_URL_KEYS = [`HASNA_${ENV_TOKEN}_DATABASE_URL`, `${ENV_TOKEN}_DATABASE_URL`];
2143
+ DB_URL_FILE_KEYS = [`HASNA_${ENV_TOKEN}_DATABASE_URL_FILE`, `${ENV_TOKEN}_DATABASE_URL_FILE`];
2144
+ DB_PATH_KEYS = [`HASNA_${ENV_TOKEN}_DB_PATH`, `${ENV_TOKEN}_DB_PATH`];
2145
+ });
2146
+
2147
+ // src/core/app-home.ts
2148
+ import { existsSync as existsSync2, mkdirSync } from "fs";
2149
+ import { homedir as homedir2 } from "os";
2150
+ import { join as join2, resolve } from "path";
2151
+ function homeDir() {
2152
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
2153
+ }
2154
+ function getTreasuryAppHome() {
2155
+ return resolve(process.env["HASNA_TREASURY_HOME"] ?? process.env["TREASURY_HOME"] ?? join2(homeDir(), ".hasna", APP_NAME));
2156
+ }
2157
+ function getTreasuryAppDir(name) {
2158
+ return join2(getTreasuryAppHome(), name);
2159
+ }
2160
+ function ensureTreasuryAppHome() {
2161
+ const root = getTreasuryAppHome();
2162
+ mkdirSync(root, { recursive: true, mode: 448 });
2163
+ const dirs = { root };
2164
+ for (const name of TREASURY_APP_SUBDIRS) {
2165
+ const dir = getTreasuryAppDir(name);
2166
+ if (!existsSync2(dir))
2167
+ mkdirSync(dir, { recursive: true, mode: 448 });
2168
+ dirs[name] = dir;
2169
+ }
2170
+ return dirs;
2171
+ }
2172
+ function getDefaultTreasuryBackupDir() {
2173
+ return getTreasuryAppDir("backups");
2174
+ }
2175
+ var TREASURY_APP_SUBDIRS;
2176
+ var init_app_home = __esm(() => {
2177
+ init_config();
2178
+ TREASURY_APP_SUBDIRS = ["config", "data", "exports", "backups", "logs", "tmp"];
2179
+ });
2180
+
2181
+ // src/db/backup.ts
2182
+ import { chmodSync, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync } from "fs";
2183
+ import { basename, join as join3, resolve as resolve2 } from "path";
2184
+ function backupsDisabled() {
2185
+ const raw = process.env["HASNA_TREASURY_BACKUP_BEFORE_MIGRATION"] ?? process.env["TREASURY_BACKUP_BEFORE_MIGRATION"];
2186
+ if (!raw)
2187
+ return false;
2188
+ return ["0", "false", "no", "off"].includes(raw.toLowerCase());
2189
+ }
2190
+ function backupDirFor(dbPath) {
2191
+ const override = process.env["HASNA_TREASURY_BACKUP_DIR"] ?? process.env["TREASURY_BACKUP_DIR"];
2192
+ if (override)
2193
+ return resolve2(override);
2194
+ const resolved = resolve2(dbPath);
2195
+ if (resolved === resolve2(getDefaultTreasuryBackupDir(), "..", "data", "treasury.db")) {
2196
+ return getDefaultTreasuryBackupDir();
2197
+ }
2198
+ return getDefaultTreasuryBackupDir();
2199
+ }
2200
+ function maybeBackupBeforeMigration(dbPath) {
2201
+ if (backupsDisabled())
2202
+ return { skipped: true, reason: "disabled" };
2203
+ if (dbPath === ":memory:")
2204
+ return { skipped: true, reason: "memory database" };
2205
+ if (!existsSync3(dbPath))
2206
+ return { skipped: true, reason: "no existing database" };
2207
+ if (statSync(dbPath).size === 0)
2208
+ return { skipped: true, reason: "empty database" };
2209
+ const dir = backupDirFor(dbPath);
2210
+ mkdirSync2(dir, { recursive: true, mode: 448 });
2211
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
2212
+ const backupPath = join3(dir, `${basename(dbPath)}-${stamp}-pre-migration.db`);
2213
+ copyFileSync(dbPath, backupPath);
2214
+ chmodSync(backupPath, 384);
2215
+ pruneOldBackups(dir, basename(dbPath));
2216
+ return { skipped: false, backup_path: backupPath };
2217
+ }
2218
+ function pruneOldBackups(dir, dbBase) {
2219
+ const prefix = `${dbBase}-`;
2220
+ const entries = readdirSync(dir).filter((n) => n.startsWith(prefix) && n.endsWith("-pre-migration.db")).map((n) => ({ name: n, path: join3(dir, n), mtime: statSync(join3(dir, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
2221
+ for (const stale of entries.slice(RETENTION)) {
2222
+ try {
2223
+ unlinkSync(stale.path);
2224
+ } catch {}
2225
+ }
2226
+ }
2227
+ var RETENTION = 10;
2228
+ var init_backup = __esm(() => {
2229
+ init_app_home();
2230
+ });
2231
+
2232
+ // src/db/schema.ts
2233
+ function runSqliteMigrations(db) {
2234
+ db.run(SQLITE_SCHEMA);
2235
+ db.run("INSERT OR IGNORE INTO schema_migrations (id) VALUES (1)");
2236
+ }
2237
+ var SQLITE_SCHEMA = `
2238
+ CREATE TABLE IF NOT EXISTS schema_migrations (
2239
+ id INTEGER PRIMARY KEY,
2240
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
2241
+ );
2242
+
2243
+ CREATE TABLE IF NOT EXISTS entities (
2244
+ entity_id TEXT PRIMARY KEY,
2245
+ entity_slug TEXT UNIQUE,
2246
+ name TEXT NOT NULL,
2247
+ base_currency TEXT NOT NULL,
2248
+ created_at TEXT NOT NULL,
2249
+ updated_at TEXT NOT NULL
2250
+ );
2251
+
2252
+ CREATE TABLE IF NOT EXISTS balance_snapshots (
2253
+ id TEXT PRIMARY KEY,
2254
+ entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2255
+ account_ref TEXT NOT NULL,
2256
+ account_kind TEXT NOT NULL,
2257
+ currency TEXT NOT NULL,
2258
+ amount_minor INTEGER NOT NULL,
2259
+ as_of TEXT NOT NULL,
2260
+ source TEXT NOT NULL,
2261
+ captured_at TEXT NOT NULL
2262
+ );
2263
+ CREATE INDEX IF NOT EXISTS idx_balances_entity ON balance_snapshots(entity_id);
2264
+
2265
+ CREATE TABLE IF NOT EXISTS fx_rates (
2266
+ id TEXT PRIMARY KEY,
2267
+ base_currency TEXT NOT NULL,
2268
+ quote_currency TEXT NOT NULL,
2269
+ rate REAL NOT NULL,
2270
+ as_of TEXT NOT NULL,
2271
+ source TEXT NOT NULL,
2272
+ captured_at TEXT NOT NULL
2273
+ );
2274
+ CREATE INDEX IF NOT EXISTS idx_fx_pair ON fx_rates(base_currency, quote_currency);
2275
+
2276
+ CREATE TABLE IF NOT EXISTS cost_feeds (
2277
+ id TEXT PRIMARY KEY,
2278
+ entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2279
+ currency TEXT NOT NULL,
2280
+ monthly_burn_minor INTEGER NOT NULL,
2281
+ as_of TEXT NOT NULL,
2282
+ source TEXT NOT NULL,
2283
+ captured_at TEXT NOT NULL
2284
+ );
2285
+ CREATE INDEX IF NOT EXISTS idx_cost_entity ON cost_feeds(entity_id);
2286
+
2287
+ CREATE TABLE IF NOT EXISTS sweep_recommendations (
2288
+ id TEXT PRIMARY KEY,
2289
+ from_entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2290
+ to_entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2291
+ currency TEXT NOT NULL,
2292
+ amount_minor INTEGER NOT NULL,
2293
+ rationale TEXT NOT NULL,
2294
+ status TEXT NOT NULL DEFAULT 'recommended',
2295
+ created_at TEXT NOT NULL,
2296
+ updated_at TEXT NOT NULL
2297
+ );
2298
+ CREATE INDEX IF NOT EXISTS idx_sweeps_from ON sweep_recommendations(from_entity_id);
2299
+
2300
+ CREATE TABLE IF NOT EXISTS audit_log (
2301
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2302
+ entity_id TEXT,
2303
+ actor_id TEXT NOT NULL,
2304
+ action TEXT NOT NULL,
2305
+ detail TEXT NOT NULL,
2306
+ prev_hash TEXT NOT NULL,
2307
+ row_hash TEXT NOT NULL,
2308
+ created_at TEXT NOT NULL
2309
+ );
2310
+
2311
+ -- Keep the hash chain strictly linear under concurrent writers: each row's
2312
+ -- prev_hash is the previous row's row_hash, so it is unique by construction.
2313
+ -- A concurrent fork (two appends reading the same prev_hash) then loses the
2314
+ -- race with a unique-violation instead of silently forking the chain (\xA74.7).
2315
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_prev_hash ON audit_log(prev_hash);
2316
+
2317
+ CREATE TRIGGER IF NOT EXISTS audit_log_no_update
2318
+ BEFORE UPDATE ON audit_log
2319
+ BEGIN
2320
+ SELECT RAISE(ABORT, 'audit_log is append-only');
2321
+ END;
2322
+
2323
+ CREATE TRIGGER IF NOT EXISTS audit_log_no_delete
2324
+ BEFORE DELETE ON audit_log
2325
+ BEGIN
2326
+ SELECT RAISE(ABORT, 'audit_log is append-only');
2327
+ END;
2328
+ `, POSTGRES_MIGRATIONS;
2329
+ var init_schema = __esm(() => {
2330
+ POSTGRES_MIGRATIONS = [
2331
+ `CREATE TABLE IF NOT EXISTS schema_migrations (
2332
+ id INTEGER PRIMARY KEY,
2333
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
2334
+ )`,
2335
+ `INSERT INTO schema_migrations (id) VALUES (1) ON CONFLICT DO NOTHING`,
2336
+ `CREATE TABLE IF NOT EXISTS entities (
2337
+ entity_id TEXT PRIMARY KEY,
2338
+ entity_slug TEXT UNIQUE,
2339
+ name TEXT NOT NULL,
2340
+ base_currency TEXT NOT NULL,
2341
+ created_at TEXT NOT NULL,
2342
+ updated_at TEXT NOT NULL
2343
+ )`,
2344
+ `CREATE TABLE IF NOT EXISTS balance_snapshots (
2345
+ id TEXT PRIMARY KEY,
2346
+ entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2347
+ account_ref TEXT NOT NULL,
2348
+ account_kind TEXT NOT NULL,
2349
+ currency TEXT NOT NULL,
2350
+ amount_minor BIGINT NOT NULL,
2351
+ as_of TEXT NOT NULL,
2352
+ source TEXT NOT NULL,
2353
+ captured_at TEXT NOT NULL
2354
+ )`,
2355
+ `CREATE INDEX IF NOT EXISTS idx_balances_entity ON balance_snapshots(entity_id)`,
2356
+ `CREATE TABLE IF NOT EXISTS fx_rates (
2357
+ id TEXT PRIMARY KEY,
2358
+ base_currency TEXT NOT NULL,
2359
+ quote_currency TEXT NOT NULL,
2360
+ rate DOUBLE PRECISION NOT NULL,
2361
+ as_of TEXT NOT NULL,
2362
+ source TEXT NOT NULL,
2363
+ captured_at TEXT NOT NULL
2364
+ )`,
2365
+ `CREATE INDEX IF NOT EXISTS idx_fx_pair ON fx_rates(base_currency, quote_currency)`,
2366
+ `CREATE TABLE IF NOT EXISTS cost_feeds (
2367
+ id TEXT PRIMARY KEY,
2368
+ entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2369
+ currency TEXT NOT NULL,
2370
+ monthly_burn_minor BIGINT NOT NULL,
2371
+ as_of TEXT NOT NULL,
2372
+ source TEXT NOT NULL,
2373
+ captured_at TEXT NOT NULL
2374
+ )`,
2375
+ `CREATE INDEX IF NOT EXISTS idx_cost_entity ON cost_feeds(entity_id)`,
2376
+ `CREATE TABLE IF NOT EXISTS sweep_recommendations (
2377
+ id TEXT PRIMARY KEY,
2378
+ from_entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2379
+ to_entity_id TEXT NOT NULL REFERENCES entities(entity_id),
2380
+ currency TEXT NOT NULL,
2381
+ amount_minor BIGINT NOT NULL,
2382
+ rationale TEXT NOT NULL,
2383
+ status TEXT NOT NULL DEFAULT 'recommended',
2384
+ created_at TEXT NOT NULL,
2385
+ updated_at TEXT NOT NULL
2386
+ )`,
2387
+ `CREATE INDEX IF NOT EXISTS idx_sweeps_from ON sweep_recommendations(from_entity_id)`,
2388
+ `CREATE TABLE IF NOT EXISTS audit_log (
2389
+ id BIGSERIAL PRIMARY KEY,
2390
+ entity_id TEXT,
2391
+ actor_id TEXT NOT NULL,
2392
+ action TEXT NOT NULL,
2393
+ detail TEXT NOT NULL,
2394
+ prev_hash TEXT NOT NULL,
2395
+ row_hash TEXT NOT NULL,
2396
+ created_at TEXT NOT NULL
2397
+ )`,
2398
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_prev_hash ON audit_log(prev_hash)`,
2399
+ `CREATE OR REPLACE FUNCTION treasury_audit_immutable() RETURNS trigger AS $$
2400
+ BEGIN RAISE EXCEPTION 'audit_log is append-only'; END; $$ LANGUAGE plpgsql`,
2401
+ `DROP TRIGGER IF EXISTS audit_log_no_mutate ON audit_log`,
2402
+ `CREATE TRIGGER audit_log_no_mutate BEFORE UPDATE OR DELETE ON audit_log
2403
+ FOR EACH ROW EXECUTE FUNCTION treasury_audit_immutable()`
2404
+ ];
2405
+ });
2406
+
2407
+ // src/generated/storage-kit/mode.ts
2408
+ function normalizeStorageMode(value) {
2409
+ const normalized = value.trim().toLowerCase().replace(/-/g, "_");
2410
+ if (normalized === "local")
2411
+ return { mode: "local", deprecatedAlias: null };
2412
+ if (normalized === "cloud")
2413
+ return { mode: "cloud", deprecatedAlias: null };
2414
+ if (DEPRECATED_STORAGE_MODE_ALIASES.includes(normalized)) {
2415
+ return { mode: "cloud", deprecatedAlias: normalized };
2416
+ }
2417
+ throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
2418
+ }
2419
+ function envToken(name) {
2420
+ return name.toUpperCase().replace(/-/g, "_");
2421
+ }
2422
+ function storageEnvKeys(name) {
2423
+ const token = envToken(name);
2424
+ return {
2425
+ modeKeys: [`HASNA_${token}_STORAGE_MODE`, `${token}_STORAGE_MODE`],
2426
+ databaseUrlKeys: [`HASNA_${token}_DATABASE_URL`, `${token}_DATABASE_URL`]
2427
+ };
2428
+ }
2429
+ function firstEnv2(env, keys) {
2430
+ for (const key of keys) {
2431
+ const value = env[key]?.trim();
2432
+ if (value)
2433
+ return { key, value };
2434
+ }
2435
+ return null;
2436
+ }
2437
+ function resolveStorageMode2(name, env = process.env) {
2438
+ const { modeKeys, databaseUrlKeys } = storageEnvKeys(name);
2439
+ const dbHit = firstEnv2(env, databaseUrlKeys);
2440
+ const databaseUrlPresent2 = Boolean(dbHit);
2441
+ const databaseUrlSource = dbHit ? dbHit.key : null;
2442
+ const modeHit = firstEnv2(env, modeKeys);
2443
+ if (!modeHit) {
2444
+ return {
2445
+ mode: "local",
2446
+ source: "default",
2447
+ deprecatedAlias: null,
2448
+ databaseUrlPresent: databaseUrlPresent2,
2449
+ databaseUrlSource,
2450
+ warning: null
2451
+ };
2452
+ }
2453
+ const { mode, deprecatedAlias } = normalizeStorageMode(modeHit.value);
2454
+ const warnings = [];
2455
+ if (deprecatedAlias) {
2456
+ warnings.push(`Deprecated storage mode '${deprecatedAlias}' from ${modeHit.key} is treated as 'cloud'. Set ${modeKeys[0]}=cloud instead.`);
2457
+ }
2458
+ if (mode === "cloud" && !databaseUrlPresent2) {
2459
+ warnings.push(`cloud mode needs ${databaseUrlKeys[0]} (PURE REMOTE: reads and writes go to cloud Postgres).`);
2460
+ }
2461
+ if (modeHit.key !== modeKeys[0]) {
2462
+ warnings.push(`Using alias env ${modeHit.key}; the canonical key is ${modeKeys[0]}.`);
2463
+ }
2464
+ return {
2465
+ mode,
2466
+ source: modeHit.key,
2467
+ deprecatedAlias,
2468
+ databaseUrlPresent: databaseUrlPresent2,
2469
+ databaseUrlSource,
2470
+ warning: warnings.length > 0 ? warnings.join(" ") : null
2471
+ };
2472
+ }
2473
+ function resolveDatabaseUrl2(name, env = process.env) {
2474
+ const { databaseUrlKeys } = storageEnvKeys(name);
2475
+ const hit = firstEnv2(env, databaseUrlKeys);
2476
+ return hit ? hit.value : null;
2477
+ }
2478
+ var DEPRECATED_STORAGE_MODE_ALIASES;
2479
+ var init_mode = __esm(() => {
2480
+ DEPRECATED_STORAGE_MODE_ALIASES = [
2481
+ "remote",
2482
+ "hybrid",
2483
+ "self_hosted"
2484
+ ];
2485
+ });
2486
+
2487
+ // src/generated/storage-kit/tls.ts
2488
+ import { readFileSync as readFileSync2 } from "fs";
2489
+ function sslModeFromConnectionString(connectionString) {
2490
+ const queryStart = connectionString.indexOf("?");
2491
+ const params = new URLSearchParams(queryStart === -1 ? "" : connectionString.slice(queryStart + 1));
2492
+ const sslmode = params.get("sslmode")?.trim().toLowerCase();
2493
+ if (sslmode) {
2494
+ switch (sslmode) {
2495
+ case "disable":
2496
+ case "prefer":
2497
+ case "require":
2498
+ case "verify-ca":
2499
+ case "verify-full":
2500
+ return sslmode;
2501
+ case "allow":
2502
+ return "prefer";
2503
+ default:
2504
+ throw new Error(`Unknown sslmode '${sslmode}' in connection string.`);
2505
+ }
2506
+ }
2507
+ const ssl = params.get("ssl")?.trim().toLowerCase();
2508
+ if (ssl && ["1", "true", "yes", "on", "require"].includes(ssl))
2509
+ return "require";
2510
+ return "disable";
2511
+ }
2512
+ function loadCaBundle(options) {
2513
+ const env = options.env ?? process.env;
2514
+ if (options.ca && options.ca.trim())
2515
+ return options.ca;
2516
+ const path = options.caCertPath ?? env.PGSSLROOTCERT ?? env.NODE_EXTRA_CA_CERTS;
2517
+ if (path && path.trim())
2518
+ return readFileSync2(path.trim(), "utf8");
2519
+ return null;
2520
+ }
2521
+ function resolveTlsConfig(connectionString, options = {}) {
2522
+ const mode = sslModeFromConnectionString(connectionString);
2523
+ if (mode === "disable" || mode === "prefer") {
2524
+ return;
2525
+ }
2526
+ const ca = loadCaBundle(options);
2527
+ if (mode === "require") {
2528
+ return ca ? { rejectUnauthorized: false, ca } : { rejectUnauthorized: false };
2529
+ }
2530
+ if (!ca) {
2531
+ throw new Error(`sslmode=${mode} requires a CA bundle. Set PGSSLROOTCERT (or pass caCertPath/ca) to the ` + `Amazon RDS global bundle: https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem`);
2532
+ }
2533
+ return { rejectUnauthorized: true, ca };
2534
+ }
2535
+ var init_tls = () => {};
2536
+
2537
+ // src/generated/storage-kit/query.ts
2538
+ var exports_query = {};
2539
+ __export(exports_query, {
2540
+ wrapExecutor: () => wrapExecutor,
2541
+ createQueryClient: () => createQueryClient
2542
+ });
2543
+ function wrapExecutor(executor) {
2544
+ return {
2545
+ async query(sql, params) {
2546
+ const result = await executor.query(sql, params);
2547
+ return { rows: result.rows, rowCount: result.rowCount ?? result.rows.length };
2548
+ },
2549
+ async many(sql, params) {
2550
+ const result = await executor.query(sql, params);
2551
+ return result.rows;
2552
+ },
2553
+ async get(sql, params) {
2554
+ const result = await executor.query(sql, params);
2555
+ return result.rows[0] ?? null;
2556
+ },
2557
+ async one(sql, params) {
2558
+ const result = await executor.query(sql, params);
2559
+ if (result.rows.length !== 1) {
2560
+ throw new Error(`Expected exactly one row, got ${result.rows.length}.`);
2561
+ }
2562
+ return result.rows[0];
2563
+ },
2564
+ async execute(sql, params) {
2565
+ await executor.query(sql, params);
2566
+ }
2567
+ };
2568
+ }
2569
+ function createQueryClient(pool) {
2570
+ const base = wrapExecutor(pool);
2571
+ return {
2572
+ ...base,
2573
+ pool,
2574
+ async transaction(fn) {
2575
+ const client = await pool.connect();
2576
+ try {
2577
+ await client.query("BEGIN");
2578
+ const result = await fn(wrapExecutor(client));
2579
+ await client.query("COMMIT");
2580
+ return result;
2581
+ } catch (error) {
2582
+ try {
2583
+ await client.query("ROLLBACK");
2584
+ } catch {}
2585
+ throw error;
2586
+ } finally {
2587
+ client.release();
2588
+ }
2589
+ },
2590
+ async close() {
2591
+ await pool.end();
2592
+ }
2593
+ };
2594
+ }
2595
+
2596
+ // src/generated/storage-kit/pool.ts
2597
+ var exports_pool = {};
2598
+ __export(exports_pool, {
2599
+ createPgPool: () => createPgPool,
2600
+ createCloudPoolFromEnv: () => createCloudPoolFromEnv
2601
+ });
2602
+ import pg from "pg";
2603
+ function createPgPool(options) {
2604
+ const ssl = resolveTlsConfig(options.connectionString, {
2605
+ ...options.ca !== undefined ? { ca: options.ca } : {},
2606
+ ...options.caCertPath !== undefined ? { caCertPath: options.caCertPath } : {},
2607
+ ...options.env !== undefined ? { env: options.env } : {}
2608
+ });
2609
+ const config = { connectionString: options.connectionString };
2610
+ if (ssl !== undefined)
2611
+ config.ssl = ssl;
2612
+ if (options.max !== undefined)
2613
+ config.max = options.max;
2614
+ if (options.idleTimeoutMillis !== undefined)
2615
+ config.idleTimeoutMillis = options.idleTimeoutMillis;
2616
+ if (options.connectionTimeoutMillis !== undefined)
2617
+ config.connectionTimeoutMillis = options.connectionTimeoutMillis;
2618
+ if (options.applicationName !== undefined)
2619
+ config.application_name = options.applicationName;
2620
+ return new pg.Pool(config);
2621
+ }
2622
+ function createCloudPoolFromEnv(appName, options = {}) {
2623
+ const env = options.env ?? process.env;
2624
+ const resolution = resolveStorageMode2(appName, env);
2625
+ if (resolution.mode !== "cloud") {
2626
+ throw new Error(`createCloudPoolFromEnv requires ${appName} storage mode 'cloud', got '${resolution.mode}'. ` + `Set HASNA_${appName.toUpperCase().replace(/-/g, "_")}_STORAGE_MODE=cloud.`);
2627
+ }
2628
+ const connectionString = resolveDatabaseUrl2(appName, env);
2629
+ if (!connectionString) {
2630
+ throw new Error(`cloud mode for ${appName} needs a database URL. Set ` + `HASNA_${appName.toUpperCase().replace(/-/g, "_")}_DATABASE_URL.`);
2631
+ }
2632
+ const pool = createPgPool({
2633
+ connectionString,
2634
+ ...options.ca !== undefined ? { ca: options.ca } : {},
2635
+ ...options.caCertPath !== undefined ? { caCertPath: options.caCertPath } : {},
2636
+ env,
2637
+ ...options.max !== undefined ? { max: options.max } : {},
2638
+ ...options.idleTimeoutMillis !== undefined ? { idleTimeoutMillis: options.idleTimeoutMillis } : {},
2639
+ ...options.connectionTimeoutMillis !== undefined ? { connectionTimeoutMillis: options.connectionTimeoutMillis } : {},
2640
+ ...options.applicationName !== undefined ? { applicationName: options.applicationName } : {}
2641
+ });
2642
+ return {
2643
+ client: createQueryClient(pool),
2644
+ connectionSource: resolution.databaseUrlSource ?? "unknown"
2645
+ };
2646
+ }
2647
+ var init_pool = __esm(() => {
2648
+ init_mode();
2649
+ init_tls();
2650
+ });
2651
+
2652
+ // src/db/database.ts
2653
+ import { Database } from "bun:sqlite";
2654
+ import { mkdirSync as mkdirSync3 } from "fs";
2655
+ import { dirname } from "path";
2656
+
2657
+ class SqliteClient {
2658
+ db;
2659
+ mode = "local";
2660
+ constructor(db) {
2661
+ this.db = db;
2662
+ }
2663
+ async all(sql, params = []) {
2664
+ return this.db.query(sql).all(...params);
2665
+ }
2666
+ async get(sql, params = []) {
2667
+ return this.db.query(sql).get(...params) ?? null;
2668
+ }
2669
+ async run(sql, params = []) {
2670
+ this.db.query(sql).run(...params);
2671
+ }
2672
+ async transaction(fn) {
2673
+ this.db.run("BEGIN");
2674
+ try {
2675
+ const result = await fn(this);
2676
+ this.db.run("COMMIT");
2677
+ return result;
2678
+ } catch (error) {
2679
+ try {
2680
+ this.db.run("ROLLBACK");
2681
+ } catch {}
2682
+ throw error;
2683
+ }
2684
+ }
2685
+ async close() {
2686
+ this.db.close();
2687
+ }
2688
+ raw() {
2689
+ return this.db;
2690
+ }
2691
+ }
2692
+ function toPgSql(sql) {
2693
+ let i = 0;
2694
+ return sql.replace(/\?/g, () => `$${++i}`);
2695
+ }
2696
+
2697
+ class PostgresClient {
2698
+ client;
2699
+ full;
2700
+ mode = "cloud";
2701
+ constructor(client, full) {
2702
+ this.client = client;
2703
+ this.full = full;
2704
+ }
2705
+ async all(sql, params = []) {
2706
+ return this.client.many(toPgSql(sql), params);
2707
+ }
2708
+ async get(sql, params = []) {
2709
+ return this.client.get(toPgSql(sql), params);
2710
+ }
2711
+ async run(sql, params = []) {
2712
+ await this.client.execute(toPgSql(sql), params);
2713
+ }
2714
+ async transaction(fn) {
2715
+ if (!this.full)
2716
+ throw new Error("Nested transaction is not supported.");
2717
+ return this.full.transaction((c) => fn(new PostgresClient(c)));
2718
+ }
2719
+ async close() {
2720
+ await this.full?.close();
2721
+ }
2722
+ }
2723
+ async function openDatabase(opts = {}) {
2724
+ if (_client && !opts.fresh && !opts.path && !opts.mode)
2725
+ return _client;
2726
+ const mode = opts.mode ?? resolveStorageMode();
2727
+ const client = opts.path !== undefined ? openLocal(opts.path) : mode === "cloud" ? await openCloud() : openLocal(resolveLocalPath());
2728
+ if (!opts.path && !opts.fresh && !opts.mode)
2729
+ _client = client;
2730
+ return client;
2731
+ }
2732
+ function resolveLocalPath() {
2733
+ ensureTreasuryAppHome();
2734
+ return resolveDbPath();
2735
+ }
2736
+ function openLocal(path) {
2737
+ if (path !== ":memory:") {
2738
+ mkdirSync3(dirname(path), { recursive: true, mode: 448 });
2739
+ maybeBackupBeforeMigration(path);
2740
+ }
2741
+ const db = new Database(path);
2742
+ db.run("PRAGMA journal_mode = WAL;");
2743
+ db.run("PRAGMA busy_timeout = 5000;");
2744
+ db.run("PRAGMA foreign_keys = ON;");
2745
+ runSqliteMigrations(db);
2746
+ return new SqliteClient(db);
2747
+ }
2748
+ async function openCloud() {
2749
+ const dsn = resolveDatabaseUrl();
2750
+ if (!dsn) {
2751
+ throw new Error("cloud storage mode requires HASNA_TREASURY_DATABASE_URL (or *_FILE). " + "PURE REMOTE refuses to fall back to local/in-memory SQLite.");
2752
+ }
2753
+ if (!/sslmode=verify-full/.test(dsn)) {
2754
+ throw new Error("cloud DSN must use sslmode=verify-full (BUILD-SPEC \xA74.8); sslmode=require is forbidden.");
2755
+ }
2756
+ const { createPgPool: createPgPool2 } = await Promise.resolve().then(() => (init_pool(), exports_pool));
2757
+ const { createQueryClient: createQueryClient2 } = await Promise.resolve().then(() => exports_query);
2758
+ const pool = createPgPool2({ connectionString: dsn, env: process.env, applicationName: "treasury" });
2759
+ const client = createQueryClient2(pool);
2760
+ for (const sql of POSTGRES_MIGRATIONS) {
2761
+ await client.execute(sql);
2762
+ }
2763
+ scrubDatabaseUrl();
2764
+ return new PostgresClient(client, client);
2765
+ }
2766
+ function now() {
2767
+ return new Date().toISOString();
2768
+ }
2769
+ function uuid() {
2770
+ return crypto.randomUUID();
2771
+ }
2772
+ var _client = null;
2773
+ var init_database = __esm(() => {
2774
+ init_config();
2775
+ init_app_home();
2776
+ init_backup();
2777
+ init_schema();
2778
+ });
2779
+
2780
+ // src/db/audit.ts
2781
+ import { createHash } from "crypto";
2782
+ function canonical(fields) {
2783
+ return JSON.stringify([fields.entity_id, fields.actor_id, fields.action, fields.detail, fields.created_at]);
2784
+ }
2785
+ async function appendAudit(db, input) {
2786
+ await db.transaction(async (tx) => {
2787
+ const last = await tx.get("SELECT row_hash FROM audit_log ORDER BY id DESC LIMIT 1");
2788
+ const prev_hash = last?.row_hash ?? GENESIS;
2789
+ const created_at = now();
2790
+ const fields = { entity_id: input.entity_id, actor_id: input.actor_id, action: input.action, detail: input.detail, created_at };
2791
+ const row_hash = createHash("sha256").update(prev_hash + canonical(fields)).digest("hex");
2792
+ await tx.run("INSERT INTO audit_log (entity_id, actor_id, action, detail, prev_hash, row_hash, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [fields.entity_id, fields.actor_id, fields.action, fields.detail, prev_hash, row_hash, created_at]);
2793
+ });
2794
+ }
2795
+ var GENESIS;
2796
+ var init_audit = __esm(() => {
2797
+ init_database();
2798
+ GENESIS = "0".repeat(64);
2799
+ });
2800
+
2801
+ // src/types/index.ts
2802
+ var TreasuryError, EntityNotFoundError, SweepNotFoundError, ValidationError, PermissionDeniedError;
2803
+ var init_types = __esm(() => {
2804
+ TreasuryError = class TreasuryError extends Error {
2805
+ static suggestion = "";
2806
+ code = "INTERNAL_ERROR";
2807
+ constructor(message) {
2808
+ super(message);
2809
+ this.name = new.target.name;
2810
+ }
2811
+ };
2812
+ EntityNotFoundError = class EntityNotFoundError extends TreasuryError {
2813
+ static suggestion = "Use `treasury entities list` to find the correct entity_id.";
2814
+ code = "ENTITY_NOT_FOUND";
2815
+ constructor(id) {
2816
+ super(`Entity not found: ${id}`);
2817
+ }
2818
+ };
2819
+ SweepNotFoundError = class SweepNotFoundError extends TreasuryError {
2820
+ static suggestion = "Use `treasury sweeps list` to find the correct sweep id.";
2821
+ code = "SWEEP_NOT_FOUND";
2822
+ constructor(id) {
2823
+ super(`Sweep recommendation not found: ${id}`);
2824
+ }
2825
+ };
2826
+ ValidationError = class ValidationError extends TreasuryError {
2827
+ static suggestion = "Check the request fields and retry.";
2828
+ code = "VALIDATION_ERROR";
2829
+ constructor(message) {
2830
+ super(message);
2831
+ }
2832
+ };
2833
+ PermissionDeniedError = class PermissionDeniedError extends TreasuryError {
2834
+ static suggestion = "Your credential lacks the required scope or entity access.";
2835
+ code = "PERMISSION_DENIED";
2836
+ constructor(action, resource) {
2837
+ super(`Permission denied for '${action}'${resource ? ` on ${resource}` : ""}.`);
2838
+ }
2839
+ };
2840
+ });
2841
+
2842
+ // src/services/authorization-constants.ts
2843
+ var allActions, rolePermissions;
2844
+ var init_authorization_constants = __esm(() => {
2845
+ allActions = ["read", "write", "recommend", "export", "admin"];
2846
+ rolePermissions = {
2847
+ system: new Set(allActions),
2848
+ owner: new Set(allActions),
2849
+ admin: new Set(allActions),
2850
+ treasurer: new Set(["read", "write", "recommend", "export"]),
2851
+ analyst: new Set(["read", "recommend"]),
2852
+ auditor: new Set(["read", "export"]),
2853
+ integration: new Set(["read", "write"])
2854
+ };
2855
+ });
2856
+
2857
+ // src/services/authorization.ts
2858
+ function allowedEntitySet(context) {
2859
+ const ids = new Set;
2860
+ if (context.entity_id)
2861
+ ids.add(context.entity_id);
2862
+ for (const id of context.entity_ids ?? [])
2863
+ ids.add(id);
2864
+ if (context.org_id)
2865
+ ids.add(context.org_id);
2866
+ for (const id of context.org_ids ?? [])
2867
+ ids.add(id);
2868
+ return ids;
2869
+ }
2870
+ function hasEntityAccess(context, entityId) {
2871
+ if (context.bypass)
2872
+ return true;
2873
+ if (!entityId)
2874
+ return true;
2875
+ return allowedEntitySet(context).has(entityId);
2876
+ }
2877
+ function hasAllEntityAccess(context, entityIds) {
2878
+ return entityIds.every((id) => hasEntityAccess(context, id));
2879
+ }
2880
+ function roleAllows(role, action) {
2881
+ return rolePermissions[role].has(action);
2882
+ }
2883
+ function authorize(action, context, resource = {}) {
2884
+ const principal = context ?? SYSTEM_AUTHORIZATION_CONTEXT;
2885
+ if (!hasEntityAccess(principal, resource.entity_id)) {
2886
+ throw new PermissionDeniedError(action, resource.resource || resource.entity_id);
2887
+ }
2888
+ if (resource.entity_ids && !hasAllEntityAccess(principal, resource.entity_ids)) {
2889
+ throw new PermissionDeniedError(action, resource.resource || "entity-group");
2890
+ }
2891
+ if (principal.bypass || principal.roles.some((role) => roleAllows(role, action))) {
2892
+ return;
2893
+ }
2894
+ throw new PermissionDeniedError(action, resource.resource);
2895
+ }
2896
+ var SYSTEM_AUTHORIZATION_CONTEXT;
2897
+ var init_authorization = __esm(() => {
2898
+ init_types();
2899
+ init_authorization_constants();
2900
+ SYSTEM_AUTHORIZATION_CONTEXT = {
2901
+ actor_id: "system",
2902
+ roles: ["system"],
2903
+ bypass: true
2904
+ };
2905
+ });
2906
+
2907
+ // src/services/context.ts
2908
+ function contextFromPrincipal(db, principal) {
2909
+ return {
2910
+ db,
2911
+ auth: {
2912
+ actor_id: principal.actor_id,
2913
+ roles: principal.roles,
2914
+ ...principal.entity_ids ? { entity_ids: principal.entity_ids } : {},
2915
+ ...principal.bypass ? { bypass: true } : {}
2916
+ },
2917
+ scopes: principal.scopes
2918
+ };
2919
+ }
2920
+ function localOwnerContext(db) {
2921
+ return {
2922
+ db,
2923
+ auth: SYSTEM_AUTHORIZATION_CONTEXT,
2924
+ scopes: ["treasury:read", "treasury:write", "treasury:recommend", "treasury:export", "treasury:admin", "storage:admin"]
2925
+ };
2926
+ }
2927
+ function guard(rc, scope, action, entity_id) {
2928
+ if (!rc.auth.bypass && !rc.scopes.includes(scope)) {
2929
+ throw new PermissionDeniedError(action, `missing scope ${scope}`);
2930
+ }
2931
+ authorize(action, rc.auth, entity_id ? { entity_id } : {});
2932
+ }
2933
+ var init_context = __esm(() => {
2934
+ init_authorization();
2935
+ init_types();
2936
+ });
2937
+
2938
+ // src/services/entities.ts
2939
+ async function createEntity(rc, input) {
2940
+ guard(rc, "treasury:admin", "admin");
2941
+ if (!input.name?.trim())
2942
+ throw new ValidationError("name is required.");
2943
+ if (!/^[A-Z]{3}$/.test(input.base_currency ?? ""))
2944
+ throw new ValidationError("base_currency must be an ISO-4217 code (e.g. USD).");
2945
+ const ts = now();
2946
+ const row = {
2947
+ entity_id: uuid(),
2948
+ entity_slug: input.entity_slug?.trim() || null,
2949
+ name: input.name.trim(),
2950
+ base_currency: input.base_currency,
2951
+ created_at: ts,
2952
+ updated_at: ts
2953
+ };
2954
+ await rc.db.run("INSERT INTO entities (entity_id, entity_slug, name, base_currency, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", [row.entity_id, row.entity_slug, row.name, row.base_currency, row.created_at, row.updated_at]);
2955
+ await appendAudit(rc.db, { entity_id: row.entity_id, actor_id: rc.auth.actor_id, action: "entity.create", detail: `name=${row.name}` });
2956
+ return row;
2957
+ }
2958
+ async function listEntities(rc) {
2959
+ guard(rc, "treasury:read", "read");
2960
+ const rows = await rc.db.all("SELECT * FROM entities ORDER BY name ASC");
2961
+ if (rc.auth.bypass || rc.auth.entity_ids === undefined) {
2962
+ return rc.auth.bypass ? rows : [];
2963
+ }
2964
+ const allowed = new Set(rc.auth.entity_ids);
2965
+ return rows.filter((r) => allowed.has(r.entity_id));
2966
+ }
2967
+ async function getEntity(rc, input) {
2968
+ guard(rc, "treasury:read", "read", input.entity_id);
2969
+ return requireEntity(rc, input.entity_id);
2970
+ }
2971
+ async function requireEntity(rc, entity_id) {
2972
+ const row = await rc.db.get("SELECT * FROM entities WHERE entity_id = ?", [entity_id]);
2973
+ if (!row)
2974
+ throw new EntityNotFoundError(entity_id);
2975
+ return row;
2976
+ }
2977
+ var init_entities = __esm(() => {
2978
+ init_database();
2979
+ init_audit();
2980
+ init_context();
2981
+ init_types();
2982
+ });
2983
+
2984
+ // src/services/fx.ts
2985
+ async function recordFxRate(rc, input) {
2986
+ guard(rc, "treasury:write", "write");
2987
+ if (!/^[A-Z]{3}$/.test(input.base_currency) || !/^[A-Z]{3}$/.test(input.quote_currency)) {
2988
+ throw new ValidationError("base_currency and quote_currency must be ISO-4217 codes.");
2989
+ }
2990
+ if (!(input.rate > 0))
2991
+ throw new ValidationError("rate must be a positive number.");
2992
+ const captured_at = now();
2993
+ const row = {
2994
+ id: uuid(),
2995
+ base_currency: input.base_currency,
2996
+ quote_currency: input.quote_currency,
2997
+ rate: input.rate,
2998
+ as_of: input.as_of ?? captured_at,
2999
+ source: input.source ?? "manual",
3000
+ captured_at
3001
+ };
3002
+ await rc.db.run("INSERT INTO fx_rates (id, base_currency, quote_currency, rate, as_of, source, captured_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [row.id, row.base_currency, row.quote_currency, row.rate, row.as_of, row.source, row.captured_at]);
3003
+ await appendAudit(rc.db, { entity_id: null, actor_id: rc.auth.actor_id, action: "fx.record", detail: `${row.base_currency}/${row.quote_currency}=${row.rate}` });
3004
+ return row;
3005
+ }
3006
+ async function listFxRates(rc) {
3007
+ guard(rc, "treasury:read", "read");
3008
+ return rc.db.all("SELECT * FROM fx_rates ORDER BY captured_at DESC");
3009
+ }
3010
+ async function convertMinor(db, amountMinor, from, to) {
3011
+ if (from === to)
3012
+ return amountMinor;
3013
+ const direct = await db.get("SELECT rate FROM fx_rates WHERE base_currency = ? AND quote_currency = ? ORDER BY captured_at DESC LIMIT 1", [to, from]);
3014
+ if (direct)
3015
+ return Math.round(amountMinor / direct.rate);
3016
+ const inverse = await db.get("SELECT rate FROM fx_rates WHERE base_currency = ? AND quote_currency = ? ORDER BY captured_at DESC LIMIT 1", [from, to]);
3017
+ if (inverse)
3018
+ return Math.round(amountMinor * inverse.rate);
3019
+ throw new ValidationError(`No FX rate available to convert ${from} -> ${to}. Record a rate first.`);
3020
+ }
3021
+ async function fxExposure(rc, input) {
3022
+ guard(rc, "treasury:read", "read");
3023
+ const base = (input.base ?? "USD").toUpperCase();
3024
+ if (!/^[A-Z]{3}$/.test(base))
3025
+ throw new ValidationError("base must be an ISO-4217 code.");
3026
+ const rows = await visibleBalanceSums(rc);
3027
+ const exposures = [];
3028
+ let totalInBase = 0;
3029
+ for (const { currency, total_minor } of rows) {
3030
+ const in_base_minor = await convertMinor(rc.db, total_minor, currency, base);
3031
+ totalInBase += in_base_minor;
3032
+ exposures.push({ currency, total_minor, in_base_minor });
3033
+ }
3034
+ exposures.sort((a, b) => b.in_base_minor - a.in_base_minor);
3035
+ return { base_currency: base, as_of: now(), total_in_base_minor: totalInBase, exposures };
3036
+ }
3037
+ async function visibleBalanceSums(rc) {
3038
+ const { clause, params } = entityFilter(rc);
3039
+ return rc.db.all(`SELECT currency, SUM(amount_minor) AS total_minor FROM balance_snapshots ${clause} GROUP BY currency`, params);
3040
+ }
3041
+ function entityFilter(rc, column = "entity_id") {
3042
+ if (rc.auth.bypass)
3043
+ return { clause: "", params: [] };
3044
+ const ids = rc.auth.entity_ids ?? [];
3045
+ if (ids.length === 0)
3046
+ return { clause: "WHERE 1=0", params: [] };
3047
+ const placeholders = ids.map(() => "?").join(", ");
3048
+ return { clause: `WHERE ${column} IN (${placeholders})`, params: [...ids] };
3049
+ }
3050
+ var init_fx = __esm(() => {
3051
+ init_database();
3052
+ init_audit();
3053
+ init_context();
3054
+ init_types();
3055
+ });
3056
+
3057
+ // src/services/runway.ts
3058
+ var exports_runway = {};
3059
+ __export(exports_runway, {
3060
+ sumBalancesInBase: () => sumBalancesInBase,
3061
+ recordCostFeed: () => recordCostFeed,
3062
+ listCostFeeds: () => listCostFeeds,
3063
+ latestCostFeeds: () => latestCostFeeds,
3064
+ groupRunway: () => groupRunway,
3065
+ entityRunway: () => entityRunway
3066
+ });
3067
+ async function recordCostFeed(rc, input) {
3068
+ guard(rc, "treasury:write", "write", input.entity_id);
3069
+ await requireEntity(rc, input.entity_id);
3070
+ if (!/^[A-Z]{3}$/.test(input.currency))
3071
+ throw new ValidationError("currency must be an ISO-4217 code.");
3072
+ if (!Number.isInteger(input.monthly_burn_minor))
3073
+ throw new ValidationError("monthly_burn_minor must be an integer (minor units).");
3074
+ const captured_at = now();
3075
+ const row = {
3076
+ id: uuid(),
3077
+ entity_id: input.entity_id,
3078
+ currency: input.currency,
3079
+ monthly_burn_minor: input.monthly_burn_minor,
3080
+ as_of: input.as_of ?? captured_at,
3081
+ source: input.source ?? "manual",
3082
+ captured_at
3083
+ };
3084
+ await rc.db.run("INSERT INTO cost_feeds (id, entity_id, currency, monthly_burn_minor, as_of, source, captured_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [row.id, row.entity_id, row.currency, row.monthly_burn_minor, row.as_of, row.source, row.captured_at]);
3085
+ await appendAudit(rc.db, { entity_id: row.entity_id, actor_id: rc.auth.actor_id, action: "cost.record", detail: `${row.currency} ${row.monthly_burn_minor}/mo` });
3086
+ return row;
3087
+ }
3088
+ async function listCostFeeds(rc, input) {
3089
+ guard(rc, "treasury:read", "read", input.entity_id);
3090
+ if (input.entity_id) {
3091
+ return rc.db.all("SELECT * FROM cost_feeds WHERE entity_id = ? ORDER BY captured_at DESC", [input.entity_id]);
3092
+ }
3093
+ const { clause, params } = entityFilter(rc);
3094
+ return rc.db.all(`SELECT * FROM cost_feeds ${clause} ORDER BY captured_at DESC`, params);
3095
+ }
3096
+ async function latestCostFeeds(rc, entity_id) {
3097
+ const rows = entity_id ? await rc.db.all("SELECT * FROM cost_feeds WHERE entity_id = ? ORDER BY captured_at DESC", [entity_id]) : await (async () => {
3098
+ const { clause, params } = entityFilter(rc);
3099
+ return rc.db.all(`SELECT * FROM cost_feeds ${clause} ORDER BY captured_at DESC`, params);
3100
+ })();
3101
+ const seen = new Set;
3102
+ const latest = [];
3103
+ for (const r of rows) {
3104
+ const key = `${r.entity_id}:${r.currency}`;
3105
+ if (seen.has(key))
3106
+ continue;
3107
+ seen.add(key);
3108
+ latest.push(r);
3109
+ }
3110
+ return latest;
3111
+ }
3112
+ async function sumBalancesInBase(rc, base, entity_id) {
3113
+ const rows = entity_id ? await rc.db.all("SELECT currency, SUM(amount_minor) AS total_minor FROM balance_snapshots WHERE entity_id = ? GROUP BY currency", [entity_id]) : await (async () => {
3114
+ const { clause, params } = entityFilter(rc);
3115
+ return rc.db.all(`SELECT currency, SUM(amount_minor) AS total_minor FROM balance_snapshots ${clause} GROUP BY currency`, params);
3116
+ })();
3117
+ let total = 0;
3118
+ for (const r of rows)
3119
+ total += await convertMinor(rc.db, r.total_minor, r.currency, base);
3120
+ return total;
3121
+ }
3122
+ function runwayMonths(cash, burn) {
3123
+ if (burn <= 0)
3124
+ return null;
3125
+ return Math.round(cash / burn * 100) / 100;
3126
+ }
3127
+ async function entityRunway(rc, input) {
3128
+ guard(rc, "treasury:read", "read", input.entity_id);
3129
+ const entity = await requireEntity(rc, input.entity_id);
3130
+ const base = (input.base ?? entity.base_currency).toUpperCase();
3131
+ const cash = await sumBalancesInBase(rc, base, input.entity_id);
3132
+ const feeds = await latestCostFeeds(rc, input.entity_id);
3133
+ let burn = 0;
3134
+ for (const f of feeds)
3135
+ burn += await convertMinor(rc.db, f.monthly_burn_minor, f.currency, base);
3136
+ return {
3137
+ scope: "entity",
3138
+ entity_id: input.entity_id,
3139
+ base_currency: base,
3140
+ cash_in_base_minor: cash,
3141
+ monthly_burn_in_base_minor: burn,
3142
+ runway_months: runwayMonths(cash, burn),
3143
+ as_of: now()
3144
+ };
3145
+ }
3146
+ async function groupRunway(rc, input) {
3147
+ guard(rc, "treasury:read", "read");
3148
+ const base = (input.base ?? "USD").toUpperCase();
3149
+ if (!/^[A-Z]{3}$/.test(base))
3150
+ throw new ValidationError("base must be an ISO-4217 code.");
3151
+ const cash = await sumBalancesInBase(rc, base);
3152
+ const feeds = await latestCostFeeds(rc);
3153
+ let burn = 0;
3154
+ for (const f of feeds)
3155
+ burn += await convertMinor(rc.db, f.monthly_burn_minor, f.currency, base);
3156
+ return {
3157
+ scope: "group",
3158
+ entity_id: null,
3159
+ base_currency: base,
3160
+ cash_in_base_minor: cash,
3161
+ monthly_burn_in_base_minor: burn,
3162
+ runway_months: runwayMonths(cash, burn),
3163
+ as_of: now()
3164
+ };
3165
+ }
3166
+ var init_runway = __esm(() => {
3167
+ init_database();
3168
+ init_audit();
3169
+ init_context();
3170
+ init_entities();
3171
+ init_fx();
3172
+ init_types();
3173
+ });
3174
+
3175
+ // src/cli/dashboard.tsx
3176
+ var exports_dashboard = {};
3177
+ __export(exports_dashboard, {
3178
+ renderDashboard: () => renderDashboard
3179
+ });
3180
+ import { Box, Text } from "ink";
3181
+ import { jsxDEV } from "react/jsx-dev-runtime";
3182
+ function fmtMinor(minor) {
3183
+ return (minor / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
3184
+ }
3185
+ function renderDashboard(report) {
3186
+ return /* @__PURE__ */ jsxDEV(Box, {
3187
+ flexDirection: "column",
3188
+ borderStyle: "round",
3189
+ paddingX: 1,
3190
+ children: [
3191
+ /* @__PURE__ */ jsxDEV(Text, {
3192
+ bold: true,
3193
+ color: "cyan",
3194
+ children: "treasury \u2014 group cash cockpit"
3195
+ }, undefined, false, undefined, this),
3196
+ /* @__PURE__ */ jsxDEV(Text, {
3197
+ children: [
3198
+ "cash: ",
3199
+ fmtMinor(report.cash_in_base_minor),
3200
+ " ",
3201
+ report.base_currency
3202
+ ]
3203
+ }, undefined, true, undefined, this),
3204
+ /* @__PURE__ */ jsxDEV(Text, {
3205
+ children: [
3206
+ "monthly burn: ",
3207
+ fmtMinor(report.monthly_burn_in_base_minor),
3208
+ " ",
3209
+ report.base_currency
3210
+ ]
3211
+ }, undefined, true, undefined, this),
3212
+ /* @__PURE__ */ jsxDEV(Text, {
3213
+ children: [
3214
+ "runway: ",
3215
+ report.runway_months === null ? "\u221E (no burn)" : `${report.runway_months} months`
3216
+ ]
3217
+ }, undefined, true, undefined, this)
3218
+ ]
3219
+ }, undefined, true, undefined, this);
3220
+ }
3221
+ var init_dashboard = () => {};
3222
+
3223
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
3224
+ var import__ = __toESM(require_commander(), 1);
3225
+ var {
3226
+ program,
3227
+ createCommand,
3228
+ createArgument,
3229
+ createOption,
3230
+ CommanderError,
3231
+ InvalidArgumentError,
3232
+ InvalidOptionArgumentError,
3233
+ Command,
3234
+ Argument,
3235
+ Option,
3236
+ Help
3237
+ } = import__.default;
3238
+
3239
+ // src/cli/index.tsx
3240
+ init_config();
3241
+ // package.json
3242
+ var package_default = {
3243
+ name: "@hasna/treasury",
3244
+ version: "0.1.2",
3245
+ description: "Multi-entity cash/treasury cockpit: consolidated balances, FX exposure, per-entity & group runway, sweep/intercompany-funding recommendations, short-horizon cash forecast. CLI + MCP + serve over a Hasna-contract store.",
3246
+ type: "module",
3247
+ main: "dist/index.js",
3248
+ types: "dist/index.d.ts",
3249
+ bin: {
3250
+ treasury: "dist/cli/index.js",
3251
+ "treasury-mcp": "dist/mcp/index.js",
3252
+ "treasury-serve": "dist/server/index.js"
3253
+ },
3254
+ exports: {
3255
+ ".": {
3256
+ types: "./dist/index.d.ts",
3257
+ import: "./dist/index.js"
3258
+ },
3259
+ "./sdk": {
3260
+ types: "./dist/index.d.ts",
3261
+ import: "./dist/index.js"
3262
+ }
3263
+ },
3264
+ files: [
3265
+ "dist",
3266
+ "hasna.contract.json",
3267
+ "openapi.json",
3268
+ "docker-compose.yml",
3269
+ "LICENSE",
3270
+ "README.md"
3271
+ ],
3272
+ scripts: {
3273
+ build: "bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external @modelcontextprotocol/sdk --external hono --external pg && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk --external pg && bun build src/server/index.ts --outdir dist/server --target bun --external hono --external pg && bun build src/index.ts --outdir dist --target bun --external pg && tsc --emitDeclarationOnly --outDir dist",
3274
+ typecheck: "tsc --noEmit",
3275
+ test: "bun test",
3276
+ conformance: "bunx @hasna/contracts@0.4.1 vendor-kit --check && bunx @hasna/contracts@0.4.1 repo-conformance",
3277
+ verify: "bun run typecheck && bun run test && bun run build && bun run conformance",
3278
+ "openapi:generate": "bun run src/cli/index.tsx openapi generate --out openapi.json",
3279
+ "openapi:check": "bun run src/cli/index.tsx openapi check --path openapi.json",
3280
+ "smoke:package": "bun run src/release/package-smoke.ts",
3281
+ "dev:cli": "bun run src/cli/index.tsx",
3282
+ "dev:mcp": "bun run src/mcp/index.ts --http --port 8890",
3283
+ "dev:serve": "bun run src/server/index.ts",
3284
+ postinstall: `node -e "try{const os=require('os'),fs=require('fs'),p=require('path');const b=p.join(os.homedir(),'.hasna','treasury');for(const d of ['','config','data','exports','backups','logs','tmp']) fs.mkdirSync(p.join(b,d),{recursive:true});}catch(e){}"`
3285
+ },
3286
+ publishConfig: {
3287
+ registry: "https://registry.npmjs.org",
3288
+ access: "public"
3289
+ },
3290
+ repository: {
3291
+ type: "git",
3292
+ url: "git+https://github.com/hasna/treasury.git"
3293
+ },
3294
+ homepage: "https://github.com/hasna/treasury#readme",
3295
+ bugs: {
3296
+ url: "https://github.com/hasna/treasury/issues"
3297
+ },
3298
+ engines: {
3299
+ bun: ">=1.1.0"
3300
+ },
3301
+ author: "Andrei Hasna <andrei@hasna.com>",
3302
+ license: "Apache-2.0",
3303
+ dependencies: {
3304
+ "@modelcontextprotocol/sdk": "^1.12.1",
3305
+ hono: "^4.6.0",
3306
+ commander: "^13.1.0",
3307
+ ink: "^5.2.0",
3308
+ react: "^18.3.1",
3309
+ chalk: "^5.4.1",
3310
+ zod: "^3.24.2",
3311
+ pg: "^8.13.1"
3312
+ },
3313
+ devDependencies: {
3314
+ "@hasna/contracts": "0.4.1",
3315
+ "@types/pg": "^8.11.10",
3316
+ "@types/bun": "^1.2.4",
3317
+ "@types/react": "^18.3.18",
3318
+ typescript: "^5.7.3",
3319
+ "bun-types": "1.3.14"
3320
+ }
3321
+ };
3322
+
3323
+ // src/version.ts
3324
+ var APP_VERSION = package_default.version;
3325
+
3326
+ // src/services/registry.ts
3327
+ init_entities();
3328
+
3329
+ // src/services/balances.ts
3330
+ init_database();
3331
+ init_audit();
3332
+ init_context();
3333
+ init_entities();
3334
+ init_fx();
3335
+ init_types();
3336
+ async function recordBalance(rc, input) {
3337
+ guard(rc, "treasury:write", "write", input.entity_id);
3338
+ await requireEntity(rc, input.entity_id);
3339
+ if (!/^[A-Z]{3}$/.test(input.currency))
3340
+ throw new ValidationError("currency must be an ISO-4217 code.");
3341
+ if (!Number.isInteger(input.amount_minor))
3342
+ throw new ValidationError("amount_minor must be an integer (minor units).");
3343
+ if (input.account_kind !== "bank" && input.account_kind !== "wallet")
3344
+ throw new ValidationError("account_kind must be 'bank' or 'wallet'.");
3345
+ const captured_at = now();
3346
+ const row = {
3347
+ id: uuid(),
3348
+ entity_id: input.entity_id,
3349
+ account_ref: input.account_ref,
3350
+ account_kind: input.account_kind,
3351
+ currency: input.currency,
3352
+ amount_minor: input.amount_minor,
3353
+ as_of: input.as_of ?? captured_at,
3354
+ source: input.source ?? "manual",
3355
+ captured_at
3356
+ };
3357
+ await rc.db.run("INSERT INTO balance_snapshots (id, entity_id, account_ref, account_kind, currency, amount_minor, as_of, source, captured_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [row.id, row.entity_id, row.account_ref, row.account_kind, row.currency, row.amount_minor, row.as_of, row.source, row.captured_at]);
3358
+ await appendAudit(rc.db, { entity_id: row.entity_id, actor_id: rc.auth.actor_id, action: "balance.record", detail: `${row.currency} ${row.amount_minor} @ ${row.account_ref}` });
3359
+ return row;
3360
+ }
3361
+ async function listBalances(rc, input) {
3362
+ guard(rc, "treasury:read", "read", input.entity_id);
3363
+ if (input.entity_id) {
3364
+ return rc.db.all("SELECT * FROM balance_snapshots WHERE entity_id = ? ORDER BY captured_at DESC", [input.entity_id]);
3365
+ }
3366
+ const { clause, params } = entityFilter(rc);
3367
+ return rc.db.all(`SELECT * FROM balance_snapshots ${clause} ORDER BY captured_at DESC`, params);
3368
+ }
3369
+ async function consolidatedBalances(rc, input) {
3370
+ guard(rc, "treasury:read", "read");
3371
+ const base = (input.base ?? "USD").toUpperCase();
3372
+ if (!/^[A-Z]{3}$/.test(base))
3373
+ throw new ValidationError("base must be an ISO-4217 code.");
3374
+ const { clause, params } = entityFilter(rc);
3375
+ const rows = await rc.db.all(`SELECT currency, SUM(amount_minor) AS total_minor FROM balance_snapshots ${clause} GROUP BY currency`, params);
3376
+ let total = 0;
3377
+ const by_currency = [];
3378
+ for (const r of rows) {
3379
+ const in_base_minor = await convertMinor(rc.db, r.total_minor, r.currency, base);
3380
+ total += in_base_minor;
3381
+ by_currency.push({ currency: r.currency, total_minor: r.total_minor, in_base_minor });
3382
+ }
3383
+ by_currency.sort((a, b) => b.in_base_minor - a.in_base_minor);
3384
+ return { base_currency: base, as_of: now(), total_in_base_minor: total, by_currency };
3385
+ }
3386
+
3387
+ // src/services/registry.ts
3388
+ init_fx();
3389
+ init_runway();
3390
+
3391
+ // src/services/forecast.ts
3392
+ init_database();
3393
+ init_context();
3394
+ init_runway();
3395
+ init_types();
3396
+ var MAX_HORIZON = 36;
3397
+ function monthLabel(offset) {
3398
+ const d = new Date;
3399
+ d.setUTCDate(1);
3400
+ d.setUTCMonth(d.getUTCMonth() + offset);
3401
+ return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`;
3402
+ }
3403
+ function project(starting, burn, horizon) {
3404
+ const points = [];
3405
+ for (let m = 0;m <= horizon; m++) {
3406
+ points.push({ month_index: m, month: monthLabel(m), projected_cash_in_base_minor: starting - burn * m });
3407
+ }
3408
+ return points;
3409
+ }
3410
+ async function cashForecast(rc, input) {
3411
+ guard(rc, "treasury:read", "read", input.entity_id);
3412
+ const horizon = input.horizon_months ?? 6;
3413
+ if (!Number.isInteger(horizon) || horizon < 1 || horizon > MAX_HORIZON) {
3414
+ throw new ValidationError(`horizon_months must be an integer in 1..${MAX_HORIZON}.`);
3415
+ }
3416
+ const runway = input.entity_id ? await entityRunway(rc, { entity_id: input.entity_id, ...input.base ? { base: input.base } : {} }) : await groupRunway(rc, { ...input.base ? { base: input.base } : {} });
3417
+ return {
3418
+ scope: runway.scope,
3419
+ entity_id: runway.entity_id,
3420
+ base_currency: runway.base_currency,
3421
+ horizon_months: horizon,
3422
+ starting_cash_in_base_minor: runway.cash_in_base_minor,
3423
+ monthly_burn_in_base_minor: runway.monthly_burn_in_base_minor,
3424
+ points: project(runway.cash_in_base_minor, runway.monthly_burn_in_base_minor, horizon),
3425
+ as_of: now()
3426
+ };
3427
+ }
3428
+
3429
+ // src/services/sweeps.ts
3430
+ init_database();
3431
+ init_audit();
3432
+ init_context();
3433
+ init_authorization();
3434
+ init_entities();
3435
+ init_runway();
3436
+ init_fx();
3437
+ init_types();
3438
+ function hydrate(row) {
3439
+ return { ...row, requires_controls_authorization: true };
3440
+ }
3441
+ async function listSweeps(rc, input = {}) {
3442
+ guard(rc, "treasury:read", "read");
3443
+ const from = entityFilter(rc, "from_entity_id");
3444
+ const to = entityFilter(rc, "to_entity_id");
3445
+ let clause = "";
3446
+ const args = [];
3447
+ if (from.clause && to.clause) {
3448
+ clause = `${from.clause} AND ${to.clause.replace(/^WHERE\s+/, "")}`;
3449
+ args.push(...from.params, ...to.params);
3450
+ }
3451
+ let sql = `SELECT * FROM sweep_recommendations ${clause}`;
3452
+ if (input.status) {
3453
+ sql += clause ? " AND status = ?" : "WHERE status = ?";
3454
+ args.push(input.status);
3455
+ }
3456
+ sql += " ORDER BY created_at DESC";
3457
+ const rows = await rc.db.all(sql, args);
3458
+ return rows.map(hydrate);
3459
+ }
3460
+ async function getSweep(rc, input) {
3461
+ guard(rc, "treasury:read", "read");
3462
+ const row = await rc.db.get("SELECT * FROM sweep_recommendations WHERE id = ?", [input.id]);
3463
+ if (!row)
3464
+ throw new SweepNotFoundError(input.id);
3465
+ authorize("read", rc.auth, { entity_ids: [row.from_entity_id, row.to_entity_id] });
3466
+ return hydrate(row);
3467
+ }
3468
+ async function generateSweeps(rc, input) {
3469
+ guard(rc, "treasury:recommend", "recommend");
3470
+ const base = (input.base ?? "USD").toUpperCase();
3471
+ if (!/^[A-Z]{3}$/.test(base))
3472
+ throw new ValidationError("base must be an ISO-4217 code.");
3473
+ const minRunway = input.min_runway_months ?? 3;
3474
+ const healthy = input.healthy_runway_months ?? 12;
3475
+ if (minRunway <= 0 || healthy <= minRunway)
3476
+ throw new ValidationError("healthy_runway_months must be greater than min_runway_months (> 0).");
3477
+ const entities = await listEntities(rc);
3478
+ const needy = [];
3479
+ const donors = [];
3480
+ for (const e of entities) {
3481
+ const r = await entityRunway(rc, { entity_id: e.entity_id, base });
3482
+ const burn = r.monthly_burn_in_base_minor;
3483
+ const cash = r.cash_in_base_minor;
3484
+ if (burn > 0) {
3485
+ const need = Math.max(0, Math.round(minRunway * burn) - cash);
3486
+ const surplus = Math.max(0, cash - Math.round(healthy * burn));
3487
+ if (need > 0)
3488
+ needy.push({ id: e.entity_id, need });
3489
+ else if (surplus > 0)
3490
+ donors.push({ id: e.entity_id, surplus });
3491
+ } else if (cash > 0) {
3492
+ donors.push({ id: e.entity_id, surplus: cash });
3493
+ }
3494
+ }
3495
+ const { clause, params } = entityFilter(rc, "from_entity_id");
3496
+ await rc.db.run(`DELETE FROM sweep_recommendations ${clause}${clause ? " AND" : "WHERE"} status = 'recommended'`, [...params]);
3497
+ needy.sort((a, b) => b.need - a.need);
3498
+ donors.sort((a, b) => b.surplus - a.surplus);
3499
+ const recs = [];
3500
+ let di = 0;
3501
+ for (const n of needy) {
3502
+ let remaining = n.need;
3503
+ while (remaining > 0 && di < donors.length) {
3504
+ const donor = donors[di];
3505
+ if (donor.surplus <= 0) {
3506
+ di++;
3507
+ continue;
3508
+ }
3509
+ const amount = Math.min(remaining, donor.surplus);
3510
+ donor.surplus -= amount;
3511
+ remaining -= amount;
3512
+ const ts = now();
3513
+ const rec = {
3514
+ id: uuid(),
3515
+ from_entity_id: donor.id,
3516
+ to_entity_id: n.id,
3517
+ currency: base,
3518
+ amount_minor: amount,
3519
+ rationale: `Lift ${n.id} toward ${input.min_runway_months ?? 3}mo runway from ${donor.id} surplus.`,
3520
+ status: "recommended",
3521
+ requires_controls_authorization: true,
3522
+ created_at: ts,
3523
+ updated_at: ts
3524
+ };
3525
+ await rc.db.run("INSERT INTO sweep_recommendations (id, from_entity_id, to_entity_id, currency, amount_minor, rationale, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [rec.id, rec.from_entity_id, rec.to_entity_id, rec.currency, rec.amount_minor, rec.rationale, rec.status, rec.created_at, rec.updated_at]);
3526
+ await appendAudit(rc.db, { entity_id: donor.id, actor_id: rc.auth.actor_id, action: "sweep.recommend", detail: `${donor.id}->${n.id} ${base} ${amount}` });
3527
+ recs.push(rec);
3528
+ if (donor.surplus <= 0)
3529
+ di++;
3530
+ }
3531
+ }
3532
+ return recs;
3533
+ }
3534
+ async function updateSweepStatus(rc, input) {
3535
+ guard(rc, "treasury:recommend", "recommend");
3536
+ if (!["recommended", "acknowledged", "dismissed"].includes(input.status)) {
3537
+ throw new ValidationError("status must be one of: recommended, acknowledged, dismissed.");
3538
+ }
3539
+ const row = await rc.db.get("SELECT * FROM sweep_recommendations WHERE id = ?", [input.id]);
3540
+ if (!row)
3541
+ throw new SweepNotFoundError(input.id);
3542
+ authorize("recommend", rc.auth, { entity_ids: [row.from_entity_id, row.to_entity_id] });
3543
+ const updated_at = now();
3544
+ await rc.db.run("UPDATE sweep_recommendations SET status = ?, updated_at = ? WHERE id = ?", [input.status, updated_at, input.id]);
3545
+ await appendAudit(rc.db, { entity_id: row.from_entity_id, actor_id: rc.auth.actor_id, action: "sweep.status", detail: `${input.id} -> ${input.status}` });
3546
+ return hydrate({ ...row, status: input.status, updated_at });
3547
+ }
3548
+
3549
+ // src/services/ingest.ts
3550
+ init_context();
3551
+ init_fx();
3552
+ init_runway();
3553
+ init_entities();
3554
+ async function ingestFromAdapters(rc, adapters) {
3555
+ guard(rc, "treasury:write", "write");
3556
+ const entities = await listEntities(rc);
3557
+ const ids = entities.map((e) => e.entity_id);
3558
+ const rates = await adapters.fx.fetchRates();
3559
+ for (const r of rates) {
3560
+ await recordFxRate(rc, { base_currency: r.base_currency, quote_currency: r.quote_currency, rate: r.rate, as_of: r.as_of, source: adapters.fx.source });
3561
+ }
3562
+ const balances = await adapters.balances.fetchBalances(ids);
3563
+ for (const b of balances) {
3564
+ await recordBalance(rc, { entity_id: b.entity_id, account_ref: b.account_ref, account_kind: b.account_kind, currency: b.currency, amount_minor: b.amount_minor, as_of: b.as_of, source: adapters.balances.source });
3565
+ }
3566
+ const feeds = await adapters.cost.fetchCostFeeds(ids);
3567
+ for (const f of feeds) {
3568
+ await recordCostFeed(rc, { entity_id: f.entity_id, currency: f.currency, monthly_burn_minor: f.monthly_burn_minor, as_of: f.as_of, source: adapters.cost.source });
3569
+ }
3570
+ return {
3571
+ balances_ingested: balances.length,
3572
+ fx_rates_ingested: rates.length,
3573
+ cost_feeds_ingested: feeds.length,
3574
+ sources: { balances: adapters.balances.source, fx: adapters.fx.source, cost: adapters.cost.source }
3575
+ };
3576
+ }
3577
+
3578
+ // src/adapters/fixtures.ts
3579
+ var AS_OF = "2026-07-01T00:00:00.000Z";
3580
+
3581
+ class FixtureBalancesAdapter {
3582
+ rows;
3583
+ source = "fixture:banking";
3584
+ constructor(rows) {
3585
+ this.rows = rows;
3586
+ }
3587
+ async fetchBalances(entityIds) {
3588
+ const set = new Set(entityIds);
3589
+ return this.rows.filter((r) => set.has(r.entity_id));
3590
+ }
3591
+ }
3592
+
3593
+ class FixtureFxAdapter {
3594
+ rows;
3595
+ source = "fixture:fx";
3596
+ constructor(rows) {
3597
+ this.rows = rows;
3598
+ }
3599
+ async fetchRates() {
3600
+ return [...this.rows];
3601
+ }
3602
+ }
3603
+
3604
+ class FixtureCostAdapter {
3605
+ rows;
3606
+ source = "fixture:cost";
3607
+ constructor(rows) {
3608
+ this.rows = rows;
3609
+ }
3610
+ async fetchCostFeeds(entityIds) {
3611
+ const set = new Set(entityIds);
3612
+ return this.rows.filter((r) => set.has(r.entity_id));
3613
+ }
3614
+ }
3615
+ function buildFixtureAdapters(usEntityId, roEntityId) {
3616
+ const balances = [
3617
+ { entity_id: usEntityId, account_ref: "mercury-1010", account_kind: "bank", currency: "USD", amount_minor: 25000000, as_of: AS_OF },
3618
+ { entity_id: usEntityId, account_ref: "wallet-usdc", account_kind: "wallet", currency: "USD", amount_minor: 3000000, as_of: AS_OF },
3619
+ { entity_id: roEntityId, account_ref: "bcr-2020", account_kind: "bank", currency: "EUR", amount_minor: 1500000, as_of: AS_OF }
3620
+ ];
3621
+ const fx = [
3622
+ { base_currency: "USD", quote_currency: "EUR", rate: 0.92, as_of: AS_OF }
3623
+ ];
3624
+ const cost = [
3625
+ { entity_id: usEntityId, currency: "USD", monthly_burn_minor: 1e6, as_of: AS_OF },
3626
+ { entity_id: roEntityId, currency: "EUR", monthly_burn_minor: 2000000, as_of: AS_OF }
3627
+ ];
3628
+ return {
3629
+ balances: new FixtureBalancesAdapter(balances),
3630
+ fx: new FixtureFxAdapter(fx),
3631
+ cost: new FixtureCostAdapter(cost)
3632
+ };
3633
+ }
3634
+
3635
+ // src/services/registry.ts
3636
+ var R = ["minimal", "standard", "full"];
3637
+ var SF = ["standard", "full"];
3638
+ var F = ["full"];
3639
+ var OPS = [
3640
+ {
3641
+ name: "list_entities",
3642
+ description: "List entities the caller is authorized to see.",
3643
+ scope: "treasury:read",
3644
+ action: "read",
3645
+ mutating: false,
3646
+ http: { method: "GET", path: "/v1/entities" },
3647
+ operationId: "listEntities",
3648
+ cli: ["entities", "list"],
3649
+ fields: [],
3650
+ profiles: R,
3651
+ run: (rc) => listEntities(rc)
3652
+ },
3653
+ {
3654
+ name: "get_entity",
3655
+ description: "Get a single entity by entity_id.",
3656
+ scope: "treasury:read",
3657
+ action: "read",
3658
+ mutating: false,
3659
+ http: { method: "GET", path: "/v1/entities/:entity_id" },
3660
+ operationId: "getEntity",
3661
+ cli: ["entities", "get"],
3662
+ fields: [{ name: "entity_id", type: "string", required: true, location: "path" }],
3663
+ profiles: R,
3664
+ run: (rc, i) => getEntity(rc, i)
3665
+ },
3666
+ {
3667
+ name: "create_entity",
3668
+ description: "Create (cache) an entity anchor.",
3669
+ scope: "treasury:admin",
3670
+ action: "admin",
3671
+ mutating: true,
3672
+ http: { method: "POST", path: "/v1/entities" },
3673
+ operationId: "createEntity",
3674
+ cli: ["entities", "create"],
3675
+ fields: [
3676
+ { name: "name", type: "string", required: true, location: "body" },
3677
+ { name: "base_currency", type: "string", required: true, location: "body" },
3678
+ { name: "entity_slug", type: "string", location: "body" }
3679
+ ],
3680
+ profiles: R,
3681
+ run: (rc, i) => createEntity(rc, i)
3682
+ },
3683
+ {
3684
+ name: "list_balances",
3685
+ description: "List cached balance snapshots (optionally for one entity).",
3686
+ scope: "treasury:read",
3687
+ action: "read",
3688
+ mutating: false,
3689
+ http: { method: "GET", path: "/v1/balances" },
3690
+ operationId: "listBalances",
3691
+ cli: ["balances", "list"],
3692
+ fields: [{ name: "entity_id", type: "string", location: "query" }],
3693
+ profiles: R,
3694
+ run: (rc, i) => listBalances(rc, i)
3695
+ },
3696
+ {
3697
+ name: "record_balance",
3698
+ description: "Cache a balance snapshot with provenance.",
3699
+ scope: "treasury:write",
3700
+ action: "write",
3701
+ mutating: true,
3702
+ http: { method: "POST", path: "/v1/balances" },
3703
+ operationId: "recordBalance",
3704
+ cli: ["balances", "record"],
3705
+ fields: [
3706
+ { name: "entity_id", type: "string", required: true, location: "body" },
3707
+ { name: "account_ref", type: "string", required: true, location: "body" },
3708
+ { name: "account_kind", type: "string", required: true, location: "body" },
3709
+ { name: "currency", type: "string", required: true, location: "body" },
3710
+ { name: "amount_minor", type: "int", required: true, location: "body" },
3711
+ { name: "source", type: "string", location: "body" }
3712
+ ],
3713
+ profiles: R,
3714
+ run: (rc, i) => recordBalance(rc, i)
3715
+ },
3716
+ {
3717
+ name: "consolidated_balances",
3718
+ description: "Consolidated balances across visible entities, converted to a base.",
3719
+ scope: "treasury:read",
3720
+ action: "read",
3721
+ mutating: false,
3722
+ http: { method: "GET", path: "/v1/balances/consolidated" },
3723
+ operationId: "consolidatedBalances",
3724
+ cli: ["balances", "consolidated"],
3725
+ fields: [{ name: "base", type: "string", location: "query" }],
3726
+ profiles: R,
3727
+ run: (rc, i) => consolidatedBalances(rc, i)
3728
+ },
3729
+ {
3730
+ name: "list_fx_rates",
3731
+ description: "List cached FX rates.",
3732
+ scope: "treasury:read",
3733
+ action: "read",
3734
+ mutating: false,
3735
+ http: { method: "GET", path: "/v1/fx-rates" },
3736
+ operationId: "listFxRates",
3737
+ cli: ["fx", "list"],
3738
+ fields: [],
3739
+ profiles: SF,
3740
+ run: (rc) => listFxRates(rc)
3741
+ },
3742
+ {
3743
+ name: "record_fx_rate",
3744
+ description: "Cache an FX rate (1 base = rate quote).",
3745
+ scope: "treasury:write",
3746
+ action: "write",
3747
+ mutating: true,
3748
+ http: { method: "POST", path: "/v1/fx-rates" },
3749
+ operationId: "recordFxRate",
3750
+ cli: ["fx", "record"],
3751
+ fields: [
3752
+ { name: "base_currency", type: "string", required: true, location: "body" },
3753
+ { name: "quote_currency", type: "string", required: true, location: "body" },
3754
+ { name: "rate", type: "number", required: true, location: "body" },
3755
+ { name: "source", type: "string", location: "body" }
3756
+ ],
3757
+ profiles: SF,
3758
+ run: (rc, i) => recordFxRate(rc, i)
3759
+ },
3760
+ {
3761
+ name: "fx_exposure",
3762
+ description: "FX exposure across visible balances, converted to a base.",
3763
+ scope: "treasury:read",
3764
+ action: "read",
3765
+ mutating: false,
3766
+ http: { method: "GET", path: "/v1/exposure" },
3767
+ operationId: "fxExposure",
3768
+ cli: ["fx", "exposure"],
3769
+ fields: [{ name: "base", type: "string", location: "query" }],
3770
+ profiles: SF,
3771
+ run: (rc, i) => fxExposure(rc, i)
3772
+ },
3773
+ {
3774
+ name: "record_cost_feed",
3775
+ description: "Record an entity's monthly net cash burn.",
3776
+ scope: "treasury:write",
3777
+ action: "write",
3778
+ mutating: true,
3779
+ http: { method: "POST", path: "/v1/cost-feeds" },
3780
+ operationId: "recordCostFeed",
3781
+ cli: ["cost", "record"],
3782
+ fields: [
3783
+ { name: "entity_id", type: "string", required: true, location: "body" },
3784
+ { name: "currency", type: "string", required: true, location: "body" },
3785
+ { name: "monthly_burn_minor", type: "int", required: true, location: "body" },
3786
+ { name: "source", type: "string", location: "body" }
3787
+ ],
3788
+ profiles: SF,
3789
+ run: (rc, i) => recordCostFeed(rc, i)
3790
+ },
3791
+ {
3792
+ name: "list_cost_feeds",
3793
+ description: "List cached cost feeds (optionally for one entity).",
3794
+ scope: "treasury:read",
3795
+ action: "read",
3796
+ mutating: false,
3797
+ http: { method: "GET", path: "/v1/cost-feeds" },
3798
+ operationId: "listCostFeeds",
3799
+ cli: ["cost", "list"],
3800
+ fields: [{ name: "entity_id", type: "string", location: "query" }],
3801
+ profiles: SF,
3802
+ run: (rc, i) => listCostFeeds(rc, i)
3803
+ },
3804
+ {
3805
+ name: "entity_runway",
3806
+ description: "Runway (months) for a single entity.",
3807
+ scope: "treasury:read",
3808
+ action: "read",
3809
+ mutating: false,
3810
+ http: { method: "GET", path: "/v1/runway/:entity_id" },
3811
+ operationId: "entityRunway",
3812
+ cli: ["runway", "entity"],
3813
+ fields: [
3814
+ { name: "entity_id", type: "string", required: true, location: "path" },
3815
+ { name: "base", type: "string", location: "query" }
3816
+ ],
3817
+ profiles: R,
3818
+ run: (rc, i) => entityRunway(rc, i)
3819
+ },
3820
+ {
3821
+ name: "group_runway",
3822
+ description: "Consolidated group runway (months).",
3823
+ scope: "treasury:read",
3824
+ action: "read",
3825
+ mutating: false,
3826
+ http: { method: "GET", path: "/v1/runway" },
3827
+ operationId: "groupRunway",
3828
+ cli: ["runway", "group"],
3829
+ fields: [{ name: "base", type: "string", location: "query" }],
3830
+ profiles: R,
3831
+ run: (rc, i) => groupRunway(rc, i)
3832
+ },
3833
+ {
3834
+ name: "cash_forecast",
3835
+ description: "Short-horizon cash projection (entity or group).",
3836
+ scope: "treasury:read",
3837
+ action: "read",
3838
+ mutating: false,
3839
+ http: { method: "GET", path: "/v1/forecast" },
3840
+ operationId: "cashForecast",
3841
+ cli: ["forecast", "run"],
3842
+ fields: [
3843
+ { name: "entity_id", type: "string", location: "query" },
3844
+ { name: "base", type: "string", location: "query" },
3845
+ { name: "horizon_months", type: "int", location: "query" }
3846
+ ],
3847
+ profiles: SF,
3848
+ run: (rc, i) => cashForecast(rc, i)
3849
+ },
3850
+ {
3851
+ name: "list_sweeps",
3852
+ description: "List sweep / intercompany-funding recommendations.",
3853
+ scope: "treasury:read",
3854
+ action: "read",
3855
+ mutating: false,
3856
+ http: { method: "GET", path: "/v1/sweeps" },
3857
+ operationId: "listSweeps",
3858
+ cli: ["sweeps", "list"],
3859
+ fields: [{ name: "status", type: "string", location: "query" }],
3860
+ profiles: SF,
3861
+ run: (rc, i) => listSweeps(rc, i)
3862
+ },
3863
+ {
3864
+ name: "get_sweep",
3865
+ description: "Get a single sweep recommendation.",
3866
+ scope: "treasury:read",
3867
+ action: "read",
3868
+ mutating: false,
3869
+ http: { method: "GET", path: "/v1/sweeps/:id" },
3870
+ operationId: "getSweep",
3871
+ cli: ["sweeps", "get"],
3872
+ fields: [{ name: "id", type: "string", required: true, location: "path" }],
3873
+ profiles: SF,
3874
+ run: (rc, i) => getSweep(rc, i)
3875
+ },
3876
+ {
3877
+ name: "generate_sweeps",
3878
+ description: "Generate intercompany-funding recommendations (advisory only).",
3879
+ scope: "treasury:recommend",
3880
+ action: "recommend",
3881
+ mutating: true,
3882
+ http: { method: "POST", path: "/v1/sweeps/generate" },
3883
+ operationId: "generateSweeps",
3884
+ cli: ["sweeps", "generate"],
3885
+ fields: [
3886
+ { name: "base", type: "string", location: "body" },
3887
+ { name: "min_runway_months", type: "number", location: "body" },
3888
+ { name: "healthy_runway_months", type: "number", location: "body" }
3889
+ ],
3890
+ profiles: F,
3891
+ run: (rc, i) => generateSweeps(rc, i)
3892
+ },
3893
+ {
3894
+ name: "update_sweep_status",
3895
+ description: "Acknowledge or dismiss a sweep recommendation (advisory only).",
3896
+ scope: "treasury:recommend",
3897
+ action: "recommend",
3898
+ mutating: true,
3899
+ http: { method: "PATCH", path: "/v1/sweeps/:id" },
3900
+ operationId: "updateSweepStatus",
3901
+ cli: ["sweeps", "update"],
3902
+ fields: [
3903
+ { name: "id", type: "string", required: true, location: "path" },
3904
+ { name: "status", type: "string", required: true, location: "body" }
3905
+ ],
3906
+ profiles: F,
3907
+ run: (rc, i) => updateSweepStatus(rc, i)
3908
+ },
3909
+ {
3910
+ name: "ingest_fixtures",
3911
+ description: "Ingest balances/FX/cost from the v0 fixture adapters for two demo entities.",
3912
+ scope: "treasury:write",
3913
+ action: "write",
3914
+ mutating: true,
3915
+ http: { method: "POST", path: "/v1/ingest" },
3916
+ operationId: "ingestFixtures",
3917
+ cli: ["ingest", "fixtures"],
3918
+ fields: [
3919
+ { name: "us_entity_id", type: "string", required: true, location: "body" },
3920
+ { name: "ro_entity_id", type: "string", required: true, location: "body" }
3921
+ ],
3922
+ profiles: F,
3923
+ run: (rc, i) => {
3924
+ const input = i;
3925
+ return ingestFromAdapters(rc, buildFixtureAdapters(input.us_entity_id, input.ro_entity_id));
3926
+ }
3927
+ }
3928
+ ];
3929
+ function coerceField(field, raw) {
3930
+ if (raw === undefined || raw === null || raw === "")
3931
+ return;
3932
+ switch (field.type) {
3933
+ case "int": {
3934
+ const n = typeof raw === "number" ? raw : Number.parseInt(String(raw), 10);
3935
+ return Number.isFinite(n) ? n : raw;
3936
+ }
3937
+ case "number": {
3938
+ const n = typeof raw === "number" ? raw : Number.parseFloat(String(raw));
3939
+ return Number.isFinite(n) ? n : raw;
3940
+ }
3941
+ case "bool":
3942
+ return raw === true || raw === "true" || raw === "1";
3943
+ default:
3944
+ return String(raw);
3945
+ }
3946
+ }
3947
+
3948
+ // src/cli/context.ts
3949
+ init_database();
3950
+ init_context();
3951
+
3952
+ // src/server/auth.ts
3953
+ init_authorization();
3954
+ import { Buffer as Buffer2 } from "buffer";
3955
+ import { timingSafeEqual } from "crypto";
3956
+
3957
+ // src/server/auth-constants.ts
3958
+ var apiScopes = [
3959
+ "treasury:read",
3960
+ "treasury:write",
3961
+ "treasury:recommend",
3962
+ "treasury:export",
3963
+ "treasury:admin",
3964
+ "storage:admin"
3965
+ ];
3966
+ var allScopes = [...apiScopes];
3967
+ var AUTH_CONSTANTS = {
3968
+ apiScopes,
3969
+ knownRoles: ["system", "owner", "admin", "treasurer", "analyst", "auditor", "integration"],
3970
+ roleScopes: {
3971
+ system: allScopes,
3972
+ owner: allScopes,
3973
+ admin: allScopes,
3974
+ treasurer: ["treasury:read", "treasury:write", "treasury:recommend", "treasury:export"],
3975
+ analyst: ["treasury:read", "treasury:recommend"],
3976
+ auditor: ["treasury:read", "treasury:export"],
3977
+ integration: ["treasury:read", "treasury:write"]
3978
+ },
3979
+ actionScope: {
3980
+ read: "treasury:read",
3981
+ write: "treasury:write",
3982
+ recommend: "treasury:recommend",
3983
+ export: "treasury:export",
3984
+ admin: "treasury:admin"
3985
+ },
3986
+ defaultAction: "treasury:admin",
3987
+ env: {
3988
+ apiKey: ["HASNA_TREASURY_API_KEY", "TREASURY_API_KEY"],
3989
+ credentials: ["HASNA_TREASURY_API_CREDENTIALS", "TREASURY_API_CREDENTIALS"]
3990
+ }
3991
+ };
3992
+
3993
+ // src/server/auth.ts
3994
+ var allScopes2 = [...AUTH_CONSTANTS.apiScopes];
3995
+ var knownScopes = new Set(allScopes2);
3996
+ var knownRoles = new Set(AUTH_CONSTANTS.knownRoles);
3997
+ var roleScopes = AUTH_CONSTANTS.roleScopes;
3998
+ var actionScope = AUTH_CONSTANTS.actionScope;
3999
+ function scopesForRoles(roles) {
4000
+ return Array.from(new Set(roles.flatMap((role) => roleScopes[role] || [])));
4001
+ }
4002
+ function legacyApiKey() {
4003
+ for (const name of AUTH_CONSTANTS.env.apiKey) {
4004
+ const value = process.env[name]?.trim();
4005
+ if (value)
4006
+ return value;
4007
+ }
4008
+ return;
4009
+ }
4010
+ function credentialsRaw() {
4011
+ for (const name of AUTH_CONSTANTS.env.credentials) {
4012
+ const value = process.env[name];
4013
+ if (value)
4014
+ return value;
4015
+ }
4016
+ return;
4017
+ }
4018
+ function configuredApiCredentials() {
4019
+ const raw = credentialsRaw();
4020
+ if (!raw)
4021
+ return [];
4022
+ let parsed;
4023
+ try {
4024
+ parsed = JSON.parse(raw);
4025
+ } catch {
4026
+ return [];
4027
+ }
4028
+ const list = Array.isArray(parsed) ? parsed : [parsed];
4029
+ return list.filter((cred) => Boolean((cred.token || cred.key) && cred.id));
4030
+ }
4031
+ function credentialEntityIds(credential) {
4032
+ const ids = new Set;
4033
+ if (credential.entity_id)
4034
+ ids.add(credential.entity_id);
4035
+ for (const id of credential.entity_ids ?? [])
4036
+ ids.add(id);
4037
+ if (credential.org_id)
4038
+ ids.add(credential.org_id);
4039
+ for (const id of credential.org_ids ?? [])
4040
+ ids.add(id);
4041
+ return ids.size > 0 ? [...ids] : undefined;
4042
+ }
4043
+ function authenticateToken(token) {
4044
+ if (!token)
4045
+ return null;
4046
+ const legacy = legacyApiKey();
4047
+ if (legacy && safeEqual(token, legacy)) {
4048
+ return {
4049
+ actor_id: "legacy-api-key",
4050
+ credential_id: "legacy-api-key",
4051
+ credential_type: "api_key",
4052
+ roles: ["owner"],
4053
+ scopes: allScopes2
4054
+ };
4055
+ }
4056
+ for (const credential of configuredApiCredentials()) {
4057
+ const secret = credential.token || credential.key || "";
4058
+ if (!safeEqual(token, secret) || credential.revoked || isExpired(credential.expires_at))
4059
+ continue;
4060
+ const roles = normalizeRoles(credential.roles);
4061
+ const scopes = normalizeScopes(credential.scopes) || scopesForRoles(roles);
4062
+ const entity_ids = credentialEntityIds(credential);
4063
+ return {
4064
+ actor_id: credential.actor_id || `${credential.type || "api_key"}:${credential.id}`,
4065
+ credential_id: credential.id,
4066
+ credential_type: credential.type || "api_key",
4067
+ roles,
4068
+ scopes,
4069
+ ...entity_ids ? { entity_ids } : {}
4070
+ };
4071
+ }
4072
+ const verify = AUTH_CONSTANTS.verifyToken;
4073
+ if (verify && token.split(".").length === 3) {
4074
+ try {
4075
+ const verified = verify(token);
4076
+ return {
4077
+ actor_id: verified.identity_id,
4078
+ credential_id: verified.jti,
4079
+ credential_type: "session",
4080
+ roles: [],
4081
+ scopes: normalizeScopes(verified.scopes) ?? [],
4082
+ ...verified.entity_ids ? { entity_ids: verified.entity_ids } : {}
4083
+ };
4084
+ } catch {
4085
+ return null;
4086
+ }
4087
+ }
4088
+ return null;
4089
+ }
4090
+ function normalizeRoles(roles = ["integration"]) {
4091
+ const normalized = roles.filter((role) => knownRoles.has(role));
4092
+ return normalized.length > 0 ? Array.from(new Set(normalized)) : ["integration"];
4093
+ }
4094
+ function normalizeScopes(scopes) {
4095
+ if (!scopes)
4096
+ return null;
4097
+ return Array.from(new Set(scopes.filter((scope) => knownScopes.has(scope))));
4098
+ }
4099
+ function safeEqual(a, b) {
4100
+ const left = Buffer2.from(a);
4101
+ const right = Buffer2.from(b);
4102
+ return left.length === right.length && timingSafeEqual(left, right);
4103
+ }
4104
+ function isExpired(expiresAt) {
4105
+ return Boolean(expiresAt && Date.parse(expiresAt) <= Date.now());
4106
+ }
4107
+
4108
+ // src/cli/context.ts
4109
+ async function buildCliContext(token) {
4110
+ const db = await openDatabase();
4111
+ const bearer = token || process.env["TREASURY_API_TOKEN"];
4112
+ if (bearer) {
4113
+ const principal = authenticateToken(bearer);
4114
+ if (!principal)
4115
+ throw new Error("Invalid TREASURY_API_TOKEN.");
4116
+ return contextFromPrincipal(db, principal);
4117
+ }
4118
+ return localOwnerContext(db);
4119
+ }
4120
+
4121
+ // src/core/errors.ts
4122
+ function normalizeError(error) {
4123
+ if (error instanceof Error) {
4124
+ const code = "code" in error && typeof error.code === "string" ? error.code : "INTERNAL_ERROR";
4125
+ const ctor = error.constructor;
4126
+ const suggestion = typeof ctor.suggestion === "string" ? ctor.suggestion : "";
4127
+ return { code, message: error.message, suggestion };
4128
+ }
4129
+ return { code: "UNKNOWN_ERROR", message: String(error), suggestion: "An unexpected error occurred." };
4130
+ }
4131
+
4132
+ // src/cli/namespaces.ts
4133
+ function assembleInput(op, opts) {
4134
+ const input = {};
4135
+ for (const f of op.fields) {
4136
+ const v = coerceField(f, opts[f.name]);
4137
+ if (v !== undefined)
4138
+ input[f.name] = v;
4139
+ }
4140
+ return input;
4141
+ }
4142
+ function emit(value, json) {
4143
+ if (json) {
4144
+ console.log(JSON.stringify(value));
4145
+ } else {
4146
+ console.log(JSON.stringify(value, null, 2));
4147
+ }
4148
+ }
4149
+ function registerOpCommands(program2, emitOpts) {
4150
+ const groups = new Map;
4151
+ for (const op of OPS) {
4152
+ const [group, sub] = op.cli;
4153
+ if (!group || !sub)
4154
+ continue;
4155
+ let groupCmd = groups.get(group);
4156
+ if (!groupCmd) {
4157
+ groupCmd = program2.command(group).description(`${group} operations`);
4158
+ groups.set(group, groupCmd);
4159
+ }
4160
+ const cmd = groupCmd.command(sub).description(op.description);
4161
+ for (const f of op.fields) {
4162
+ const flag = `--${f.name} <value>`;
4163
+ if (f.required)
4164
+ cmd.requiredOption(flag, f.description ?? f.name);
4165
+ else
4166
+ cmd.option(flag, f.description ?? f.name);
4167
+ }
4168
+ cmd.option("--token <token>", "Act as a scoped API credential");
4169
+ cmd.action(async (opts) => {
4170
+ try {
4171
+ const rc = await buildCliContext(opts["token"]);
4172
+ const result = await op.run(rc, assembleInput(op, opts));
4173
+ emit(result, emitOpts.json());
4174
+ } catch (error) {
4175
+ emit(normalizeError(error), emitOpts.json());
4176
+ process.exitCode = 1;
4177
+ }
4178
+ });
4179
+ }
4180
+ }
4181
+
4182
+ // src/services/storage.ts
4183
+ init_context();
4184
+ init_audit();
4185
+ init_database();
4186
+ init_config();
4187
+ async function storageStatus(rc) {
4188
+ guard(rc, "treasury:read", "read");
4189
+ const mode = resolveStorageMode();
4190
+ const migrations = await rc.db.get("SELECT COUNT(*) AS c FROM schema_migrations");
4191
+ let remoteReachable = false;
4192
+ if (mode === "cloud") {
4193
+ try {
4194
+ const probe = await rc.db.get("SELECT 1 AS ok");
4195
+ remoteReachable = probe?.ok === 1;
4196
+ } catch {
4197
+ remoteReachable = false;
4198
+ }
4199
+ }
4200
+ return {
4201
+ mode,
4202
+ dsn_present: databaseUrlPresent(),
4203
+ sqlite_path: resolveDbPath(),
4204
+ migrations_applied: Number(migrations?.c ?? 0),
4205
+ remote_reachable: remoteReachable
4206
+ };
4207
+ }
4208
+
4209
+ // src/db/migration-plan.ts
4210
+ var CURRENT_MIGRATION_PLAN = {
4211
+ version: "2026-07-06",
4212
+ current_schema_id: "0000-baseline",
4213
+ entries: [
4214
+ {
4215
+ id: "0000-baseline",
4216
+ name: "baseline treasury schema",
4217
+ status: "applied",
4218
+ description: "entities, balance_snapshots, fx_rates, cost_feeds, sweep_recommendations, and the append-only hash-chained audit_log with immutability triggers."
4219
+ }
4220
+ ]
4221
+ };
4222
+ function getCurrentMigrationPlan() {
4223
+ return CURRENT_MIGRATION_PLAN;
4224
+ }
4225
+
4226
+ // src/api/index.ts
4227
+ function jsonType(f) {
4228
+ if (f.type === "int")
4229
+ return "integer";
4230
+ if (f.type === "number")
4231
+ return "number";
4232
+ if (f.type === "bool")
4233
+ return "boolean";
4234
+ return "string";
4235
+ }
4236
+ function toOpenApiPath(path) {
4237
+ return path.replace(/:([A-Za-z_]+)/g, "{$1}");
4238
+ }
4239
+ function operationFor(op) {
4240
+ const params = op.fields.filter((f) => f.location === "path" || f.location === "query").map((f) => ({ name: f.name, in: f.location, required: Boolean(f.required), schema: { type: jsonType(f) } }));
4241
+ const bodyFields = op.fields.filter((f) => f.location === "body");
4242
+ const operation = {
4243
+ operationId: op.operationId,
4244
+ summary: op.description,
4245
+ responses: { "200": { description: "OK" }, "4XX": { description: "Error envelope { code, message, suggestion }" } }
4246
+ };
4247
+ if (params.length > 0)
4248
+ operation.parameters = params;
4249
+ if (bodyFields.length > 0) {
4250
+ operation.requestBody = {
4251
+ required: bodyFields.some((f) => f.required),
4252
+ content: {
4253
+ "application/json": {
4254
+ schema: {
4255
+ type: "object",
4256
+ required: bodyFields.filter((f) => f.required).map((f) => f.name),
4257
+ properties: Object.fromEntries(bodyFields.map((f) => [f.name, { type: jsonType(f) }]))
4258
+ }
4259
+ }
4260
+ }
4261
+ };
4262
+ }
4263
+ return operation;
4264
+ }
4265
+ function openApiDocument() {
4266
+ const paths = {
4267
+ "/health": { get: { operationId: "getHealth", summary: "Liveness", responses: { "200": { description: "OK" } } } },
4268
+ "/ready": { get: { operationId: "getReady", summary: "Readiness", responses: { "200": { description: "OK" } } } },
4269
+ "/version": { get: { operationId: "getVersion", summary: "Version", responses: { "200": { description: "OK" } } } }
4270
+ };
4271
+ for (const op of OPS) {
4272
+ const p = toOpenApiPath(op.http.path);
4273
+ paths[p] = paths[p] ?? {};
4274
+ paths[p][op.http.method.toLowerCase()] = operationFor(op);
4275
+ }
4276
+ return {
4277
+ openapi: "3.1.0",
4278
+ info: {
4279
+ title: "@hasna/treasury API",
4280
+ version: APP_VERSION,
4281
+ description: "Multi-entity cash/treasury cockpit \u2014 consolidated balances, FX exposure, runway, forecast, and advisory sweep recommendations."
4282
+ },
4283
+ paths
4284
+ };
4285
+ }
4286
+ var openApiDoc = openApiDocument();
4287
+
4288
+ // src/cli/index.tsx
4289
+ var program2 = new Command;
4290
+ var jsonMode = false;
4291
+ function emit2(value) {
4292
+ console.log(jsonMode ? JSON.stringify(value) : JSON.stringify(value, null, 2));
4293
+ }
4294
+ program2.name(APP_NAME).description("Multi-entity cash/treasury cockpit: balances, FX exposure, runway, forecast, advisory sweeps.").version(APP_VERSION).option("--json", "Emit machine-readable JSON").hook("preAction", () => {
4295
+ jsonMode = Boolean(program2.opts().json);
4296
+ });
4297
+ registerOpCommands(program2, { json: () => jsonMode });
4298
+ program2.command("doctor").description("Show storage mode, DB path, migration plan, and redacted storage status").action(async () => {
4299
+ try {
4300
+ const rc = await buildCliContext();
4301
+ emit2({
4302
+ app: APP_NAME,
4303
+ version: APP_VERSION,
4304
+ mode: resolveStorageMode(),
4305
+ db_path: resolveDbPath(),
4306
+ migration_plan: getCurrentMigrationPlan(),
4307
+ storage_status: await storageStatus(rc)
4308
+ });
4309
+ } catch (error) {
4310
+ emit2(normalizeError(error));
4311
+ process.exitCode = 1;
4312
+ }
4313
+ });
4314
+ var openapi = program2.command("openapi").description("OpenAPI document operations");
4315
+ openapi.command("generate").description("Write the OpenAPI document to a file").option("--out <path>", "Output path", "openapi.json").action(async (opts) => {
4316
+ const { writeFileSync } = await import("fs");
4317
+ writeFileSync(opts.out, `${JSON.stringify(openApiDocument(), null, 2)}
4318
+ `);
4319
+ emit2({ ok: true, out: opts.out });
4320
+ });
4321
+ openapi.command("check").description("Verify the checked-in OpenAPI document matches the generated one").option("--path <path>", "Path to compare", "openapi.json").action(async (opts) => {
4322
+ const { readFileSync: readFileSync3 } = await import("fs");
4323
+ const onDisk = JSON.stringify(JSON.parse(readFileSync3(opts.path, "utf8")));
4324
+ const current = JSON.stringify(openApiDocument());
4325
+ if (onDisk !== current) {
4326
+ emit2({ ok: false, message: "openapi.json is stale; run `treasury openapi generate`." });
4327
+ process.exitCode = 1;
4328
+ return;
4329
+ }
4330
+ emit2({ ok: true });
4331
+ });
4332
+ program2.command("dashboard").description("Render an Ink cockpit summary (group runway) \u2014 interactive").option("--base <currency>", "Reporting base currency", "USD").action(async (opts) => {
4333
+ const rc = await buildCliContext();
4334
+ const { groupRunway: groupRunway2 } = await Promise.resolve().then(() => (init_runway(), exports_runway));
4335
+ const report = await groupRunway2(rc, { base: opts.base });
4336
+ if (jsonMode || !process.stdout.isTTY) {
4337
+ emit2(report);
4338
+ return;
4339
+ }
4340
+ const { render } = await import("ink");
4341
+ const { renderDashboard: renderDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), exports_dashboard));
4342
+ const { waitUntilExit } = render(renderDashboard2(report));
4343
+ await waitUntilExit();
4344
+ });
4345
+ async function main() {
4346
+ await program2.parseAsync(process.argv);
4347
+ }
4348
+ if (import.meta.main) {
4349
+ main().catch((error) => {
4350
+ console.error(error instanceof Error ? error.message : String(error));
4351
+ process.exit(1);
4352
+ });
4353
+ }