@hasna/skills 0.9.2 → 0.9.4

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.
@@ -0,0 +1,4913 @@
1
+ // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __require = import.meta.require;
34
+
35
+ // node_modules/commander/lib/error.js
36
+ var require_error = __commonJS((exports) => {
37
+ class CommanderError extends Error {
38
+ constructor(exitCode, code, message) {
39
+ super(message);
40
+ Error.captureStackTrace(this, this.constructor);
41
+ this.name = this.constructor.name;
42
+ this.code = code;
43
+ this.exitCode = exitCode;
44
+ this.nestedError = undefined;
45
+ }
46
+ }
47
+
48
+ class InvalidArgumentError extends CommanderError {
49
+ constructor(message) {
50
+ super(1, "commander.invalidArgument", message);
51
+ Error.captureStackTrace(this, this.constructor);
52
+ this.name = this.constructor.name;
53
+ }
54
+ }
55
+ exports.CommanderError = CommanderError;
56
+ exports.InvalidArgumentError = InvalidArgumentError;
57
+ });
58
+
59
+ // node_modules/commander/lib/argument.js
60
+ var require_argument = __commonJS((exports) => {
61
+ var { InvalidArgumentError } = require_error();
62
+
63
+ class Argument {
64
+ constructor(name, description) {
65
+ this.description = description || "";
66
+ this.variadic = false;
67
+ this.parseArg = undefined;
68
+ this.defaultValue = undefined;
69
+ this.defaultValueDescription = undefined;
70
+ this.argChoices = undefined;
71
+ switch (name[0]) {
72
+ case "<":
73
+ this.required = true;
74
+ this._name = name.slice(1, -1);
75
+ break;
76
+ case "[":
77
+ this.required = false;
78
+ this._name = name.slice(1, -1);
79
+ break;
80
+ default:
81
+ this.required = true;
82
+ this._name = name;
83
+ break;
84
+ }
85
+ if (this._name.length > 3 && this._name.slice(-3) === "...") {
86
+ this.variadic = true;
87
+ this._name = this._name.slice(0, -3);
88
+ }
89
+ }
90
+ name() {
91
+ return this._name;
92
+ }
93
+ _concatValue(value, previous) {
94
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
95
+ return [value];
96
+ }
97
+ return previous.concat(value);
98
+ }
99
+ default(value, description) {
100
+ this.defaultValue = value;
101
+ this.defaultValueDescription = description;
102
+ return this;
103
+ }
104
+ argParser(fn) {
105
+ this.parseArg = fn;
106
+ return this;
107
+ }
108
+ choices(values) {
109
+ this.argChoices = values.slice();
110
+ this.parseArg = (arg, previous) => {
111
+ if (!this.argChoices.includes(arg)) {
112
+ throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
113
+ }
114
+ if (this.variadic) {
115
+ return this._concatValue(arg, previous);
116
+ }
117
+ return arg;
118
+ };
119
+ return this;
120
+ }
121
+ argRequired() {
122
+ this.required = true;
123
+ return this;
124
+ }
125
+ argOptional() {
126
+ this.required = false;
127
+ return this;
128
+ }
129
+ }
130
+ function humanReadableArgName(arg) {
131
+ const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
132
+ return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
133
+ }
134
+ exports.Argument = Argument;
135
+ exports.humanReadableArgName = humanReadableArgName;
136
+ });
137
+
138
+ // node_modules/commander/lib/help.js
139
+ var require_help = __commonJS((exports) => {
140
+ var { humanReadableArgName } = require_argument();
141
+
142
+ class Help {
143
+ constructor() {
144
+ this.helpWidth = undefined;
145
+ this.sortSubcommands = false;
146
+ this.sortOptions = false;
147
+ this.showGlobalOptions = false;
148
+ }
149
+ visibleCommands(cmd) {
150
+ const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden);
151
+ const helpCommand = cmd._getHelpCommand();
152
+ if (helpCommand && !helpCommand._hidden) {
153
+ visibleCommands.push(helpCommand);
154
+ }
155
+ if (this.sortSubcommands) {
156
+ visibleCommands.sort((a, b) => {
157
+ return a.name().localeCompare(b.name());
158
+ });
159
+ }
160
+ return visibleCommands;
161
+ }
162
+ compareOptions(a, b) {
163
+ const getSortKey = (option) => {
164
+ return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, "");
165
+ };
166
+ return getSortKey(a).localeCompare(getSortKey(b));
167
+ }
168
+ visibleOptions(cmd) {
169
+ const visibleOptions = cmd.options.filter((option) => !option.hidden);
170
+ const helpOption = cmd._getHelpOption();
171
+ if (helpOption && !helpOption.hidden) {
172
+ const removeShort = helpOption.short && cmd._findOption(helpOption.short);
173
+ const removeLong = helpOption.long && cmd._findOption(helpOption.long);
174
+ if (!removeShort && !removeLong) {
175
+ visibleOptions.push(helpOption);
176
+ } else if (helpOption.long && !removeLong) {
177
+ visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
178
+ } else if (helpOption.short && !removeShort) {
179
+ visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
180
+ }
181
+ }
182
+ if (this.sortOptions) {
183
+ visibleOptions.sort(this.compareOptions);
184
+ }
185
+ return visibleOptions;
186
+ }
187
+ visibleGlobalOptions(cmd) {
188
+ if (!this.showGlobalOptions)
189
+ return [];
190
+ const globalOptions = [];
191
+ for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
192
+ const visibleOptions = ancestorCmd.options.filter((option) => !option.hidden);
193
+ globalOptions.push(...visibleOptions);
194
+ }
195
+ if (this.sortOptions) {
196
+ globalOptions.sort(this.compareOptions);
197
+ }
198
+ return globalOptions;
199
+ }
200
+ visibleArguments(cmd) {
201
+ if (cmd._argsDescription) {
202
+ cmd.registeredArguments.forEach((argument) => {
203
+ argument.description = argument.description || cmd._argsDescription[argument.name()] || "";
204
+ });
205
+ }
206
+ if (cmd.registeredArguments.find((argument) => argument.description)) {
207
+ return cmd.registeredArguments;
208
+ }
209
+ return [];
210
+ }
211
+ subcommandTerm(cmd) {
212
+ const args = cmd.registeredArguments.map((arg) => humanReadableArgName(arg)).join(" ");
213
+ return cmd._name + (cmd._aliases[0] ? "|" + cmd._aliases[0] : "") + (cmd.options.length ? " [options]" : "") + (args ? " " + args : "");
214
+ }
215
+ optionTerm(option) {
216
+ return option.flags;
217
+ }
218
+ argumentTerm(argument) {
219
+ return argument.name();
220
+ }
221
+ longestSubcommandTermLength(cmd, helper) {
222
+ return helper.visibleCommands(cmd).reduce((max, command) => {
223
+ return Math.max(max, helper.subcommandTerm(command).length);
224
+ }, 0);
225
+ }
226
+ longestOptionTermLength(cmd, helper) {
227
+ return helper.visibleOptions(cmd).reduce((max, option) => {
228
+ return Math.max(max, helper.optionTerm(option).length);
229
+ }, 0);
230
+ }
231
+ longestGlobalOptionTermLength(cmd, helper) {
232
+ return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
233
+ return Math.max(max, helper.optionTerm(option).length);
234
+ }, 0);
235
+ }
236
+ longestArgumentTermLength(cmd, helper) {
237
+ return helper.visibleArguments(cmd).reduce((max, argument) => {
238
+ return Math.max(max, helper.argumentTerm(argument).length);
239
+ }, 0);
240
+ }
241
+ commandUsage(cmd) {
242
+ let cmdName = cmd._name;
243
+ if (cmd._aliases[0]) {
244
+ cmdName = cmdName + "|" + cmd._aliases[0];
245
+ }
246
+ let ancestorCmdNames = "";
247
+ for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
248
+ ancestorCmdNames = ancestorCmd.name() + " " + ancestorCmdNames;
249
+ }
250
+ return ancestorCmdNames + cmdName + " " + cmd.usage();
251
+ }
252
+ commandDescription(cmd) {
253
+ return cmd.description();
254
+ }
255
+ subcommandDescription(cmd) {
256
+ return cmd.summary() || cmd.description();
257
+ }
258
+ optionDescription(option) {
259
+ const extraInfo = [];
260
+ if (option.argChoices) {
261
+ extraInfo.push(`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
262
+ }
263
+ if (option.defaultValue !== undefined) {
264
+ const showDefault = option.required || option.optional || option.isBoolean() && typeof option.defaultValue === "boolean";
265
+ if (showDefault) {
266
+ extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
267
+ }
268
+ }
269
+ if (option.presetArg !== undefined && option.optional) {
270
+ extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
271
+ }
272
+ if (option.envVar !== undefined) {
273
+ extraInfo.push(`env: ${option.envVar}`);
274
+ }
275
+ if (extraInfo.length > 0) {
276
+ return `${option.description} (${extraInfo.join(", ")})`;
277
+ }
278
+ return option.description;
279
+ }
280
+ argumentDescription(argument) {
281
+ const extraInfo = [];
282
+ if (argument.argChoices) {
283
+ extraInfo.push(`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
284
+ }
285
+ if (argument.defaultValue !== undefined) {
286
+ extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
287
+ }
288
+ if (extraInfo.length > 0) {
289
+ const extraDescripton = `(${extraInfo.join(", ")})`;
290
+ if (argument.description) {
291
+ return `${argument.description} ${extraDescripton}`;
292
+ }
293
+ return extraDescripton;
294
+ }
295
+ return argument.description;
296
+ }
297
+ formatHelp(cmd, helper) {
298
+ const termWidth = helper.padWidth(cmd, helper);
299
+ const helpWidth = helper.helpWidth || 80;
300
+ const itemIndentWidth = 2;
301
+ const itemSeparatorWidth = 2;
302
+ function formatItem(term, description) {
303
+ if (description) {
304
+ const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
305
+ return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
306
+ }
307
+ return term;
308
+ }
309
+ function formatList(textArray) {
310
+ return textArray.join(`
311
+ `).replace(/^/gm, " ".repeat(itemIndentWidth));
312
+ }
313
+ let output = [`Usage: ${helper.commandUsage(cmd)}`, ""];
314
+ const commandDescription = helper.commandDescription(cmd);
315
+ if (commandDescription.length > 0) {
316
+ output = output.concat([
317
+ helper.wrap(commandDescription, helpWidth, 0),
318
+ ""
319
+ ]);
320
+ }
321
+ const argumentList = helper.visibleArguments(cmd).map((argument) => {
322
+ return formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument));
323
+ });
324
+ if (argumentList.length > 0) {
325
+ output = output.concat(["Arguments:", formatList(argumentList), ""]);
326
+ }
327
+ const optionList = helper.visibleOptions(cmd).map((option) => {
328
+ return formatItem(helper.optionTerm(option), helper.optionDescription(option));
329
+ });
330
+ if (optionList.length > 0) {
331
+ output = output.concat(["Options:", formatList(optionList), ""]);
332
+ }
333
+ if (this.showGlobalOptions) {
334
+ const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
335
+ return formatItem(helper.optionTerm(option), helper.optionDescription(option));
336
+ });
337
+ if (globalOptionList.length > 0) {
338
+ output = output.concat([
339
+ "Global Options:",
340
+ formatList(globalOptionList),
341
+ ""
342
+ ]);
343
+ }
344
+ }
345
+ const commandList = helper.visibleCommands(cmd).map((cmd2) => {
346
+ return formatItem(helper.subcommandTerm(cmd2), helper.subcommandDescription(cmd2));
347
+ });
348
+ if (commandList.length > 0) {
349
+ output = output.concat(["Commands:", formatList(commandList), ""]);
350
+ }
351
+ return output.join(`
352
+ `);
353
+ }
354
+ padWidth(cmd, helper) {
355
+ return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
356
+ }
357
+ wrap(str, width, indent, minColumnWidth = 40) {
358
+ const indents = " \\f\\t\\v\xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF";
359
+ const manualIndent = new RegExp(`[\\n][${indents}]+`);
360
+ if (str.match(manualIndent))
361
+ return str;
362
+ const columnWidth = width - indent;
363
+ if (columnWidth < minColumnWidth)
364
+ return str;
365
+ const leadingStr = str.slice(0, indent);
366
+ const columnText = str.slice(indent).replace(`\r
367
+ `, `
368
+ `);
369
+ const indentString = " ".repeat(indent);
370
+ const zeroWidthSpace = "\u200B";
371
+ const breaks = `\\s${zeroWidthSpace}`;
372
+ const regex = new RegExp(`
373
+ |.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`, "g");
374
+ const lines = columnText.match(regex) || [];
375
+ return leadingStr + lines.map((line, i) => {
376
+ if (line === `
377
+ `)
378
+ return "";
379
+ return (i > 0 ? indentString : "") + line.trimEnd();
380
+ }).join(`
381
+ `);
382
+ }
383
+ }
384
+ exports.Help = Help;
385
+ });
386
+
387
+ // node_modules/commander/lib/option.js
388
+ var require_option = __commonJS((exports) => {
389
+ var { InvalidArgumentError } = require_error();
390
+
391
+ class Option {
392
+ constructor(flags, description) {
393
+ this.flags = flags;
394
+ this.description = description || "";
395
+ this.required = flags.includes("<");
396
+ this.optional = flags.includes("[");
397
+ this.variadic = /\w\.\.\.[>\]]$/.test(flags);
398
+ this.mandatory = false;
399
+ const optionFlags = splitOptionFlags(flags);
400
+ this.short = optionFlags.shortFlag;
401
+ this.long = optionFlags.longFlag;
402
+ this.negate = false;
403
+ if (this.long) {
404
+ this.negate = this.long.startsWith("--no-");
405
+ }
406
+ this.defaultValue = undefined;
407
+ this.defaultValueDescription = undefined;
408
+ this.presetArg = undefined;
409
+ this.envVar = undefined;
410
+ this.parseArg = undefined;
411
+ this.hidden = false;
412
+ this.argChoices = undefined;
413
+ this.conflictsWith = [];
414
+ this.implied = undefined;
415
+ }
416
+ default(value, description) {
417
+ this.defaultValue = value;
418
+ this.defaultValueDescription = description;
419
+ return this;
420
+ }
421
+ preset(arg) {
422
+ this.presetArg = arg;
423
+ return this;
424
+ }
425
+ conflicts(names) {
426
+ this.conflictsWith = this.conflictsWith.concat(names);
427
+ return this;
428
+ }
429
+ implies(impliedOptionValues) {
430
+ let newImplied = impliedOptionValues;
431
+ if (typeof impliedOptionValues === "string") {
432
+ newImplied = { [impliedOptionValues]: true };
433
+ }
434
+ this.implied = Object.assign(this.implied || {}, newImplied);
435
+ return this;
436
+ }
437
+ env(name) {
438
+ this.envVar = name;
439
+ return this;
440
+ }
441
+ argParser(fn) {
442
+ this.parseArg = fn;
443
+ return this;
444
+ }
445
+ makeOptionMandatory(mandatory = true) {
446
+ this.mandatory = !!mandatory;
447
+ return this;
448
+ }
449
+ hideHelp(hide = true) {
450
+ this.hidden = !!hide;
451
+ return this;
452
+ }
453
+ _concatValue(value, previous) {
454
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
455
+ return [value];
456
+ }
457
+ return previous.concat(value);
458
+ }
459
+ choices(values) {
460
+ this.argChoices = values.slice();
461
+ this.parseArg = (arg, previous) => {
462
+ if (!this.argChoices.includes(arg)) {
463
+ throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
464
+ }
465
+ if (this.variadic) {
466
+ return this._concatValue(arg, previous);
467
+ }
468
+ return arg;
469
+ };
470
+ return this;
471
+ }
472
+ name() {
473
+ if (this.long) {
474
+ return this.long.replace(/^--/, "");
475
+ }
476
+ return this.short.replace(/^-/, "");
477
+ }
478
+ attributeName() {
479
+ return camelcase(this.name().replace(/^no-/, ""));
480
+ }
481
+ is(arg) {
482
+ return this.short === arg || this.long === arg;
483
+ }
484
+ isBoolean() {
485
+ return !this.required && !this.optional && !this.negate;
486
+ }
487
+ }
488
+
489
+ class DualOptions {
490
+ constructor(options) {
491
+ this.positiveOptions = new Map;
492
+ this.negativeOptions = new Map;
493
+ this.dualOptions = new Set;
494
+ options.forEach((option) => {
495
+ if (option.negate) {
496
+ this.negativeOptions.set(option.attributeName(), option);
497
+ } else {
498
+ this.positiveOptions.set(option.attributeName(), option);
499
+ }
500
+ });
501
+ this.negativeOptions.forEach((value, key) => {
502
+ if (this.positiveOptions.has(key)) {
503
+ this.dualOptions.add(key);
504
+ }
505
+ });
506
+ }
507
+ valueFromOption(value, option) {
508
+ const optionKey = option.attributeName();
509
+ if (!this.dualOptions.has(optionKey))
510
+ return true;
511
+ const preset = this.negativeOptions.get(optionKey).presetArg;
512
+ const negativeValue = preset !== undefined ? preset : false;
513
+ return option.negate === (negativeValue === value);
514
+ }
515
+ }
516
+ function camelcase(str) {
517
+ return str.split("-").reduce((str2, word) => {
518
+ return str2 + word[0].toUpperCase() + word.slice(1);
519
+ });
520
+ }
521
+ function splitOptionFlags(flags) {
522
+ let shortFlag;
523
+ let longFlag;
524
+ const flagParts = flags.split(/[ |,]+/);
525
+ if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1]))
526
+ shortFlag = flagParts.shift();
527
+ longFlag = flagParts.shift();
528
+ if (!shortFlag && /^-[^-]$/.test(longFlag)) {
529
+ shortFlag = longFlag;
530
+ longFlag = undefined;
531
+ }
532
+ return { shortFlag, longFlag };
533
+ }
534
+ exports.Option = Option;
535
+ exports.DualOptions = DualOptions;
536
+ });
537
+
538
+ // node_modules/commander/lib/suggestSimilar.js
539
+ var require_suggestSimilar = __commonJS((exports) => {
540
+ var maxDistance = 3;
541
+ function editDistance(a, b) {
542
+ if (Math.abs(a.length - b.length) > maxDistance)
543
+ return Math.max(a.length, b.length);
544
+ const d = [];
545
+ for (let i = 0;i <= a.length; i++) {
546
+ d[i] = [i];
547
+ }
548
+ for (let j = 0;j <= b.length; j++) {
549
+ d[0][j] = j;
550
+ }
551
+ for (let j = 1;j <= b.length; j++) {
552
+ for (let i = 1;i <= a.length; i++) {
553
+ let cost = 1;
554
+ if (a[i - 1] === b[j - 1]) {
555
+ cost = 0;
556
+ } else {
557
+ cost = 1;
558
+ }
559
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
560
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
561
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
562
+ }
563
+ }
564
+ }
565
+ return d[a.length][b.length];
566
+ }
567
+ function suggestSimilar(word, candidates) {
568
+ if (!candidates || candidates.length === 0)
569
+ return "";
570
+ candidates = Array.from(new Set(candidates));
571
+ const searchingOptions = word.startsWith("--");
572
+ if (searchingOptions) {
573
+ word = word.slice(2);
574
+ candidates = candidates.map((candidate) => candidate.slice(2));
575
+ }
576
+ let similar = [];
577
+ let bestDistance = maxDistance;
578
+ const minSimilarity = 0.4;
579
+ candidates.forEach((candidate) => {
580
+ if (candidate.length <= 1)
581
+ return;
582
+ const distance = editDistance(word, candidate);
583
+ const length = Math.max(word.length, candidate.length);
584
+ const similarity = (length - distance) / length;
585
+ if (similarity > minSimilarity) {
586
+ if (distance < bestDistance) {
587
+ bestDistance = distance;
588
+ similar = [candidate];
589
+ } else if (distance === bestDistance) {
590
+ similar.push(candidate);
591
+ }
592
+ }
593
+ });
594
+ similar.sort((a, b) => a.localeCompare(b));
595
+ if (searchingOptions) {
596
+ similar = similar.map((candidate) => `--${candidate}`);
597
+ }
598
+ if (similar.length > 1) {
599
+ return `
600
+ (Did you mean one of ${similar.join(", ")}?)`;
601
+ }
602
+ if (similar.length === 1) {
603
+ return `
604
+ (Did you mean ${similar[0]}?)`;
605
+ }
606
+ return "";
607
+ }
608
+ exports.suggestSimilar = suggestSimilar;
609
+ });
610
+
611
+ // node_modules/commander/lib/command.js
612
+ var require_command = __commonJS((exports) => {
613
+ var EventEmitter = __require("events").EventEmitter;
614
+ var childProcess = __require("child_process");
615
+ var path = __require("path");
616
+ var fs = __require("fs");
617
+ var process2 = __require("process");
618
+ var { Argument, humanReadableArgName } = require_argument();
619
+ var { CommanderError } = require_error();
620
+ var { Help } = require_help();
621
+ var { Option, DualOptions } = require_option();
622
+ var { suggestSimilar } = require_suggestSimilar();
623
+
624
+ class Command extends EventEmitter {
625
+ constructor(name) {
626
+ super();
627
+ this.commands = [];
628
+ this.options = [];
629
+ this.parent = null;
630
+ this._allowUnknownOption = false;
631
+ this._allowExcessArguments = true;
632
+ this.registeredArguments = [];
633
+ this._args = this.registeredArguments;
634
+ this.args = [];
635
+ this.rawArgs = [];
636
+ this.processedArgs = [];
637
+ this._scriptPath = null;
638
+ this._name = name || "";
639
+ this._optionValues = {};
640
+ this._optionValueSources = {};
641
+ this._storeOptionsAsProperties = false;
642
+ this._actionHandler = null;
643
+ this._executableHandler = false;
644
+ this._executableFile = null;
645
+ this._executableDir = null;
646
+ this._defaultCommandName = null;
647
+ this._exitCallback = null;
648
+ this._aliases = [];
649
+ this._combineFlagAndOptionalValue = true;
650
+ this._description = "";
651
+ this._summary = "";
652
+ this._argsDescription = undefined;
653
+ this._enablePositionalOptions = false;
654
+ this._passThroughOptions = false;
655
+ this._lifeCycleHooks = {};
656
+ this._showHelpAfterError = false;
657
+ this._showSuggestionAfterError = true;
658
+ this._outputConfiguration = {
659
+ writeOut: (str) => process2.stdout.write(str),
660
+ writeErr: (str) => process2.stderr.write(str),
661
+ getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : undefined,
662
+ getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : undefined,
663
+ outputError: (str, write) => write(str)
664
+ };
665
+ this._hidden = false;
666
+ this._helpOption = undefined;
667
+ this._addImplicitHelpCommand = undefined;
668
+ this._helpCommand = undefined;
669
+ this._helpConfiguration = {};
670
+ }
671
+ copyInheritedSettings(sourceCommand) {
672
+ this._outputConfiguration = sourceCommand._outputConfiguration;
673
+ this._helpOption = sourceCommand._helpOption;
674
+ this._helpCommand = sourceCommand._helpCommand;
675
+ this._helpConfiguration = sourceCommand._helpConfiguration;
676
+ this._exitCallback = sourceCommand._exitCallback;
677
+ this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
678
+ this._combineFlagAndOptionalValue = sourceCommand._combineFlagAndOptionalValue;
679
+ this._allowExcessArguments = sourceCommand._allowExcessArguments;
680
+ this._enablePositionalOptions = sourceCommand._enablePositionalOptions;
681
+ this._showHelpAfterError = sourceCommand._showHelpAfterError;
682
+ this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;
683
+ return this;
684
+ }
685
+ _getCommandAndAncestors() {
686
+ const result = [];
687
+ for (let command = this;command; command = command.parent) {
688
+ result.push(command);
689
+ }
690
+ return result;
691
+ }
692
+ command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
693
+ let desc = actionOptsOrExecDesc;
694
+ let opts = execOpts;
695
+ if (typeof desc === "object" && desc !== null) {
696
+ opts = desc;
697
+ desc = null;
698
+ }
699
+ opts = opts || {};
700
+ const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
701
+ const cmd = this.createCommand(name);
702
+ if (desc) {
703
+ cmd.description(desc);
704
+ cmd._executableHandler = true;
705
+ }
706
+ if (opts.isDefault)
707
+ this._defaultCommandName = cmd._name;
708
+ cmd._hidden = !!(opts.noHelp || opts.hidden);
709
+ cmd._executableFile = opts.executableFile || null;
710
+ if (args)
711
+ cmd.arguments(args);
712
+ this._registerCommand(cmd);
713
+ cmd.parent = this;
714
+ cmd.copyInheritedSettings(this);
715
+ if (desc)
716
+ return this;
717
+ return cmd;
718
+ }
719
+ createCommand(name) {
720
+ return new Command(name);
721
+ }
722
+ createHelp() {
723
+ return Object.assign(new Help, this.configureHelp());
724
+ }
725
+ configureHelp(configuration) {
726
+ if (configuration === undefined)
727
+ return this._helpConfiguration;
728
+ this._helpConfiguration = configuration;
729
+ return this;
730
+ }
731
+ configureOutput(configuration) {
732
+ if (configuration === undefined)
733
+ return this._outputConfiguration;
734
+ Object.assign(this._outputConfiguration, configuration);
735
+ return this;
736
+ }
737
+ showHelpAfterError(displayHelp = true) {
738
+ if (typeof displayHelp !== "string")
739
+ displayHelp = !!displayHelp;
740
+ this._showHelpAfterError = displayHelp;
741
+ return this;
742
+ }
743
+ showSuggestionAfterError(displaySuggestion = true) {
744
+ this._showSuggestionAfterError = !!displaySuggestion;
745
+ return this;
746
+ }
747
+ addCommand(cmd, opts) {
748
+ if (!cmd._name) {
749
+ throw new Error(`Command passed to .addCommand() must have a name
750
+ - specify the name in Command constructor or using .name()`);
751
+ }
752
+ opts = opts || {};
753
+ if (opts.isDefault)
754
+ this._defaultCommandName = cmd._name;
755
+ if (opts.noHelp || opts.hidden)
756
+ cmd._hidden = true;
757
+ this._registerCommand(cmd);
758
+ cmd.parent = this;
759
+ cmd._checkForBrokenPassThrough();
760
+ return this;
761
+ }
762
+ createArgument(name, description) {
763
+ return new Argument(name, description);
764
+ }
765
+ argument(name, description, fn, defaultValue) {
766
+ const argument = this.createArgument(name, description);
767
+ if (typeof fn === "function") {
768
+ argument.default(defaultValue).argParser(fn);
769
+ } else {
770
+ argument.default(fn);
771
+ }
772
+ this.addArgument(argument);
773
+ return this;
774
+ }
775
+ arguments(names) {
776
+ names.trim().split(/ +/).forEach((detail) => {
777
+ this.argument(detail);
778
+ });
779
+ return this;
780
+ }
781
+ addArgument(argument) {
782
+ const previousArgument = this.registeredArguments.slice(-1)[0];
783
+ if (previousArgument && previousArgument.variadic) {
784
+ throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`);
785
+ }
786
+ if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) {
787
+ throw new Error(`a default value for a required argument is never used: '${argument.name()}'`);
788
+ }
789
+ this.registeredArguments.push(argument);
790
+ return this;
791
+ }
792
+ helpCommand(enableOrNameAndArgs, description) {
793
+ if (typeof enableOrNameAndArgs === "boolean") {
794
+ this._addImplicitHelpCommand = enableOrNameAndArgs;
795
+ return this;
796
+ }
797
+ enableOrNameAndArgs = enableOrNameAndArgs ?? "help [command]";
798
+ const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
799
+ const helpDescription = description ?? "display help for command";
800
+ const helpCommand = this.createCommand(helpName);
801
+ helpCommand.helpOption(false);
802
+ if (helpArgs)
803
+ helpCommand.arguments(helpArgs);
804
+ if (helpDescription)
805
+ helpCommand.description(helpDescription);
806
+ this._addImplicitHelpCommand = true;
807
+ this._helpCommand = helpCommand;
808
+ return this;
809
+ }
810
+ addHelpCommand(helpCommand, deprecatedDescription) {
811
+ if (typeof helpCommand !== "object") {
812
+ this.helpCommand(helpCommand, deprecatedDescription);
813
+ return this;
814
+ }
815
+ this._addImplicitHelpCommand = true;
816
+ this._helpCommand = helpCommand;
817
+ return this;
818
+ }
819
+ _getHelpCommand() {
820
+ const hasImplicitHelpCommand = this._addImplicitHelpCommand ?? (this.commands.length && !this._actionHandler && !this._findCommand("help"));
821
+ if (hasImplicitHelpCommand) {
822
+ if (this._helpCommand === undefined) {
823
+ this.helpCommand(undefined, undefined);
824
+ }
825
+ return this._helpCommand;
826
+ }
827
+ return null;
828
+ }
829
+ hook(event, listener) {
830
+ const allowedValues = ["preSubcommand", "preAction", "postAction"];
831
+ if (!allowedValues.includes(event)) {
832
+ throw new Error(`Unexpected value for event passed to hook : '${event}'.
833
+ Expecting one of '${allowedValues.join("', '")}'`);
834
+ }
835
+ if (this._lifeCycleHooks[event]) {
836
+ this._lifeCycleHooks[event].push(listener);
837
+ } else {
838
+ this._lifeCycleHooks[event] = [listener];
839
+ }
840
+ return this;
841
+ }
842
+ exitOverride(fn) {
843
+ if (fn) {
844
+ this._exitCallback = fn;
845
+ } else {
846
+ this._exitCallback = (err) => {
847
+ if (err.code !== "commander.executeSubCommandAsync") {
848
+ throw err;
849
+ }
850
+ };
851
+ }
852
+ return this;
853
+ }
854
+ _exit(exitCode, code, message) {
855
+ if (this._exitCallback) {
856
+ this._exitCallback(new CommanderError(exitCode, code, message));
857
+ }
858
+ process2.exit(exitCode);
859
+ }
860
+ action(fn) {
861
+ const listener = (args) => {
862
+ const expectedArgsCount = this.registeredArguments.length;
863
+ const actionArgs = args.slice(0, expectedArgsCount);
864
+ if (this._storeOptionsAsProperties) {
865
+ actionArgs[expectedArgsCount] = this;
866
+ } else {
867
+ actionArgs[expectedArgsCount] = this.opts();
868
+ }
869
+ actionArgs.push(this);
870
+ return fn.apply(this, actionArgs);
871
+ };
872
+ this._actionHandler = listener;
873
+ return this;
874
+ }
875
+ createOption(flags, description) {
876
+ return new Option(flags, description);
877
+ }
878
+ _callParseArg(target, value, previous, invalidArgumentMessage) {
879
+ try {
880
+ return target.parseArg(value, previous);
881
+ } catch (err) {
882
+ if (err.code === "commander.invalidArgument") {
883
+ const message = `${invalidArgumentMessage} ${err.message}`;
884
+ this.error(message, { exitCode: err.exitCode, code: err.code });
885
+ }
886
+ throw err;
887
+ }
888
+ }
889
+ _registerOption(option) {
890
+ const matchingOption = option.short && this._findOption(option.short) || option.long && this._findOption(option.long);
891
+ if (matchingOption) {
892
+ const matchingFlag = option.long && this._findOption(option.long) ? option.long : option.short;
893
+ throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
894
+ - already used by option '${matchingOption.flags}'`);
895
+ }
896
+ this.options.push(option);
897
+ }
898
+ _registerCommand(command) {
899
+ const knownBy = (cmd) => {
900
+ return [cmd.name()].concat(cmd.aliases());
901
+ };
902
+ const alreadyUsed = knownBy(command).find((name) => this._findCommand(name));
903
+ if (alreadyUsed) {
904
+ const existingCmd = knownBy(this._findCommand(alreadyUsed)).join("|");
905
+ const newCmd = knownBy(command).join("|");
906
+ throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
907
+ }
908
+ this.commands.push(command);
909
+ }
910
+ addOption(option) {
911
+ this._registerOption(option);
912
+ const oname = option.name();
913
+ const name = option.attributeName();
914
+ if (option.negate) {
915
+ const positiveLongFlag = option.long.replace(/^--no-/, "--");
916
+ if (!this._findOption(positiveLongFlag)) {
917
+ this.setOptionValueWithSource(name, option.defaultValue === undefined ? true : option.defaultValue, "default");
918
+ }
919
+ } else if (option.defaultValue !== undefined) {
920
+ this.setOptionValueWithSource(name, option.defaultValue, "default");
921
+ }
922
+ const handleOptionValue = (val, invalidValueMessage, valueSource) => {
923
+ if (val == null && option.presetArg !== undefined) {
924
+ val = option.presetArg;
925
+ }
926
+ const oldValue = this.getOptionValue(name);
927
+ if (val !== null && option.parseArg) {
928
+ val = this._callParseArg(option, val, oldValue, invalidValueMessage);
929
+ } else if (val !== null && option.variadic) {
930
+ val = option._concatValue(val, oldValue);
931
+ }
932
+ if (val == null) {
933
+ if (option.negate) {
934
+ val = false;
935
+ } else if (option.isBoolean() || option.optional) {
936
+ val = true;
937
+ } else {
938
+ val = "";
939
+ }
940
+ }
941
+ this.setOptionValueWithSource(name, val, valueSource);
942
+ };
943
+ this.on("option:" + oname, (val) => {
944
+ const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
945
+ handleOptionValue(val, invalidValueMessage, "cli");
946
+ });
947
+ if (option.envVar) {
948
+ this.on("optionEnv:" + oname, (val) => {
949
+ const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
950
+ handleOptionValue(val, invalidValueMessage, "env");
951
+ });
952
+ }
953
+ return this;
954
+ }
955
+ _optionEx(config, flags, description, fn, defaultValue) {
956
+ if (typeof flags === "object" && flags instanceof Option) {
957
+ throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");
958
+ }
959
+ const option = this.createOption(flags, description);
960
+ option.makeOptionMandatory(!!config.mandatory);
961
+ if (typeof fn === "function") {
962
+ option.default(defaultValue).argParser(fn);
963
+ } else if (fn instanceof RegExp) {
964
+ const regex = fn;
965
+ fn = (val, def) => {
966
+ const m = regex.exec(val);
967
+ return m ? m[0] : def;
968
+ };
969
+ option.default(defaultValue).argParser(fn);
970
+ } else {
971
+ option.default(fn);
972
+ }
973
+ return this.addOption(option);
974
+ }
975
+ option(flags, description, parseArg, defaultValue) {
976
+ return this._optionEx({}, flags, description, parseArg, defaultValue);
977
+ }
978
+ requiredOption(flags, description, parseArg, defaultValue) {
979
+ return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
980
+ }
981
+ combineFlagAndOptionalValue(combine = true) {
982
+ this._combineFlagAndOptionalValue = !!combine;
983
+ return this;
984
+ }
985
+ allowUnknownOption(allowUnknown = true) {
986
+ this._allowUnknownOption = !!allowUnknown;
987
+ return this;
988
+ }
989
+ allowExcessArguments(allowExcess = true) {
990
+ this._allowExcessArguments = !!allowExcess;
991
+ return this;
992
+ }
993
+ enablePositionalOptions(positional = true) {
994
+ this._enablePositionalOptions = !!positional;
995
+ return this;
996
+ }
997
+ passThroughOptions(passThrough = true) {
998
+ this._passThroughOptions = !!passThrough;
999
+ this._checkForBrokenPassThrough();
1000
+ return this;
1001
+ }
1002
+ _checkForBrokenPassThrough() {
1003
+ if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) {
1004
+ throw new Error(`passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`);
1005
+ }
1006
+ }
1007
+ storeOptionsAsProperties(storeAsProperties = true) {
1008
+ if (this.options.length) {
1009
+ throw new Error("call .storeOptionsAsProperties() before adding options");
1010
+ }
1011
+ if (Object.keys(this._optionValues).length) {
1012
+ throw new Error("call .storeOptionsAsProperties() before setting option values");
1013
+ }
1014
+ this._storeOptionsAsProperties = !!storeAsProperties;
1015
+ return this;
1016
+ }
1017
+ getOptionValue(key) {
1018
+ if (this._storeOptionsAsProperties) {
1019
+ return this[key];
1020
+ }
1021
+ return this._optionValues[key];
1022
+ }
1023
+ setOptionValue(key, value) {
1024
+ return this.setOptionValueWithSource(key, value, undefined);
1025
+ }
1026
+ setOptionValueWithSource(key, value, source) {
1027
+ if (this._storeOptionsAsProperties) {
1028
+ this[key] = value;
1029
+ } else {
1030
+ this._optionValues[key] = value;
1031
+ }
1032
+ this._optionValueSources[key] = source;
1033
+ return this;
1034
+ }
1035
+ getOptionValueSource(key) {
1036
+ return this._optionValueSources[key];
1037
+ }
1038
+ getOptionValueSourceWithGlobals(key) {
1039
+ let source;
1040
+ this._getCommandAndAncestors().forEach((cmd) => {
1041
+ if (cmd.getOptionValueSource(key) !== undefined) {
1042
+ source = cmd.getOptionValueSource(key);
1043
+ }
1044
+ });
1045
+ return source;
1046
+ }
1047
+ _prepareUserArgs(argv, parseOptions) {
1048
+ if (argv !== undefined && !Array.isArray(argv)) {
1049
+ throw new Error("first parameter to parse must be array or undefined");
1050
+ }
1051
+ parseOptions = parseOptions || {};
1052
+ if (argv === undefined && parseOptions.from === undefined) {
1053
+ if (process2.versions?.electron) {
1054
+ parseOptions.from = "electron";
1055
+ }
1056
+ const execArgv = process2.execArgv ?? [];
1057
+ if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
1058
+ parseOptions.from = "eval";
1059
+ }
1060
+ }
1061
+ if (argv === undefined) {
1062
+ argv = process2.argv;
1063
+ }
1064
+ this.rawArgs = argv.slice();
1065
+ let userArgs;
1066
+ switch (parseOptions.from) {
1067
+ case undefined:
1068
+ case "node":
1069
+ this._scriptPath = argv[1];
1070
+ userArgs = argv.slice(2);
1071
+ break;
1072
+ case "electron":
1073
+ if (process2.defaultApp) {
1074
+ this._scriptPath = argv[1];
1075
+ userArgs = argv.slice(2);
1076
+ } else {
1077
+ userArgs = argv.slice(1);
1078
+ }
1079
+ break;
1080
+ case "user":
1081
+ userArgs = argv.slice(0);
1082
+ break;
1083
+ case "eval":
1084
+ userArgs = argv.slice(1);
1085
+ break;
1086
+ default:
1087
+ throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
1088
+ }
1089
+ if (!this._name && this._scriptPath)
1090
+ this.nameFromFilename(this._scriptPath);
1091
+ this._name = this._name || "program";
1092
+ return userArgs;
1093
+ }
1094
+ parse(argv, parseOptions) {
1095
+ const userArgs = this._prepareUserArgs(argv, parseOptions);
1096
+ this._parseCommand([], userArgs);
1097
+ return this;
1098
+ }
1099
+ async parseAsync(argv, parseOptions) {
1100
+ const userArgs = this._prepareUserArgs(argv, parseOptions);
1101
+ await this._parseCommand([], userArgs);
1102
+ return this;
1103
+ }
1104
+ _executeSubCommand(subcommand, args) {
1105
+ args = args.slice();
1106
+ let launchWithNode = false;
1107
+ const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1108
+ function findFile(baseDir, baseName) {
1109
+ const localBin = path.resolve(baseDir, baseName);
1110
+ if (fs.existsSync(localBin))
1111
+ return localBin;
1112
+ if (sourceExt.includes(path.extname(baseName)))
1113
+ return;
1114
+ const foundExt = sourceExt.find((ext) => fs.existsSync(`${localBin}${ext}`));
1115
+ if (foundExt)
1116
+ return `${localBin}${foundExt}`;
1117
+ return;
1118
+ }
1119
+ this._checkForMissingMandatoryOptions();
1120
+ this._checkForConflictingOptions();
1121
+ let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
1122
+ let executableDir = this._executableDir || "";
1123
+ if (this._scriptPath) {
1124
+ let resolvedScriptPath;
1125
+ try {
1126
+ resolvedScriptPath = fs.realpathSync(this._scriptPath);
1127
+ } catch (err) {
1128
+ resolvedScriptPath = this._scriptPath;
1129
+ }
1130
+ executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
1131
+ }
1132
+ if (executableDir) {
1133
+ let localFile = findFile(executableDir, executableFile);
1134
+ if (!localFile && !subcommand._executableFile && this._scriptPath) {
1135
+ const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
1136
+ if (legacyName !== this._name) {
1137
+ localFile = findFile(executableDir, `${legacyName}-${subcommand._name}`);
1138
+ }
1139
+ }
1140
+ executableFile = localFile || executableFile;
1141
+ }
1142
+ launchWithNode = sourceExt.includes(path.extname(executableFile));
1143
+ let proc;
1144
+ if (process2.platform !== "win32") {
1145
+ if (launchWithNode) {
1146
+ args.unshift(executableFile);
1147
+ args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1148
+ proc = childProcess.spawn(process2.argv[0], args, { stdio: "inherit" });
1149
+ } else {
1150
+ proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
1151
+ }
1152
+ } else {
1153
+ args.unshift(executableFile);
1154
+ args = incrementNodeInspectorPort(process2.execArgv).concat(args);
1155
+ proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
1156
+ }
1157
+ if (!proc.killed) {
1158
+ const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
1159
+ signals.forEach((signal) => {
1160
+ process2.on(signal, () => {
1161
+ if (proc.killed === false && proc.exitCode === null) {
1162
+ proc.kill(signal);
1163
+ }
1164
+ });
1165
+ });
1166
+ }
1167
+ const exitCallback = this._exitCallback;
1168
+ proc.on("close", (code) => {
1169
+ code = code ?? 1;
1170
+ if (!exitCallback) {
1171
+ process2.exit(code);
1172
+ } else {
1173
+ exitCallback(new CommanderError(code, "commander.executeSubCommandAsync", "(close)"));
1174
+ }
1175
+ });
1176
+ proc.on("error", (err) => {
1177
+ if (err.code === "ENOENT") {
1178
+ 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";
1179
+ const executableMissing = `'${executableFile}' does not exist
1180
+ - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1181
+ - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
1182
+ - ${executableDirMessage}`;
1183
+ throw new Error(executableMissing);
1184
+ } else if (err.code === "EACCES") {
1185
+ throw new Error(`'${executableFile}' not executable`);
1186
+ }
1187
+ if (!exitCallback) {
1188
+ process2.exit(1);
1189
+ } else {
1190
+ const wrappedError = new CommanderError(1, "commander.executeSubCommandAsync", "(error)");
1191
+ wrappedError.nestedError = err;
1192
+ exitCallback(wrappedError);
1193
+ }
1194
+ });
1195
+ this.runningCommand = proc;
1196
+ }
1197
+ _dispatchSubcommand(commandName, operands, unknown) {
1198
+ const subCommand = this._findCommand(commandName);
1199
+ if (!subCommand)
1200
+ this.help({ error: true });
1201
+ let promiseChain;
1202
+ promiseChain = this._chainOrCallSubCommandHook(promiseChain, subCommand, "preSubcommand");
1203
+ promiseChain = this._chainOrCall(promiseChain, () => {
1204
+ if (subCommand._executableHandler) {
1205
+ this._executeSubCommand(subCommand, operands.concat(unknown));
1206
+ } else {
1207
+ return subCommand._parseCommand(operands, unknown);
1208
+ }
1209
+ });
1210
+ return promiseChain;
1211
+ }
1212
+ _dispatchHelpCommand(subcommandName) {
1213
+ if (!subcommandName) {
1214
+ this.help();
1215
+ }
1216
+ const subCommand = this._findCommand(subcommandName);
1217
+ if (subCommand && !subCommand._executableHandler) {
1218
+ subCommand.help();
1219
+ }
1220
+ return this._dispatchSubcommand(subcommandName, [], [this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? "--help"]);
1221
+ }
1222
+ _checkNumberOfArguments() {
1223
+ this.registeredArguments.forEach((arg, i) => {
1224
+ if (arg.required && this.args[i] == null) {
1225
+ this.missingArgument(arg.name());
1226
+ }
1227
+ });
1228
+ if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) {
1229
+ return;
1230
+ }
1231
+ if (this.args.length > this.registeredArguments.length) {
1232
+ this._excessArguments(this.args);
1233
+ }
1234
+ }
1235
+ _processArguments() {
1236
+ const myParseArg = (argument, value, previous) => {
1237
+ let parsedValue = value;
1238
+ if (value !== null && argument.parseArg) {
1239
+ const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`;
1240
+ parsedValue = this._callParseArg(argument, value, previous, invalidValueMessage);
1241
+ }
1242
+ return parsedValue;
1243
+ };
1244
+ this._checkNumberOfArguments();
1245
+ const processedArgs = [];
1246
+ this.registeredArguments.forEach((declaredArg, index) => {
1247
+ let value = declaredArg.defaultValue;
1248
+ if (declaredArg.variadic) {
1249
+ if (index < this.args.length) {
1250
+ value = this.args.slice(index);
1251
+ if (declaredArg.parseArg) {
1252
+ value = value.reduce((processed, v) => {
1253
+ return myParseArg(declaredArg, v, processed);
1254
+ }, declaredArg.defaultValue);
1255
+ }
1256
+ } else if (value === undefined) {
1257
+ value = [];
1258
+ }
1259
+ } else if (index < this.args.length) {
1260
+ value = this.args[index];
1261
+ if (declaredArg.parseArg) {
1262
+ value = myParseArg(declaredArg, value, declaredArg.defaultValue);
1263
+ }
1264
+ }
1265
+ processedArgs[index] = value;
1266
+ });
1267
+ this.processedArgs = processedArgs;
1268
+ }
1269
+ _chainOrCall(promise, fn) {
1270
+ if (promise && promise.then && typeof promise.then === "function") {
1271
+ return promise.then(() => fn());
1272
+ }
1273
+ return fn();
1274
+ }
1275
+ _chainOrCallHooks(promise, event) {
1276
+ let result = promise;
1277
+ const hooks = [];
1278
+ this._getCommandAndAncestors().reverse().filter((cmd) => cmd._lifeCycleHooks[event] !== undefined).forEach((hookedCommand) => {
1279
+ hookedCommand._lifeCycleHooks[event].forEach((callback) => {
1280
+ hooks.push({ hookedCommand, callback });
1281
+ });
1282
+ });
1283
+ if (event === "postAction") {
1284
+ hooks.reverse();
1285
+ }
1286
+ hooks.forEach((hookDetail) => {
1287
+ result = this._chainOrCall(result, () => {
1288
+ return hookDetail.callback(hookDetail.hookedCommand, this);
1289
+ });
1290
+ });
1291
+ return result;
1292
+ }
1293
+ _chainOrCallSubCommandHook(promise, subCommand, event) {
1294
+ let result = promise;
1295
+ if (this._lifeCycleHooks[event] !== undefined) {
1296
+ this._lifeCycleHooks[event].forEach((hook) => {
1297
+ result = this._chainOrCall(result, () => {
1298
+ return hook(this, subCommand);
1299
+ });
1300
+ });
1301
+ }
1302
+ return result;
1303
+ }
1304
+ _parseCommand(operands, unknown) {
1305
+ const parsed = this.parseOptions(unknown);
1306
+ this._parseOptionsEnv();
1307
+ this._parseOptionsImplied();
1308
+ operands = operands.concat(parsed.operands);
1309
+ unknown = parsed.unknown;
1310
+ this.args = operands.concat(unknown);
1311
+ if (operands && this._findCommand(operands[0])) {
1312
+ return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
1313
+ }
1314
+ if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
1315
+ return this._dispatchHelpCommand(operands[1]);
1316
+ }
1317
+ if (this._defaultCommandName) {
1318
+ this._outputHelpIfRequested(unknown);
1319
+ return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
1320
+ }
1321
+ if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
1322
+ this.help({ error: true });
1323
+ }
1324
+ this._outputHelpIfRequested(parsed.unknown);
1325
+ this._checkForMissingMandatoryOptions();
1326
+ this._checkForConflictingOptions();
1327
+ const checkForUnknownOptions = () => {
1328
+ if (parsed.unknown.length > 0) {
1329
+ this.unknownOption(parsed.unknown[0]);
1330
+ }
1331
+ };
1332
+ const commandEvent = `command:${this.name()}`;
1333
+ if (this._actionHandler) {
1334
+ checkForUnknownOptions();
1335
+ this._processArguments();
1336
+ let promiseChain;
1337
+ promiseChain = this._chainOrCallHooks(promiseChain, "preAction");
1338
+ promiseChain = this._chainOrCall(promiseChain, () => this._actionHandler(this.processedArgs));
1339
+ if (this.parent) {
1340
+ promiseChain = this._chainOrCall(promiseChain, () => {
1341
+ this.parent.emit(commandEvent, operands, unknown);
1342
+ });
1343
+ }
1344
+ promiseChain = this._chainOrCallHooks(promiseChain, "postAction");
1345
+ return promiseChain;
1346
+ }
1347
+ if (this.parent && this.parent.listenerCount(commandEvent)) {
1348
+ checkForUnknownOptions();
1349
+ this._processArguments();
1350
+ this.parent.emit(commandEvent, operands, unknown);
1351
+ } else if (operands.length) {
1352
+ if (this._findCommand("*")) {
1353
+ return this._dispatchSubcommand("*", operands, unknown);
1354
+ }
1355
+ if (this.listenerCount("command:*")) {
1356
+ this.emit("command:*", operands, unknown);
1357
+ } else if (this.commands.length) {
1358
+ this.unknownCommand();
1359
+ } else {
1360
+ checkForUnknownOptions();
1361
+ this._processArguments();
1362
+ }
1363
+ } else if (this.commands.length) {
1364
+ checkForUnknownOptions();
1365
+ this.help({ error: true });
1366
+ } else {
1367
+ checkForUnknownOptions();
1368
+ this._processArguments();
1369
+ }
1370
+ }
1371
+ _findCommand(name) {
1372
+ if (!name)
1373
+ return;
1374
+ return this.commands.find((cmd) => cmd._name === name || cmd._aliases.includes(name));
1375
+ }
1376
+ _findOption(arg) {
1377
+ return this.options.find((option) => option.is(arg));
1378
+ }
1379
+ _checkForMissingMandatoryOptions() {
1380
+ this._getCommandAndAncestors().forEach((cmd) => {
1381
+ cmd.options.forEach((anOption) => {
1382
+ if (anOption.mandatory && cmd.getOptionValue(anOption.attributeName()) === undefined) {
1383
+ cmd.missingMandatoryOptionValue(anOption);
1384
+ }
1385
+ });
1386
+ });
1387
+ }
1388
+ _checkForConflictingLocalOptions() {
1389
+ const definedNonDefaultOptions = this.options.filter((option) => {
1390
+ const optionKey = option.attributeName();
1391
+ if (this.getOptionValue(optionKey) === undefined) {
1392
+ return false;
1393
+ }
1394
+ return this.getOptionValueSource(optionKey) !== "default";
1395
+ });
1396
+ const optionsWithConflicting = definedNonDefaultOptions.filter((option) => option.conflictsWith.length > 0);
1397
+ optionsWithConflicting.forEach((option) => {
1398
+ const conflictingAndDefined = definedNonDefaultOptions.find((defined) => option.conflictsWith.includes(defined.attributeName()));
1399
+ if (conflictingAndDefined) {
1400
+ this._conflictingOption(option, conflictingAndDefined);
1401
+ }
1402
+ });
1403
+ }
1404
+ _checkForConflictingOptions() {
1405
+ this._getCommandAndAncestors().forEach((cmd) => {
1406
+ cmd._checkForConflictingLocalOptions();
1407
+ });
1408
+ }
1409
+ parseOptions(argv) {
1410
+ const operands = [];
1411
+ const unknown = [];
1412
+ let dest = operands;
1413
+ const args = argv.slice();
1414
+ function maybeOption(arg) {
1415
+ return arg.length > 1 && arg[0] === "-";
1416
+ }
1417
+ let activeVariadicOption = null;
1418
+ while (args.length) {
1419
+ const arg = args.shift();
1420
+ if (arg === "--") {
1421
+ if (dest === unknown)
1422
+ dest.push(arg);
1423
+ dest.push(...args);
1424
+ break;
1425
+ }
1426
+ if (activeVariadicOption && !maybeOption(arg)) {
1427
+ this.emit(`option:${activeVariadicOption.name()}`, arg);
1428
+ continue;
1429
+ }
1430
+ activeVariadicOption = null;
1431
+ if (maybeOption(arg)) {
1432
+ const option = this._findOption(arg);
1433
+ if (option) {
1434
+ if (option.required) {
1435
+ const value = args.shift();
1436
+ if (value === undefined)
1437
+ this.optionMissingArgument(option);
1438
+ this.emit(`option:${option.name()}`, value);
1439
+ } else if (option.optional) {
1440
+ let value = null;
1441
+ if (args.length > 0 && !maybeOption(args[0])) {
1442
+ value = args.shift();
1443
+ }
1444
+ this.emit(`option:${option.name()}`, value);
1445
+ } else {
1446
+ this.emit(`option:${option.name()}`);
1447
+ }
1448
+ activeVariadicOption = option.variadic ? option : null;
1449
+ continue;
1450
+ }
1451
+ }
1452
+ if (arg.length > 2 && arg[0] === "-" && arg[1] !== "-") {
1453
+ const option = this._findOption(`-${arg[1]}`);
1454
+ if (option) {
1455
+ if (option.required || option.optional && this._combineFlagAndOptionalValue) {
1456
+ this.emit(`option:${option.name()}`, arg.slice(2));
1457
+ } else {
1458
+ this.emit(`option:${option.name()}`);
1459
+ args.unshift(`-${arg.slice(2)}`);
1460
+ }
1461
+ continue;
1462
+ }
1463
+ }
1464
+ if (/^--[^=]+=/.test(arg)) {
1465
+ const index = arg.indexOf("=");
1466
+ const option = this._findOption(arg.slice(0, index));
1467
+ if (option && (option.required || option.optional)) {
1468
+ this.emit(`option:${option.name()}`, arg.slice(index + 1));
1469
+ continue;
1470
+ }
1471
+ }
1472
+ if (maybeOption(arg)) {
1473
+ dest = unknown;
1474
+ }
1475
+ if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
1476
+ if (this._findCommand(arg)) {
1477
+ operands.push(arg);
1478
+ if (args.length > 0)
1479
+ unknown.push(...args);
1480
+ break;
1481
+ } else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
1482
+ operands.push(arg);
1483
+ if (args.length > 0)
1484
+ operands.push(...args);
1485
+ break;
1486
+ } else if (this._defaultCommandName) {
1487
+ unknown.push(arg);
1488
+ if (args.length > 0)
1489
+ unknown.push(...args);
1490
+ break;
1491
+ }
1492
+ }
1493
+ if (this._passThroughOptions) {
1494
+ dest.push(arg);
1495
+ if (args.length > 0)
1496
+ dest.push(...args);
1497
+ break;
1498
+ }
1499
+ dest.push(arg);
1500
+ }
1501
+ return { operands, unknown };
1502
+ }
1503
+ opts() {
1504
+ if (this._storeOptionsAsProperties) {
1505
+ const result = {};
1506
+ const len = this.options.length;
1507
+ for (let i = 0;i < len; i++) {
1508
+ const key = this.options[i].attributeName();
1509
+ result[key] = key === this._versionOptionName ? this._version : this[key];
1510
+ }
1511
+ return result;
1512
+ }
1513
+ return this._optionValues;
1514
+ }
1515
+ optsWithGlobals() {
1516
+ return this._getCommandAndAncestors().reduce((combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()), {});
1517
+ }
1518
+ error(message, errorOptions) {
1519
+ this._outputConfiguration.outputError(`${message}
1520
+ `, this._outputConfiguration.writeErr);
1521
+ if (typeof this._showHelpAfterError === "string") {
1522
+ this._outputConfiguration.writeErr(`${this._showHelpAfterError}
1523
+ `);
1524
+ } else if (this._showHelpAfterError) {
1525
+ this._outputConfiguration.writeErr(`
1526
+ `);
1527
+ this.outputHelp({ error: true });
1528
+ }
1529
+ const config = errorOptions || {};
1530
+ const exitCode = config.exitCode || 1;
1531
+ const code = config.code || "commander.error";
1532
+ this._exit(exitCode, code, message);
1533
+ }
1534
+ _parseOptionsEnv() {
1535
+ this.options.forEach((option) => {
1536
+ if (option.envVar && option.envVar in process2.env) {
1537
+ const optionKey = option.attributeName();
1538
+ if (this.getOptionValue(optionKey) === undefined || ["default", "config", "env"].includes(this.getOptionValueSource(optionKey))) {
1539
+ if (option.required || option.optional) {
1540
+ this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
1541
+ } else {
1542
+ this.emit(`optionEnv:${option.name()}`);
1543
+ }
1544
+ }
1545
+ }
1546
+ });
1547
+ }
1548
+ _parseOptionsImplied() {
1549
+ const dualHelper = new DualOptions(this.options);
1550
+ const hasCustomOptionValue = (optionKey) => {
1551
+ return this.getOptionValue(optionKey) !== undefined && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
1552
+ };
1553
+ this.options.filter((option) => option.implied !== undefined && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(this.getOptionValue(option.attributeName()), option)).forEach((option) => {
1554
+ Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => {
1555
+ this.setOptionValueWithSource(impliedKey, option.implied[impliedKey], "implied");
1556
+ });
1557
+ });
1558
+ }
1559
+ missingArgument(name) {
1560
+ const message = `error: missing required argument '${name}'`;
1561
+ this.error(message, { code: "commander.missingArgument" });
1562
+ }
1563
+ optionMissingArgument(option) {
1564
+ const message = `error: option '${option.flags}' argument missing`;
1565
+ this.error(message, { code: "commander.optionMissingArgument" });
1566
+ }
1567
+ missingMandatoryOptionValue(option) {
1568
+ const message = `error: required option '${option.flags}' not specified`;
1569
+ this.error(message, { code: "commander.missingMandatoryOptionValue" });
1570
+ }
1571
+ _conflictingOption(option, conflictingOption) {
1572
+ const findBestOptionFromValue = (option2) => {
1573
+ const optionKey = option2.attributeName();
1574
+ const optionValue = this.getOptionValue(optionKey);
1575
+ const negativeOption = this.options.find((target) => target.negate && optionKey === target.attributeName());
1576
+ const positiveOption = this.options.find((target) => !target.negate && optionKey === target.attributeName());
1577
+ if (negativeOption && (negativeOption.presetArg === undefined && optionValue === false || negativeOption.presetArg !== undefined && optionValue === negativeOption.presetArg)) {
1578
+ return negativeOption;
1579
+ }
1580
+ return positiveOption || option2;
1581
+ };
1582
+ const getErrorMessage = (option2) => {
1583
+ const bestOption = findBestOptionFromValue(option2);
1584
+ const optionKey = bestOption.attributeName();
1585
+ const source = this.getOptionValueSource(optionKey);
1586
+ if (source === "env") {
1587
+ return `environment variable '${bestOption.envVar}'`;
1588
+ }
1589
+ return `option '${bestOption.flags}'`;
1590
+ };
1591
+ const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
1592
+ this.error(message, { code: "commander.conflictingOption" });
1593
+ }
1594
+ unknownOption(flag) {
1595
+ if (this._allowUnknownOption)
1596
+ return;
1597
+ let suggestion = "";
1598
+ if (flag.startsWith("--") && this._showSuggestionAfterError) {
1599
+ let candidateFlags = [];
1600
+ let command = this;
1601
+ do {
1602
+ const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long);
1603
+ candidateFlags = candidateFlags.concat(moreFlags);
1604
+ command = command.parent;
1605
+ } while (command && !command._enablePositionalOptions);
1606
+ suggestion = suggestSimilar(flag, candidateFlags);
1607
+ }
1608
+ const message = `error: unknown option '${flag}'${suggestion}`;
1609
+ this.error(message, { code: "commander.unknownOption" });
1610
+ }
1611
+ _excessArguments(receivedArgs) {
1612
+ if (this._allowExcessArguments)
1613
+ return;
1614
+ const expected = this.registeredArguments.length;
1615
+ const s = expected === 1 ? "" : "s";
1616
+ const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
1617
+ const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
1618
+ this.error(message, { code: "commander.excessArguments" });
1619
+ }
1620
+ unknownCommand() {
1621
+ const unknownName = this.args[0];
1622
+ let suggestion = "";
1623
+ if (this._showSuggestionAfterError) {
1624
+ const candidateNames = [];
1625
+ this.createHelp().visibleCommands(this).forEach((command) => {
1626
+ candidateNames.push(command.name());
1627
+ if (command.alias())
1628
+ candidateNames.push(command.alias());
1629
+ });
1630
+ suggestion = suggestSimilar(unknownName, candidateNames);
1631
+ }
1632
+ const message = `error: unknown command '${unknownName}'${suggestion}`;
1633
+ this.error(message, { code: "commander.unknownCommand" });
1634
+ }
1635
+ version(str, flags, description) {
1636
+ if (str === undefined)
1637
+ return this._version;
1638
+ this._version = str;
1639
+ flags = flags || "-V, --version";
1640
+ description = description || "output the version number";
1641
+ const versionOption = this.createOption(flags, description);
1642
+ this._versionOptionName = versionOption.attributeName();
1643
+ this._registerOption(versionOption);
1644
+ this.on("option:" + versionOption.name(), () => {
1645
+ this._outputConfiguration.writeOut(`${str}
1646
+ `);
1647
+ this._exit(0, "commander.version", str);
1648
+ });
1649
+ return this;
1650
+ }
1651
+ description(str, argsDescription) {
1652
+ if (str === undefined && argsDescription === undefined)
1653
+ return this._description;
1654
+ this._description = str;
1655
+ if (argsDescription) {
1656
+ this._argsDescription = argsDescription;
1657
+ }
1658
+ return this;
1659
+ }
1660
+ summary(str) {
1661
+ if (str === undefined)
1662
+ return this._summary;
1663
+ this._summary = str;
1664
+ return this;
1665
+ }
1666
+ alias(alias) {
1667
+ if (alias === undefined)
1668
+ return this._aliases[0];
1669
+ let command = this;
1670
+ if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
1671
+ command = this.commands[this.commands.length - 1];
1672
+ }
1673
+ if (alias === command._name)
1674
+ throw new Error("Command alias can't be the same as its name");
1675
+ const matchingCommand = this.parent?._findCommand(alias);
1676
+ if (matchingCommand) {
1677
+ const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|");
1678
+ throw new Error(`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`);
1679
+ }
1680
+ command._aliases.push(alias);
1681
+ return this;
1682
+ }
1683
+ aliases(aliases) {
1684
+ if (aliases === undefined)
1685
+ return this._aliases;
1686
+ aliases.forEach((alias) => this.alias(alias));
1687
+ return this;
1688
+ }
1689
+ usage(str) {
1690
+ if (str === undefined) {
1691
+ if (this._usage)
1692
+ return this._usage;
1693
+ const args = this.registeredArguments.map((arg) => {
1694
+ return humanReadableArgName(arg);
1695
+ });
1696
+ return [].concat(this.options.length || this._helpOption !== null ? "[options]" : [], this.commands.length ? "[command]" : [], this.registeredArguments.length ? args : []).join(" ");
1697
+ }
1698
+ this._usage = str;
1699
+ return this;
1700
+ }
1701
+ name(str) {
1702
+ if (str === undefined)
1703
+ return this._name;
1704
+ this._name = str;
1705
+ return this;
1706
+ }
1707
+ nameFromFilename(filename) {
1708
+ this._name = path.basename(filename, path.extname(filename));
1709
+ return this;
1710
+ }
1711
+ executableDir(path2) {
1712
+ if (path2 === undefined)
1713
+ return this._executableDir;
1714
+ this._executableDir = path2;
1715
+ return this;
1716
+ }
1717
+ helpInformation(contextOptions) {
1718
+ const helper = this.createHelp();
1719
+ if (helper.helpWidth === undefined) {
1720
+ helper.helpWidth = contextOptions && contextOptions.error ? this._outputConfiguration.getErrHelpWidth() : this._outputConfiguration.getOutHelpWidth();
1721
+ }
1722
+ return helper.formatHelp(this, helper);
1723
+ }
1724
+ _getHelpContext(contextOptions) {
1725
+ contextOptions = contextOptions || {};
1726
+ const context = { error: !!contextOptions.error };
1727
+ let write;
1728
+ if (context.error) {
1729
+ write = (arg) => this._outputConfiguration.writeErr(arg);
1730
+ } else {
1731
+ write = (arg) => this._outputConfiguration.writeOut(arg);
1732
+ }
1733
+ context.write = contextOptions.write || write;
1734
+ context.command = this;
1735
+ return context;
1736
+ }
1737
+ outputHelp(contextOptions) {
1738
+ let deprecatedCallback;
1739
+ if (typeof contextOptions === "function") {
1740
+ deprecatedCallback = contextOptions;
1741
+ contextOptions = undefined;
1742
+ }
1743
+ const context = this._getHelpContext(contextOptions);
1744
+ this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", context));
1745
+ this.emit("beforeHelp", context);
1746
+ let helpInformation = this.helpInformation(context);
1747
+ if (deprecatedCallback) {
1748
+ helpInformation = deprecatedCallback(helpInformation);
1749
+ if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
1750
+ throw new Error("outputHelp callback must return a string or a Buffer");
1751
+ }
1752
+ }
1753
+ context.write(helpInformation);
1754
+ if (this._getHelpOption()?.long) {
1755
+ this.emit(this._getHelpOption().long);
1756
+ }
1757
+ this.emit("afterHelp", context);
1758
+ this._getCommandAndAncestors().forEach((command) => command.emit("afterAllHelp", context));
1759
+ }
1760
+ helpOption(flags, description) {
1761
+ if (typeof flags === "boolean") {
1762
+ if (flags) {
1763
+ this._helpOption = this._helpOption ?? undefined;
1764
+ } else {
1765
+ this._helpOption = null;
1766
+ }
1767
+ return this;
1768
+ }
1769
+ flags = flags ?? "-h, --help";
1770
+ description = description ?? "display help for command";
1771
+ this._helpOption = this.createOption(flags, description);
1772
+ return this;
1773
+ }
1774
+ _getHelpOption() {
1775
+ if (this._helpOption === undefined) {
1776
+ this.helpOption(undefined, undefined);
1777
+ }
1778
+ return this._helpOption;
1779
+ }
1780
+ addHelpOption(option) {
1781
+ this._helpOption = option;
1782
+ return this;
1783
+ }
1784
+ help(contextOptions) {
1785
+ this.outputHelp(contextOptions);
1786
+ let exitCode = process2.exitCode || 0;
1787
+ if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
1788
+ exitCode = 1;
1789
+ }
1790
+ this._exit(exitCode, "commander.help", "(outputHelp)");
1791
+ }
1792
+ addHelpText(position, text) {
1793
+ const allowedValues = ["beforeAll", "before", "after", "afterAll"];
1794
+ if (!allowedValues.includes(position)) {
1795
+ throw new Error(`Unexpected value for position to addHelpText.
1796
+ Expecting one of '${allowedValues.join("', '")}'`);
1797
+ }
1798
+ const helpEvent = `${position}Help`;
1799
+ this.on(helpEvent, (context) => {
1800
+ let helpStr;
1801
+ if (typeof text === "function") {
1802
+ helpStr = text({ error: context.error, command: context.command });
1803
+ } else {
1804
+ helpStr = text;
1805
+ }
1806
+ if (helpStr) {
1807
+ context.write(`${helpStr}
1808
+ `);
1809
+ }
1810
+ });
1811
+ return this;
1812
+ }
1813
+ _outputHelpIfRequested(args) {
1814
+ const helpOption = this._getHelpOption();
1815
+ const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
1816
+ if (helpRequested) {
1817
+ this.outputHelp();
1818
+ this._exit(0, "commander.helpDisplayed", "(outputHelp)");
1819
+ }
1820
+ }
1821
+ }
1822
+ function incrementNodeInspectorPort(args) {
1823
+ return args.map((arg) => {
1824
+ if (!arg.startsWith("--inspect")) {
1825
+ return arg;
1826
+ }
1827
+ let debugOption;
1828
+ let debugHost = "127.0.0.1";
1829
+ let debugPort = "9229";
1830
+ let match;
1831
+ if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
1832
+ debugOption = match[1];
1833
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
1834
+ debugOption = match[1];
1835
+ if (/^\d+$/.test(match[3])) {
1836
+ debugPort = match[3];
1837
+ } else {
1838
+ debugHost = match[3];
1839
+ }
1840
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
1841
+ debugOption = match[1];
1842
+ debugHost = match[3];
1843
+ debugPort = match[4];
1844
+ }
1845
+ if (debugOption && debugPort !== "0") {
1846
+ return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
1847
+ }
1848
+ return arg;
1849
+ });
1850
+ }
1851
+ exports.Command = Command;
1852
+ });
1853
+
1854
+ // node_modules/commander/index.js
1855
+ var require_commander = __commonJS((exports) => {
1856
+ var { Argument } = require_argument();
1857
+ var { Command } = require_command();
1858
+ var { CommanderError, InvalidArgumentError } = require_error();
1859
+ var { Help } = require_help();
1860
+ var { Option } = require_option();
1861
+ exports.program = new Command;
1862
+ exports.createCommand = (name) => new Command(name);
1863
+ exports.createOption = (flags, description) => new Option(flags, description);
1864
+ exports.createArgument = (name, description) => new Argument(name, description);
1865
+ exports.Command = Command;
1866
+ exports.Option = Option;
1867
+ exports.Argument = Argument;
1868
+ exports.Help = Help;
1869
+ exports.CommanderError = CommanderError;
1870
+ exports.InvalidArgumentError = InvalidArgumentError;
1871
+ exports.InvalidOptionArgumentError = InvalidArgumentError;
1872
+ });
1873
+
1874
+ // node_modules/commander/esm.mjs
1875
+ var import__ = __toESM(require_commander(), 1);
1876
+ var {
1877
+ program,
1878
+ createCommand,
1879
+ createArgument,
1880
+ createOption,
1881
+ CommanderError,
1882
+ InvalidArgumentError,
1883
+ InvalidOptionArgumentError,
1884
+ Command,
1885
+ Argument,
1886
+ Option,
1887
+ Help
1888
+ } = import__.default;
1889
+
1890
+ // src/server/maintenance-entry.ts
1891
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "fs";
1892
+ import { createHash as createHash3 } from "crypto";
1893
+
1894
+ // src/server/execution-grant-store.ts
1895
+ import { randomUUID } from "crypto";
1896
+ function next(p, id, grants, expected) {
1897
+ return {
1898
+ schema: "hasna.skills-execution-grants.v1",
1899
+ profileId: id,
1900
+ workspaceId: p.orgId,
1901
+ revision: randomUUID(),
1902
+ previousRevision: expected,
1903
+ updatedAt: new Date().toISOString(),
1904
+ actorId: p.userId,
1905
+ grants: structuredClone(grants)
1906
+ };
1907
+ }
1908
+ function document(row) {
1909
+ return row ? typeof row.document_json === "string" ? JSON.parse(row.document_json) : structuredClone(row.document_json) : null;
1910
+ }
1911
+
1912
+ class MemoryExecutionGrantStore {
1913
+ current = new Map;
1914
+ history = new Map;
1915
+ async get(p, id, revision) {
1916
+ return structuredClone((revision ? this.history.get(JSON.stringify([p.orgId, id, revision])) : this.current.get(JSON.stringify([p.orgId, id]))) ?? null);
1917
+ }
1918
+ async save(p, id, grants, expected) {
1919
+ const key = JSON.stringify([p.orgId, id]);
1920
+ if ((this.current.get(key)?.revision ?? null) !== expected)
1921
+ return null;
1922
+ const value = next(p, id, grants, expected);
1923
+ this.current.set(key, value);
1924
+ this.history.set(JSON.stringify([p.orgId, id, value.revision]), value);
1925
+ return structuredClone(value);
1926
+ }
1927
+ }
1928
+
1929
+ class SqliteExecutionGrantStore {
1930
+ db;
1931
+ constructor(db) {
1932
+ this.db = db;
1933
+ }
1934
+ async get(p, id, revision) {
1935
+ return document(revision ? this.db.query("SELECT document_json FROM skills_execution_grant_revisions WHERE org_id=? AND profile_id=? AND revision=?").get(p.orgId, id, revision) : this.db.query("SELECT document_json FROM skills_execution_grants WHERE org_id=? AND profile_id=?").get(p.orgId, id));
1936
+ }
1937
+ async save(p, id, grants, expected) {
1938
+ return this.db.transaction(() => {
1939
+ const value = next(p, id, grants, expected), json = JSON.stringify(value);
1940
+ const row = expected === null ? this.db.query("INSERT INTO skills_execution_grants(org_id,profile_id,revision,document_json) VALUES(?,?,?,?) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING document_json").get(p.orgId, id, value.revision, json) : this.db.query("UPDATE skills_execution_grants SET revision=?,document_json=? WHERE org_id=? AND profile_id=? AND revision=? RETURNING document_json").get(value.revision, json, p.orgId, id, expected);
1941
+ if (!row)
1942
+ return null;
1943
+ this.db.query("INSERT INTO skills_execution_grant_revisions(org_id,profile_id,revision,document_json) VALUES(?,?,?,?)").run(p.orgId, id, value.revision, json);
1944
+ return document(row);
1945
+ })();
1946
+ }
1947
+ }
1948
+
1949
+ class PostgresExecutionGrantStore {
1950
+ sql;
1951
+ constructor(sql) {
1952
+ this.sql = sql;
1953
+ }
1954
+ async get(p, id, revision) {
1955
+ const rows = revision ? await this.sql`SELECT document_json FROM skills_execution_grant_revisions WHERE org_id=${p.orgId} AND profile_id=${id} AND revision=${revision}` : await this.sql`SELECT document_json FROM skills_execution_grants WHERE org_id=${p.orgId} AND profile_id=${id}`;
1956
+ return document(rows[0]);
1957
+ }
1958
+ async save(p, id, grants, expected) {
1959
+ const value = next(p, id, grants, expected), json = JSON.stringify(value);
1960
+ const rows = expected === null ? await this.sql`WITH changed AS (INSERT INTO skills_execution_grants(org_id,profile_id,revision,document_json) VALUES(${p.orgId},${id},${value.revision},${json}::jsonb) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *) INSERT INTO skills_execution_grant_revisions(org_id,profile_id,revision,document_json) SELECT org_id,profile_id,revision,document_json FROM changed RETURNING document_json` : await this.sql`WITH changed AS (UPDATE skills_execution_grants SET revision=${value.revision},document_json=${json}::jsonb WHERE org_id=${p.orgId} AND profile_id=${id} AND revision=${expected} RETURNING *) INSERT INTO skills_execution_grant_revisions(org_id,profile_id,revision,document_json) SELECT org_id,profile_id,revision,document_json FROM changed RETURNING document_json`;
1961
+ return document(rows[0]);
1962
+ }
1963
+ }
1964
+
1965
+ // src/server/store.ts
1966
+ import { randomUUID as randomUUID5 } from "crypto";
1967
+
1968
+ // src/server/selection-store.ts
1969
+ import { randomUUID as randomUUID2 } from "crypto";
1970
+ function selections(value) {
1971
+ return typeof value === "string" ? JSON.parse(value) : structuredClone(value);
1972
+ }
1973
+ function time(value) {
1974
+ return value instanceof Date ? value.toISOString() : String(value);
1975
+ }
1976
+ function profile(row) {
1977
+ return {
1978
+ id: String(row.profile_id),
1979
+ workspaceId: String(row.org_id),
1980
+ revision: String(row.revision),
1981
+ selections: selections(row.selections_json),
1982
+ updatedAt: time(row.updated_at)
1983
+ };
1984
+ }
1985
+ function state(row) {
1986
+ return {
1987
+ stationId: String(row.station_id),
1988
+ workspaceId: String(row.org_id),
1989
+ actorId: String(row.actor_id),
1990
+ profileId: String(row.profile_id),
1991
+ profileRevision: String(row.profile_revision),
1992
+ selections: selections(row.selections_json),
1993
+ appliedAt: time(row.applied_at)
1994
+ };
1995
+ }
1996
+
1997
+ class MemorySkillSelectionStore {
1998
+ profiles = new Map;
1999
+ states = new Map;
2000
+ key(...values) {
2001
+ return JSON.stringify(values);
2002
+ }
2003
+ async getProfile(p, id) {
2004
+ return structuredClone(this.profiles.get(this.key(p.orgId, id)) ?? null);
2005
+ }
2006
+ async profilesReferencingSkill(p, slug) {
2007
+ return [...this.profiles.values()].filter((profile2) => profile2.workspaceId === p.orgId && profile2.selections.some((selection) => selection.slug === slug)).map((profile2) => profile2.id).sort();
2008
+ }
2009
+ async saveProfile(p, id, selected, expected) {
2010
+ const key = this.key(p.orgId, id), previous = this.profiles.get(key);
2011
+ if ((previous?.revision ?? null) !== expected)
2012
+ return null;
2013
+ const next2 = {
2014
+ id,
2015
+ workspaceId: p.orgId,
2016
+ revision: randomUUID2(),
2017
+ selections: structuredClone(selected),
2018
+ updatedAt: new Date().toISOString()
2019
+ };
2020
+ this.profiles.set(key, next2);
2021
+ return structuredClone(next2);
2022
+ }
2023
+ async getStationState(p, id) {
2024
+ return structuredClone(this.states.get(this.key(p.orgId, p.userId, id)) ?? null);
2025
+ }
2026
+ async saveStationState(p, id, input) {
2027
+ if (this.profiles.get(this.key(p.orgId, input.profileId))?.revision !== input.profileRevision)
2028
+ return null;
2029
+ const next2 = {
2030
+ ...structuredClone(input),
2031
+ stationId: id,
2032
+ workspaceId: p.orgId,
2033
+ actorId: p.userId,
2034
+ appliedAt: new Date().toISOString()
2035
+ };
2036
+ this.states.set(this.key(p.orgId, p.userId, id), next2);
2037
+ return structuredClone(next2);
2038
+ }
2039
+ }
2040
+
2041
+ class SqliteSkillSelectionStore {
2042
+ db;
2043
+ constructor(db) {
2044
+ this.db = db;
2045
+ }
2046
+ async profilesReferencingSkill(p, slug) {
2047
+ const rows = this.db.query("SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=?").all(p.orgId);
2048
+ return rows.filter((row) => selections(row.selections_json).some((selection) => selection.slug === slug)).map((row) => String(row.profile_id)).sort();
2049
+ }
2050
+ async getProfile(p, id) {
2051
+ const row = this.db.query("SELECT * FROM skills_profiles WHERE org_id=? AND profile_id=?").get(p.orgId, id);
2052
+ return row ? profile(row) : null;
2053
+ }
2054
+ async saveProfile(p, id, selected, expected) {
2055
+ return this.db.transaction(() => {
2056
+ for (const selection of [...selected].sort((a, b) => a.slug.localeCompare(b.slug))) {
2057
+ const skill = this.db.query("SELECT lifecycle FROM skills_registry WHERE org_id=? AND slug=?").get(p.orgId, selection.slug);
2058
+ if (skill?.lifecycle === "archived")
2059
+ return null;
2060
+ }
2061
+ const revision = randomUUID2(), timestamp = new Date().toISOString();
2062
+ const row = expected === null ? this.db.query("INSERT INTO skills_profiles(org_id,profile_id,revision,selections_json,updated_at) VALUES(?,?,?,?,?) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *").get(p.orgId, id, revision, JSON.stringify(selected), timestamp) : this.db.query("UPDATE skills_profiles SET revision=?,selections_json=?,updated_at=? WHERE org_id=? AND profile_id=? AND revision=? RETURNING *").get(revision, JSON.stringify(selected), timestamp, p.orgId, id, expected);
2063
+ return row ? profile(row) : null;
2064
+ }).immediate();
2065
+ }
2066
+ async getStationState(p, id) {
2067
+ const row = this.db.query("SELECT * FROM skills_station_state WHERE org_id=? AND actor_id=? AND station_id=?").get(p.orgId, p.userId, id);
2068
+ return row ? state(row) : null;
2069
+ }
2070
+ async saveStationState(p, id, input) {
2071
+ const row = this.db.query(`INSERT INTO skills_station_state(org_id,actor_id,station_id,profile_id,profile_revision,selections_json,applied_at)
2072
+ SELECT ?,?,?,?,?,?,? WHERE EXISTS(SELECT 1 FROM skills_profiles WHERE org_id=? AND profile_id=? AND revision=?)
2073
+ ON CONFLICT(org_id,actor_id,station_id) DO UPDATE SET profile_id=excluded.profile_id,profile_revision=excluded.profile_revision,selections_json=excluded.selections_json,applied_at=excluded.applied_at RETURNING *`).get(p.orgId, p.userId, id, input.profileId, input.profileRevision, JSON.stringify(input.selections), new Date().toISOString(), p.orgId, input.profileId, input.profileRevision);
2074
+ return row ? state(row) : null;
2075
+ }
2076
+ }
2077
+
2078
+ class PostgresSkillSelectionStore {
2079
+ sql;
2080
+ constructor(sql) {
2081
+ this.sql = sql;
2082
+ }
2083
+ async profilesReferencingSkill(p, slug) {
2084
+ const rows = await this.sql`SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=${p.orgId}`;
2085
+ return rows.filter((row) => selections(row.selections_json).some((selection) => selection.slug === slug)).map((row) => String(row.profile_id)).sort();
2086
+ }
2087
+ async getProfile(p, id) {
2088
+ const rows = await this.sql`SELECT * FROM skills_profiles WHERE org_id=${p.orgId} AND profile_id=${id}`;
2089
+ return rows[0] ? profile(rows[0]) : null;
2090
+ }
2091
+ async saveProfile(p, id, selected, expected) {
2092
+ return this.sql.begin(async (tx) => {
2093
+ for (const selection of [...selected].sort((a, b) => a.slug.localeCompare(b.slug))) {
2094
+ const rows2 = await tx`SELECT lifecycle FROM skills_registry WHERE org_id=${p.orgId} AND slug=${selection.slug} FOR UPDATE`;
2095
+ if (rows2[0]?.lifecycle === "archived")
2096
+ return null;
2097
+ }
2098
+ const revision = randomUUID2(), timestamp = new Date().toISOString();
2099
+ const rows = expected === null ? await tx`INSERT INTO skills_profiles(org_id,profile_id,revision,selections_json,updated_at) VALUES(${p.orgId},${id},${revision},${JSON.stringify(selected)}::jsonb,${timestamp}) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *` : await tx`UPDATE skills_profiles SET revision=${revision},selections_json=${JSON.stringify(selected)}::jsonb,updated_at=${timestamp} WHERE org_id=${p.orgId} AND profile_id=${id} AND revision=${expected} RETURNING *`;
2100
+ return rows[0] ? profile(rows[0]) : null;
2101
+ });
2102
+ }
2103
+ async getStationState(p, id) {
2104
+ const rows = await this.sql`SELECT * FROM skills_station_state WHERE org_id=${p.orgId} AND actor_id=${p.userId} AND station_id=${id}`;
2105
+ return rows[0] ? state(rows[0]) : null;
2106
+ }
2107
+ async saveStationState(p, id, input) {
2108
+ const rows = await this.sql`INSERT INTO skills_station_state(org_id,actor_id,station_id,profile_id,profile_revision,selections_json,applied_at)
2109
+ SELECT ${p.orgId},${p.userId},${id},${input.profileId},${input.profileRevision},${JSON.stringify(input.selections)}::jsonb,${new Date().toISOString()}
2110
+ WHERE EXISTS(SELECT 1 FROM skills_profiles WHERE org_id=${p.orgId} AND profile_id=${input.profileId} AND revision=${input.profileRevision})
2111
+ ON CONFLICT(org_id,actor_id,station_id) DO UPDATE SET profile_id=excluded.profile_id,profile_revision=excluded.profile_revision,selections_json=excluded.selections_json,applied_at=excluded.applied_at RETURNING *`;
2112
+ return rows[0] ? state(rows[0]) : null;
2113
+ }
2114
+ }
2115
+
2116
+ // src/server/types.ts
2117
+ class StaleLeaseGenerationError extends Error {
2118
+ runId;
2119
+ expectedGeneration;
2120
+ currentGeneration;
2121
+ status;
2122
+ constructor(runId, expectedGeneration, currentGeneration, status) {
2123
+ super(`late transition rejected for run ${runId}: expected lease_generation ${expectedGeneration}, ` + `current ${currentGeneration} (status ${status})`);
2124
+ this.name = "StaleLeaseGenerationError";
2125
+ this.runId = runId;
2126
+ this.expectedGeneration = expectedGeneration;
2127
+ this.currentGeneration = currentGeneration;
2128
+ this.status = status;
2129
+ }
2130
+ }
2131
+
2132
+ class SkillRevisionConflictError extends Error {
2133
+ slug;
2134
+ expectedRevisionId;
2135
+ currentRevisionId;
2136
+ constructor(slug, expectedRevisionId, currentRevisionId) {
2137
+ super(`revision conflict for '${slug}': expected revision ${expectedRevisionId ?? "(none)"}, ` + `current is ${currentRevisionId ?? "(none)"}. Refused rather than silently overwriting a newer revision.`);
2138
+ this.name = "SkillRevisionConflictError";
2139
+ this.slug = slug;
2140
+ this.expectedRevisionId = expectedRevisionId;
2141
+ this.currentRevisionId = currentRevisionId;
2142
+ }
2143
+ }
2144
+
2145
+ class SkillVersionExistsError extends Error {
2146
+ slug;
2147
+ version;
2148
+ existingSha256;
2149
+ attemptedSha256;
2150
+ constructor(slug, version, existingSha256, attemptedSha256) {
2151
+ super(`version conflict for '${slug}@${version}': already published with bundle ${existingSha256}, ` + `refusing to overwrite it with ${attemptedSha256}. Publish a new version instead.`);
2152
+ this.slug = slug;
2153
+ this.version = version;
2154
+ this.existingSha256 = existingSha256;
2155
+ this.attemptedSha256 = attemptedSha256;
2156
+ this.name = "SkillVersionExistsError";
2157
+ }
2158
+ }
2159
+
2160
+ class SkillLifecycleConflictError extends Error {
2161
+ slug;
2162
+ profiles;
2163
+ constructor(slug, profiles) {
2164
+ super(`cannot archive '${slug}' while active profiles reference it: ${profiles.join(", ")}`);
2165
+ this.slug = slug;
2166
+ this.profiles = profiles;
2167
+ this.name = "SkillLifecycleConflictError";
2168
+ }
2169
+ }
2170
+ function validOperatorScopeEnrollmentInput(input) {
2171
+ const bounded = (value, max = 256) => typeof value === "string" && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
2172
+ return bounded(input.keyId) && bounded(input.stationId) && bounded(input.orgId) && bounded(input.operationId) && bounded(input.manifestDigest, 64) && /^[a-f0-9]{64}$/.test(input.manifestDigest) && bounded(input.operatorJobId) && bounded(input.operatorTaskArn, 512) && input.expectedScopes.length > 0 && input.expectedScopes.length <= 32 && input.expectedScopes.every((scope) => bounded(scope, 128) && /^[a-z][a-z0-9_-]*:[a-z][a-z0-9_-]*$/.test(scope));
2173
+ }
2174
+ function validOperatorScopeList(scopes) {
2175
+ return Array.isArray(scopes) && scopes.length <= 32 && scopes.every((scope) => typeof scope === "string" && scope.length > 0 && scope.length <= 128 && /^[a-z][a-z0-9_-]*:[a-z][a-z0-9_-]*$/.test(scope));
2176
+ }
2177
+
2178
+ // src/server/auth.ts
2179
+ import { createHash } from "crypto";
2180
+ function hashApiKey(token) {
2181
+ return createHash("sha256").update(token).digest("hex");
2182
+ }
2183
+ function publicPrincipal(partial = {}) {
2184
+ return {
2185
+ apiKeyId: partial.apiKeyId || "key_dev",
2186
+ orgId: partial.orgId || "org_dev",
2187
+ orgSlug: partial.orgSlug || "dev",
2188
+ orgName: partial.orgName || "Development",
2189
+ userId: partial.userId || "user_dev",
2190
+ email: partial.email || "dev@example.com",
2191
+ role: partial.role || "owner",
2192
+ scopes: partial.scopes ?? ["*"]
2193
+ };
2194
+ }
2195
+
2196
+ // src/server/database-url.ts
2197
+ import { isAbsolute, join as join2 } from "path";
2198
+ import { fileURLToPath } from "url";
2199
+
2200
+ // src/lib/config.ts
2201
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
2202
+
2203
+ // src/lib/retired-settings.ts
2204
+ var RETIRED_CONFIG_KEY_REASONS = {
2205
+ mode: "Deployment modes were removed: a Skills client either resolves a credential " + "or it does not, and that is the whole of it.",
2206
+ apiUrl: "The service address is no longer kept in this app's config file: it is read from " + "HASNA_SKILLS_API_URL, then the macOS Keychain item hasna.credentials.skills.api-url, " + "then ~/.hasna/skills/config/credentials, then the fleet gateway."
2207
+ };
2208
+
2209
+ // src/lib/app-home.ts
2210
+ import { existsSync } from "fs";
2211
+ import { homedir } from "os";
2212
+ import { join, resolve } from "path";
2213
+ import { homedir as pathsResolverHomedir } from "os";
2214
+ import { join as pathsResolverJoin } from "path";
2215
+ var PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME";
2216
+ var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2217
+ function pathsResolverAssertApp(app) {
2218
+ if (typeof app !== "string" || app.length === 0) {
2219
+ throw new TypeError("paths: app must be a non-empty string");
2220
+ }
2221
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
2222
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
2223
+ }
2224
+ }
2225
+ function pathsResolverDataBaseDir(options) {
2226
+ const env = options.env ?? process.env;
2227
+ const override = env[PATHS_RESOLVER_DATA_ENV];
2228
+ if (typeof override === "string" && override.length > 0)
2229
+ return override;
2230
+ const home = options.home ?? pathsResolverHomedir();
2231
+ const platform = options.platform ?? process.platform;
2232
+ return platform === "darwin" ? pathsResolverJoin(home, "Library", "Application Support", "Hasna") : pathsResolverJoin(home, ".local", "share", "hasna");
2233
+ }
2234
+ function dataDir(options) {
2235
+ pathsResolverAssertApp(options.app);
2236
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
2237
+ return pathsResolverJoin(pathsResolverDataBaseDir(options), appSegment);
2238
+ }
2239
+ var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
2240
+ var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
2241
+ var SKILLS_HOME_ENV = "SKILLS_HOME";
2242
+ var DEFAULT_SQLITE_FILENAME = "server.db";
2243
+ var GLOBAL_CONFIG_FILENAME = "config.json";
2244
+ function effectiveHome() {
2245
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
2246
+ }
2247
+ function legacyDataRoot() {
2248
+ return join(effectiveHome(), ".hasna", "skills");
2249
+ }
2250
+ function resolverDataRoot(home = effectiveHome(), env) {
2251
+ return dataDir({ app: "skills", home, env });
2252
+ }
2253
+ function adoptResolverDataRoot(resolved, env = process.env) {
2254
+ const dataOverride = env.HASNA_DATA_HOME;
2255
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
2256
+ return true;
2257
+ return existsSync(join(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join(resolved, GLOBAL_CONFIG_FILENAME));
2258
+ }
2259
+ function exactDataRoot() {
2260
+ for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
2261
+ const dir = process.env[key]?.trim();
2262
+ if (dir)
2263
+ return resolve(dir);
2264
+ }
2265
+ return;
2266
+ }
2267
+ function getDataRoot() {
2268
+ const exact = exactDataRoot();
2269
+ if (exact)
2270
+ return exact;
2271
+ const resolved = resolverDataRoot();
2272
+ return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
2273
+ }
2274
+ // src/lib/config.ts
2275
+ function getDataDir() {
2276
+ const root = getDataRoot();
2277
+ try {
2278
+ mkdirSync(root, { recursive: true });
2279
+ } catch {}
2280
+ return root;
2281
+ }
2282
+
2283
+ // src/server/database-url.ts
2284
+ var DEFAULT_SQLITE_FILENAME2 = "server.db";
2285
+ var SQLITE_MEMORY_PATH = ":memory:";
2286
+ var POSTGRES_SCHEMES = new Set(["postgres", "postgresql"]);
2287
+ var SQLITE_SCHEMES = new Set(["sqlite", "sqlite3", "file"]);
2288
+ var MEMORY_SCHEMES = new Set(["memory"]);
2289
+ var SQLITE_EXTENSIONS = [".db", ".sqlite", ".sqlite3", ".db3"];
2290
+ function defaultSqlitePath() {
2291
+ return join2(getDataDir(), DEFAULT_SQLITE_FILENAME2);
2292
+ }
2293
+ function resolveDatabaseTarget(raw) {
2294
+ const value = raw?.trim();
2295
+ if (!value) {
2296
+ const path = defaultSqlitePath();
2297
+ return { kind: "sqlite", path, durable: true, label: `sqlite (${path})` };
2298
+ }
2299
+ if (value === SQLITE_MEMORY_PATH) {
2300
+ return { kind: "sqlite", path: SQLITE_MEMORY_PATH, durable: false, label: "sqlite (in-memory)" };
2301
+ }
2302
+ const schemeMatch = value.match(/^([a-z][a-z0-9+.-]+):/i);
2303
+ const scheme = schemeMatch?.[1]?.toLowerCase();
2304
+ if (scheme && POSTGRES_SCHEMES.has(scheme)) {
2305
+ return { kind: "postgres", url: value, durable: true, label: "postgres" };
2306
+ }
2307
+ if (scheme && MEMORY_SCHEMES.has(scheme)) {
2308
+ return { kind: "memory", durable: false, label: "memory (non-durable)" };
2309
+ }
2310
+ if (scheme && SQLITE_SCHEMES.has(scheme)) {
2311
+ const path = sqlitePathFromUrl(value, scheme);
2312
+ return path === SQLITE_MEMORY_PATH ? { kind: "sqlite", path, durable: false, label: "sqlite (in-memory)" } : { kind: "sqlite", path, durable: true, label: `sqlite (${path})` };
2313
+ }
2314
+ if (scheme) {
2315
+ throw new Error(`unsupported database scheme "${scheme}:". Supported: postgres://, postgresql://, sqlite:, file:, ` + `an absolute or relative path to a .db/.sqlite file, ":memory:", or "memory:" (non-durable, tests only). ` + `Leave the setting empty to use the default SQLite database at ${defaultSqlitePath()}.`);
2316
+ }
2317
+ if (looksLikeSqlitePath(value)) {
2318
+ const path = isAbsolute(value) ? value : join2(process.cwd(), value);
2319
+ return { kind: "sqlite", path, durable: true, label: `sqlite (${path})` };
2320
+ }
2321
+ throw new Error(`could not resolve a database backend from "${value}". Use postgres://\u2026, sqlite:\u2026, an absolute or ` + `relative path ending in ${SQLITE_EXTENSIONS.join("/")}, ":memory:", or leave it empty for the ` + `default SQLite database at ${defaultSqlitePath()}.`);
2322
+ }
2323
+ function sqlitePathFromUrl(value, scheme) {
2324
+ const rest = value.slice(scheme.length + 1).replace(/[?#].*$/, "");
2325
+ if (rest.startsWith(SQLITE_MEMORY_PATH))
2326
+ return SQLITE_MEMORY_PATH;
2327
+ if (rest === "" || rest === "//") {
2328
+ throw new Error(`"${value}" names no database file. Give a path (${scheme}:///var/lib/skills/server.db), ` + `or ":memory:" if a non-durable database is genuinely what you want. ` + "An empty path here usually means an unset variable in a templated URL.");
2329
+ }
2330
+ if (rest.startsWith("//")) {
2331
+ if (!rest.startsWith("///")) {
2332
+ const authority = rest.slice(2).split("/")[0];
2333
+ throw new Error(`"${value}" has a host ("${authority}") where a path was expected. Remote SQLite files are not a thing; ` + `for an absolute path use ${scheme}:///${rest.slice(2)}, and for a relative one use ${scheme}:${rest.slice(2)}.`);
2334
+ }
2335
+ if (scheme === "file") {
2336
+ try {
2337
+ return fileURLToPath(new URL(value));
2338
+ } catch {}
2339
+ }
2340
+ return rest.slice(2).replace(/^\/\/+/, "/");
2341
+ }
2342
+ return isAbsolute(rest) ? rest : join2(process.cwd(), rest);
2343
+ }
2344
+ function looksLikeSqlitePath(value) {
2345
+ if (value.includes("/"))
2346
+ return true;
2347
+ return SQLITE_EXTENSIONS.some((extension) => value.toLowerCase().endsWith(extension));
2348
+ }
2349
+
2350
+ // src/server/rows.ts
2351
+ import { randomUUID as randomUUID3 } from "crypto";
2352
+
2353
+ // src/lib/skill-entry-path.ts
2354
+ class SkillEntryPaths {
2355
+ files = new Set;
2356
+ directories = new Set;
2357
+ add(path, maxBytes, invalid, limit) {
2358
+ if (path.length > maxBytes)
2359
+ limit();
2360
+ const encoded = new TextEncoder().encode(path);
2361
+ if (encoded.byteLength > maxBytes)
2362
+ limit();
2363
+ if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(encoded) !== path)
2364
+ invalid("Invalid UTF-8 entry path");
2365
+ if (!path || /[\\:\x00-\x1f\x7f]/u.test(path))
2366
+ invalid("Unsafe entry path");
2367
+ if (path.split("/").some((segment) => !segment || segment === "." || segment === ".."))
2368
+ invalid("Unsafe entry path segment");
2369
+ const key = path.normalize("NFC").toLowerCase().normalize("NFC");
2370
+ if (this.files.has(key) || this.directories.has(key))
2371
+ invalid("Duplicate or conflicting entry path");
2372
+ const parents = key.split("/");
2373
+ parents.pop();
2374
+ while (parents.length) {
2375
+ const parent = parents.join("/");
2376
+ if (this.files.has(parent))
2377
+ invalid("Conflicting entry file ancestor");
2378
+ this.directories.add(parent);
2379
+ parents.pop();
2380
+ }
2381
+ this.files.add(key);
2382
+ }
2383
+ }
2384
+
2385
+ // src/lib/skill-bundle.ts
2386
+ var BLOCK = 512;
2387
+ var ANY_SEGMENT_EXCLUDES = new Set([
2388
+ ".git",
2389
+ ".ds_store",
2390
+ ".system",
2391
+ "node_modules",
2392
+ ".aws",
2393
+ ".ssh",
2394
+ ".gnupg",
2395
+ ".docker"
2396
+ ]);
2397
+ var ROOT_EXCLUDES = new Set(["dist", "build", ".turbo"]);
2398
+ var TOOL_SIDECAR_FILENAMES = new Set([".hasna-skills.json", ".skills-dependency-preparation"]);
2399
+ var CREDENTIAL_FILENAMES = new Set([
2400
+ ".npmrc",
2401
+ ".pypirc",
2402
+ ".netrc",
2403
+ ".envrc",
2404
+ ".pgpass",
2405
+ ".git-credentials",
2406
+ "credentials",
2407
+ "id_rsa",
2408
+ "id_dsa",
2409
+ "id_ecdsa",
2410
+ "id_ed25519"
2411
+ ]);
2412
+ var ENV_TEMPLATE_NAMES = new Set([".env.example", ".env.sample", ".env.template", ".env.dist"]);
2413
+ var NON_DOTENV_EXTENSIONS = new Set([
2414
+ "ts",
2415
+ "tsx",
2416
+ "mts",
2417
+ "cts",
2418
+ "js",
2419
+ "jsx",
2420
+ "mjs",
2421
+ "cjs",
2422
+ "json",
2423
+ "jsonc",
2424
+ "json5",
2425
+ "py",
2426
+ "rb",
2427
+ "go",
2428
+ "rs",
2429
+ "java",
2430
+ "kt",
2431
+ "kts",
2432
+ "swift",
2433
+ "c",
2434
+ "h",
2435
+ "cc",
2436
+ "cpp",
2437
+ "hpp",
2438
+ "cs",
2439
+ "php",
2440
+ "sh",
2441
+ "bash",
2442
+ "zsh",
2443
+ "fish",
2444
+ "ps1",
2445
+ "bat",
2446
+ "cmd",
2447
+ "lua",
2448
+ "pl",
2449
+ "pm",
2450
+ "r",
2451
+ "jl",
2452
+ "dart",
2453
+ "ex",
2454
+ "exs",
2455
+ "scala",
2456
+ "clj",
2457
+ "cljs",
2458
+ "groovy",
2459
+ "gradle",
2460
+ "vb",
2461
+ "fs",
2462
+ "yaml",
2463
+ "yml",
2464
+ "toml",
2465
+ "ini",
2466
+ "xml",
2467
+ "csv",
2468
+ "tsv",
2469
+ "html",
2470
+ "htm",
2471
+ "css",
2472
+ "scss",
2473
+ "sass",
2474
+ "less",
2475
+ "md",
2476
+ "mdx",
2477
+ "txt",
2478
+ "rst",
2479
+ "adoc",
2480
+ "sql",
2481
+ "graphql",
2482
+ "gql",
2483
+ "proto",
2484
+ "d",
2485
+ "png",
2486
+ "jpg",
2487
+ "jpeg",
2488
+ "gif",
2489
+ "svg",
2490
+ "webp",
2491
+ "ico",
2492
+ "bmp",
2493
+ "pdf",
2494
+ "woff",
2495
+ "woff2",
2496
+ "ttf",
2497
+ "otf",
2498
+ "eot",
2499
+ "mp3",
2500
+ "mp4",
2501
+ "wav",
2502
+ "webm",
2503
+ "zip",
2504
+ "gz",
2505
+ "tar",
2506
+ "wasm"
2507
+ ]);
2508
+ function ownBytes(view) {
2509
+ const source = view instanceof ArrayBuffer ? new Uint8Array(view) : view;
2510
+ const out = new Uint8Array(new ArrayBuffer(source.byteLength));
2511
+ out.set(source);
2512
+ return out;
2513
+ }
2514
+ var SKILL_BUNDLE_INSPECTION_LIMITS = Object.freeze({
2515
+ compressedBytes: 16 * 1024 * 1024,
2516
+ decompressedBytes: 64 * 1024 * 1024,
2517
+ entries: 1024,
2518
+ fileBytes: 16 * 1024 * 1024,
2519
+ pathBytes: 100,
2520
+ timeoutMs: 5000
2521
+ });
2522
+
2523
+ class SkillBundleInspectionError extends Error {
2524
+ code;
2525
+ constructor(code, message) {
2526
+ super(message);
2527
+ this.code = code;
2528
+ this.name = "SkillBundleInspectionError";
2529
+ }
2530
+ }
2531
+ function invalidBundle(message) {
2532
+ throw new SkillBundleInspectionError("BUNDLE_INVALID", message);
2533
+ }
2534
+ class BoundedTarReader {
2535
+ limits;
2536
+ check;
2537
+ header = new Uint8Array(BLOCK);
2538
+ headerOffset = 0;
2539
+ pending;
2540
+ bodyOffset = 0;
2541
+ padding = 0;
2542
+ zeroBlocks = 0;
2543
+ entries = [];
2544
+ paths = new SkillEntryPaths;
2545
+ fileBytes = 0;
2546
+ constructor(limits, check) {
2547
+ this.limits = limits;
2548
+ this.check = check;
2549
+ }
2550
+ push(chunk) {
2551
+ let offset = 0;
2552
+ while (offset < chunk.byteLength) {
2553
+ this.check();
2554
+ if (this.pending) {
2555
+ const count = Math.min(this.pending.bytes.byteLength - this.bodyOffset, chunk.byteLength - offset);
2556
+ this.pending.bytes.set(chunk.subarray(offset, offset + count), this.bodyOffset);
2557
+ offset += count;
2558
+ this.bodyOffset += count;
2559
+ if (this.bodyOffset === this.pending.bytes.byteLength) {
2560
+ this.entries.push(this.pending);
2561
+ this.pending = undefined;
2562
+ }
2563
+ } else if (this.padding) {
2564
+ const count = Math.min(this.padding, chunk.byteLength - offset);
2565
+ if (chunk.subarray(offset, offset + count).some((byte) => byte !== 0))
2566
+ invalidBundle("Nonzero tar body padding");
2567
+ offset += count;
2568
+ this.padding -= count;
2569
+ } else {
2570
+ const count = Math.min(BLOCK - this.headerOffset, chunk.byteLength - offset);
2571
+ this.header.set(chunk.subarray(offset, offset + count), this.headerOffset);
2572
+ offset += count;
2573
+ this.headerOffset += count;
2574
+ if (this.headerOffset === BLOCK) {
2575
+ this.readHeader();
2576
+ this.headerOffset = 0;
2577
+ }
2578
+ }
2579
+ }
2580
+ }
2581
+ finish() {
2582
+ this.check();
2583
+ if (this.pending || this.padding || this.headerOffset || this.zeroBlocks < 2)
2584
+ invalidBundle("Truncated tar bundle");
2585
+ return this.entries;
2586
+ }
2587
+ readHeader() {
2588
+ this.check();
2589
+ const h = this.header;
2590
+ if (h.every((byte) => byte === 0)) {
2591
+ this.zeroBlocks++;
2592
+ return;
2593
+ }
2594
+ if (this.zeroBlocks)
2595
+ invalidBundle("Nonzero tar data after terminator");
2596
+ if (this.entries.length >= this.limits.entries)
2597
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle entry limit exceeded");
2598
+ let checksum = 0;
2599
+ for (let i = 0;i < BLOCK; i++)
2600
+ checksum += i >= 148 && i < 156 ? 32 : h[i];
2601
+ if (tarOctal(h.subarray(148, 156)) !== checksum)
2602
+ invalidBundle("Invalid tar header checksum");
2603
+ if (new TextDecoder().decode(h.subarray(257, 265)) !== "ustar\x00" + "00")
2604
+ invalidBundle("Unsupported tar format");
2605
+ if (h[156] !== 48 && h[156] !== 0 || h.subarray(157, 257).some((b) => b !== 0) || h.subarray(345).some((b) => b !== 0))
2606
+ invalidBundle("Unsupported tar entry or path prefix");
2607
+ const mode = tarOctal(h.subarray(100, 108));
2608
+ if (mode > 511)
2609
+ invalidBundle("Unsupported tar permission bits");
2610
+ tarOctal(h.subarray(108, 116));
2611
+ tarOctal(h.subarray(116, 124));
2612
+ tarOctal(h.subarray(136, 148));
2613
+ const size = tarOctal(h.subarray(124, 136));
2614
+ if (size > this.limits.fileBytes || this.fileBytes + size > this.limits.decompressedBytes) {
2615
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle file byte limit exceeded");
2616
+ }
2617
+ const name = h.subarray(0, 100);
2618
+ const end = name.indexOf(0);
2619
+ if (end !== -1 && name.subarray(end).some((b) => b !== 0))
2620
+ invalidBundle("Invalid tar path padding");
2621
+ const raw = end === -1 ? name : name.subarray(0, end);
2622
+ if (raw.byteLength > this.limits.pathBytes)
2623
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
2624
+ let path;
2625
+ try {
2626
+ path = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(raw);
2627
+ } catch {
2628
+ return invalidBundle("Invalid UTF-8 bundle path");
2629
+ }
2630
+ this.paths.add(path, this.limits.pathBytes, invalidBundle, () => {
2631
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
2632
+ });
2633
+ this.fileBytes += size;
2634
+ this.pending = { path, mode, bytes: new Uint8Array(new ArrayBuffer(size)) };
2635
+ this.bodyOffset = 0;
2636
+ this.padding = (BLOCK - size % BLOCK) % BLOCK;
2637
+ if (!size) {
2638
+ this.entries.push(this.pending);
2639
+ this.pending = undefined;
2640
+ }
2641
+ }
2642
+ }
2643
+ function tarOctal(field) {
2644
+ const text = new TextDecoder().decode(field);
2645
+ if (!/^[0-7]+[\0 ]*$/.test(text))
2646
+ invalidBundle("Invalid tar octal field");
2647
+ const value = Number.parseInt(text, 8);
2648
+ if (!Number.isSafeInteger(value))
2649
+ invalidBundle("Tar integer is out of range");
2650
+ return value;
2651
+ }
2652
+
2653
+ // src/server/rows.ts
2654
+ function nowIso() {
2655
+ return new Date().toISOString();
2656
+ }
2657
+ function normalizeLimit(limit) {
2658
+ return Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : 0;
2659
+ }
2660
+ function runId() {
2661
+ return `run_${Date.now().toString(36)}_${randomUUID3().slice(0, 8)}`;
2662
+ }
2663
+ function rowToRun(row) {
2664
+ return {
2665
+ id: String(row.id),
2666
+ orgId: String(row.org_id),
2667
+ userId: String(row.user_id),
2668
+ skill: String(row.skill_slug),
2669
+ requestedSlug: String(row.requested_slug),
2670
+ status: String(row.status),
2671
+ input: parseJsonObject(row.input_json),
2672
+ args: parseJsonArray(row.args_json),
2673
+ ...typeof row.idempotency_key === "string" ? { idempotencyKey: row.idempotency_key } : {},
2674
+ correlationId: String(row.correlation_id),
2675
+ costCents: Number(row.cost_cents ?? 0),
2676
+ leaseGeneration: Number(row.lease_generation ?? 0),
2677
+ ...typeof row.output_type === "string" ? { outputType: row.output_type } : {},
2678
+ ...typeof row.output_preview === "string" ? { outputPreview: row.output_preview } : {},
2679
+ ...typeof row.error_code === "string" ? { errorCode: row.error_code } : {},
2680
+ ...typeof row.error_message === "string" ? { errorMessage: row.error_message } : {},
2681
+ createdAt: dateString(row.created_at),
2682
+ ...row.started_at ? { startedAt: dateString(row.started_at) } : {},
2683
+ ...row.completed_at ? { completedAt: dateString(row.completed_at) } : {}
2684
+ };
2685
+ }
2686
+ function rowToLog(row) {
2687
+ return {
2688
+ runId: String(row.run_id),
2689
+ sequence: Number(row.sequence),
2690
+ level: String(row.level),
2691
+ message: String(row.message),
2692
+ createdAt: dateString(row.created_at)
2693
+ };
2694
+ }
2695
+ function rowToArtifact(row) {
2696
+ return {
2697
+ id: String(row.id),
2698
+ runId: String(row.run_id),
2699
+ orgId: String(row.org_id),
2700
+ fileName: String(row.file_name),
2701
+ relativePath: String(row.relative_path),
2702
+ contentType: String(row.content_type),
2703
+ byteSize: Number(row.byte_size),
2704
+ sha256: String(row.sha256),
2705
+ storageKind: String(row.storage_kind),
2706
+ ...typeof row.storage_key === "string" ? { storageKey: row.storage_key } : {},
2707
+ ...typeof row.body_text === "string" ? { bodyText: row.body_text } : {},
2708
+ visibility: String(row.visibility ?? "private") === "public" ? "public" : "private",
2709
+ ...row.expires_at ? { expiresAt: dateString(row.expires_at) } : {},
2710
+ createdAt: dateString(row.created_at)
2711
+ };
2712
+ }
2713
+ function rowToSkill(row) {
2714
+ return {
2715
+ orgId: String(row.org_id),
2716
+ slug: String(row.slug),
2717
+ displayName: String(row.display_name),
2718
+ description: String(row.description ?? ""),
2719
+ category: String(row.category),
2720
+ tags: parseJsonArray(row.tags_json),
2721
+ source: String(row.source),
2722
+ kind: String(row.kind) === "instruction" ? "instruction" : "executable",
2723
+ ...typeof row.version === "string" ? { version: row.version } : {},
2724
+ ...typeof row.skill_md === "string" ? { skillMd: row.skill_md } : {},
2725
+ ...typeof row.bundle_sha256 === "string" ? { bundleSha256: row.bundle_sha256 } : {},
2726
+ ...row.bundle_byte_size === null || row.bundle_byte_size === undefined ? {} : { bundleByteSize: Number(row.bundle_byte_size) },
2727
+ ...typeof row.published_by_user_id === "string" ? { publishedByUserId: row.published_by_user_id } : {},
2728
+ createdAt: dateString(row.created_at),
2729
+ updatedAt: dateString(row.updated_at),
2730
+ revisionId: String(row.revision_id ?? ""),
2731
+ revisionNumber: Number(row.revision_number ?? 0),
2732
+ ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {},
2733
+ ...row.tombstone_purge_after ? { tombstonePurgeAfter: dateString(row.tombstone_purge_after) } : {},
2734
+ lifecycle: row.lifecycle === "archived" ? "archived" : "active",
2735
+ ...row.archived_at ? { archivedAt: dateString(row.archived_at) } : {},
2736
+ ...row.archive_reason ? { archiveReason: String(row.archive_reason) } : {},
2737
+ ...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
2738
+ };
2739
+ }
2740
+ function rowToPin(row) {
2741
+ return {
2742
+ orgId: String(row.org_id),
2743
+ principal: String(row.principal),
2744
+ slug: String(row.slug),
2745
+ pinnedAt: dateString(row.pinned_at),
2746
+ metadata: parseJsonObject(row.metadata_json)
2747
+ };
2748
+ }
2749
+ function rowToSkillBundle(row) {
2750
+ return {
2751
+ orgId: String(row.org_id),
2752
+ sha256: String(row.sha256),
2753
+ byteSize: Number(row.byte_size),
2754
+ contentType: String(row.content_type),
2755
+ storageKind: String(row.storage_kind),
2756
+ ...typeof row.storage_key === "string" ? { storageKey: row.storage_key } : {},
2757
+ ...toBytes(row.body_blob) ? { bytes: toBytes(row.body_blob) } : {},
2758
+ createdAt: dateString(row.created_at)
2759
+ };
2760
+ }
2761
+ function toBytes(value) {
2762
+ if (value == null)
2763
+ return null;
2764
+ if (value instanceof Uint8Array)
2765
+ return ownBytes(value);
2766
+ if (value instanceof ArrayBuffer)
2767
+ return ownBytes(value);
2768
+ return null;
2769
+ }
2770
+ function parseJsonObject(value) {
2771
+ if (value && typeof value === "object" && !Array.isArray(value))
2772
+ return value;
2773
+ if (typeof value === "string") {
2774
+ try {
2775
+ const parsed = JSON.parse(value);
2776
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2777
+ } catch {}
2778
+ }
2779
+ return {};
2780
+ }
2781
+ function parseJsonArray(value) {
2782
+ const parsed = typeof value === "string" ? safeParse(value) : value;
2783
+ return Array.isArray(parsed) ? parsed.map(String) : [];
2784
+ }
2785
+ function safeParse(value) {
2786
+ try {
2787
+ return JSON.parse(value);
2788
+ } catch {
2789
+ return;
2790
+ }
2791
+ }
2792
+ function dateString(value) {
2793
+ if (value instanceof Date)
2794
+ return value.toISOString();
2795
+ return String(value);
2796
+ }
2797
+ function rowToSkillVersion(row) {
2798
+ return {
2799
+ orgId: String(row.org_id),
2800
+ slug: String(row.slug),
2801
+ version: String(row.version),
2802
+ bundleSha256: String(row.bundle_sha256),
2803
+ bundleByteSize: Number(row.bundle_byte_size),
2804
+ storageKind: String(row.storage_kind ?? "db"),
2805
+ ...typeof row.storage_key === "string" ? { storageKey: row.storage_key } : {},
2806
+ manifest: parseJsonObject(row.manifest_json),
2807
+ ...typeof row.published_by_user_id === "string" ? { publishedByUserId: row.published_by_user_id } : {},
2808
+ createdAt: dateString(row.created_at)
2809
+ };
2810
+ }
2811
+
2812
+ // src/lib/revision.ts
2813
+ import { createHash as createHash2 } from "crypto";
2814
+ function revisionIdOf(content) {
2815
+ const canonicalContent = {
2816
+ slug: content.slug,
2817
+ displayName: content.displayName,
2818
+ description: content.description,
2819
+ category: content.category,
2820
+ tags: content.tags,
2821
+ source: content.source,
2822
+ kind: content.kind,
2823
+ version: content.version ?? null,
2824
+ skillMd: content.skillMd ?? null,
2825
+ bundleSha256: content.bundleSha256 ?? null,
2826
+ bundleByteSize: content.bundleByteSize ?? null
2827
+ };
2828
+ if (content.lifecycle === "archived" || content.archiveReason !== undefined || content.replacementSlug !== undefined) {
2829
+ canonicalContent.lifecycle = content.lifecycle ?? "active";
2830
+ canonicalContent.archiveReason = content.archiveReason ?? null;
2831
+ canonicalContent.replacementSlug = content.replacementSlug ?? null;
2832
+ }
2833
+ const canonical = JSON.stringify(canonicalContent);
2834
+ return createHash2("sha256").update(canonical).digest("hex");
2835
+ }
2836
+ function revisionIdOfRecord(record) {
2837
+ return revisionIdOf(record);
2838
+ }
2839
+
2840
+ // src/server/sqlite-store.ts
2841
+ import { Database } from "bun:sqlite";
2842
+ import { randomUUID as randomUUID4 } from "crypto";
2843
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync } from "fs";
2844
+ import { dirname as dirname2, join as join4 } from "path";
2845
+
2846
+ // src/server/migrations-dir.ts
2847
+ import { existsSync as existsSync3 } from "fs";
2848
+ import { dirname, join as join3 } from "path";
2849
+ var MIGRATION_DIALECTS = ["postgres", "sqlite"];
2850
+ var MAX_WALK_UP = 6;
2851
+ function findMigrationsRoot(startDirs = defaultStartDirs()) {
2852
+ for (const start of startDirs) {
2853
+ let dir = start;
2854
+ for (let level = 0;level < MAX_WALK_UP; level += 1) {
2855
+ const candidate = join3(dir, "migrations");
2856
+ if (MIGRATION_DIALECTS.some((dialect) => existsSync3(join3(candidate, dialect))))
2857
+ return candidate;
2858
+ const parent = dirname(dir);
2859
+ if (parent === dir)
2860
+ break;
2861
+ dir = parent;
2862
+ }
2863
+ }
2864
+ return null;
2865
+ }
2866
+ function resolveMigrationsDir(dialect, root = findMigrationsRoot()) {
2867
+ if (!root) {
2868
+ throw new Error(`could not locate the migrations directory for dialect "${dialect}". ` + `Expected a migrations/${dialect}/ folder alongside the package root.`);
2869
+ }
2870
+ const dir = join3(root, dialect);
2871
+ if (!existsSync3(dir)) {
2872
+ throw new Error(`migrations directory not found: ${dir}`);
2873
+ }
2874
+ return dir;
2875
+ }
2876
+ function defaultStartDirs() {
2877
+ const dirs = [import.meta.dir];
2878
+ const cwd = process.cwd();
2879
+ if (cwd && cwd !== import.meta.dir)
2880
+ dirs.push(cwd);
2881
+ return dirs;
2882
+ }
2883
+
2884
+ // src/server/sqlite-store.ts
2885
+ var CLAIM_ATTEMPTS = 8;
2886
+ var CLAIMABLE_STATUSES = ["queued", "retrying"];
2887
+ var NO_REVISION_SENTINEL = "0000000000000000000000000000000000000000000000000000000000000000";
2888
+ var LAST_USED_RESOLUTION_MS = 60000;
2889
+
2890
+ class SqliteSkillsStore {
2891
+ get selectionStore() {
2892
+ return new SqliteSkillSelectionStore(this.db);
2893
+ }
2894
+ get executionGrantStore() {
2895
+ return new SqliteExecutionGrantStore(this.db);
2896
+ }
2897
+ backend;
2898
+ db;
2899
+ closed = false;
2900
+ constructor(path = SQLITE_MEMORY_PATH, options = {}) {
2901
+ const inMemory = path === SQLITE_MEMORY_PATH;
2902
+ if (!inMemory)
2903
+ mkdirSync2(dirname2(path), { recursive: true });
2904
+ this.db = openDatabase(path);
2905
+ this.db.exec(`PRAGMA busy_timeout = ${Math.max(0, options.busyTimeoutMs ?? 5000)}`);
2906
+ if (!inMemory)
2907
+ enableWalMode(this.db);
2908
+ this.db.exec("PRAGMA foreign_keys = ON");
2909
+ this.db.exec("PRAGMA synchronous = NORMAL");
2910
+ if (options.migrate !== false) {
2911
+ applySqliteMigrations(this.db, options.migrationsDir);
2912
+ }
2913
+ this.backfillLegacyRevisions();
2914
+ this.backend = {
2915
+ kind: "sqlite",
2916
+ durable: !inMemory,
2917
+ label: inMemory ? "sqlite (in-memory)" : `sqlite (${path})`
2918
+ };
2919
+ }
2920
+ backfillLegacyRevisions() {
2921
+ const rows = this.all("SELECT * FROM skills_registry WHERE revision_id = ''", []);
2922
+ for (const row of rows) {
2923
+ const record = rowToSkill(row);
2924
+ this.db.run("UPDATE skills_registry SET revision_id = ? WHERE org_id = ? AND slug = ?", [
2925
+ revisionIdOfRecord(record),
2926
+ record.orgId,
2927
+ record.slug
2928
+ ]);
2929
+ }
2930
+ }
2931
+ get database() {
2932
+ return this.db;
2933
+ }
2934
+ async close() {
2935
+ if (this.closed)
2936
+ return;
2937
+ this.closed = true;
2938
+ this.db.close(false);
2939
+ }
2940
+ async ensureBootstrapApiKey(token, principal) {
2941
+ const resolved = publicPrincipal(principal);
2942
+ this.db.transaction(() => {
2943
+ this.db.run(`INSERT INTO organizations (id, slug, name) VALUES (?, ?, ?)
2944
+ ON CONFLICT (id) DO UPDATE SET slug = excluded.slug, name = excluded.name`, [resolved.orgId, resolved.orgSlug, resolved.orgName]);
2945
+ this.db.run(`INSERT INTO users (id, email, name) VALUES (?, ?, ?)
2946
+ ON CONFLICT (id) DO UPDATE SET email = excluded.email`, [resolved.userId, resolved.email, resolved.email]);
2947
+ this.db.run(`INSERT INTO organization_members (org_id, user_id, role) VALUES (?, ?, ?)
2948
+ ON CONFLICT (org_id, user_id) DO UPDATE SET role = excluded.role`, [resolved.orgId, resolved.userId, resolved.role]);
2949
+ this.db.run(`INSERT INTO api_keys (id, org_id, user_id, name, key_hash, scopes_json)
2950
+ VALUES (?, ?, ?, ?, ?, ?)
2951
+ ON CONFLICT (key_hash) DO NOTHING`, [resolved.apiKeyId, resolved.orgId, resolved.userId, "bootstrap", hashApiKey(token), JSON.stringify(resolved.scopes)]);
2952
+ })();
2953
+ }
2954
+ async authenticateApiKeyHash(hash) {
2955
+ const row = this.get(`SELECT k.id AS api_key_id, k.scopes_json, k.last_used_at, o.id AS org_id, o.slug AS org_slug, o.name AS org_name,
2956
+ u.id AS user_id, u.email, m.role
2957
+ FROM api_keys k
2958
+ JOIN organizations o ON o.id = k.org_id
2959
+ JOIN users u ON u.id = k.user_id
2960
+ LEFT JOIN organization_members m ON m.org_id = k.org_id AND m.user_id = k.user_id
2961
+ WHERE k.key_hash = ? AND k.revoked_at IS NULL
2962
+ LIMIT 1`, [hash]);
2963
+ if (!row)
2964
+ return null;
2965
+ const lastUsed = typeof row.last_used_at === "string" ? Date.parse(row.last_used_at) : Number.NaN;
2966
+ if (!Number.isFinite(lastUsed) || Date.now() - lastUsed >= LAST_USED_RESOLUTION_MS) {
2967
+ this.db.run("UPDATE api_keys SET last_used_at = ? WHERE id = ?", [nowIso(), String(row.api_key_id)]);
2968
+ }
2969
+ return {
2970
+ apiKeyId: String(row.api_key_id),
2971
+ orgId: String(row.org_id),
2972
+ orgSlug: String(row.org_slug),
2973
+ orgName: String(row.org_name),
2974
+ userId: String(row.user_id),
2975
+ email: String(row.email),
2976
+ role: typeof row.role === "string" ? row.role : "member",
2977
+ scopes: parseScopes(row.scopes_json)
2978
+ };
2979
+ }
2980
+ async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
2981
+ const row = this.get("SELECT scopes_json FROM api_keys WHERE id = ? AND org_id = ? AND revoked_at IS NULL LIMIT 1", [keyId, actor.orgId]);
2982
+ if (!row)
2983
+ return { kind: "not_found" };
2984
+ const current = parseScopes(row.scopes_json);
2985
+ if (current.length !== expectedScopes.length || current.some((scope, index) => scope !== expectedScopes[index])) {
2986
+ return { kind: "stale", scopes: current };
2987
+ }
2988
+ const scopes = [...current, ...addScopes.filter((scope) => !current.includes(scope))];
2989
+ const tx = this.db.transaction(() => {
2990
+ const changed = this.db.run("UPDATE api_keys SET scopes_json = ? WHERE id = ? AND org_id = ? AND revoked_at IS NULL AND scopes_json = ?", [JSON.stringify(scopes), keyId, actor.orgId, JSON.stringify(expectedScopes)]);
2991
+ if (changed.changes !== 1)
2992
+ return false;
2993
+ this.db.run("INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json) VALUES (?, ?, ?, ?, ?, ?, ?)", [actor.orgId, actor.userId, actor.apiKeyId, "api_key_scopes_added", "api_key", keyId, JSON.stringify({ added: addScopes, scopes })]);
2994
+ return true;
2995
+ });
2996
+ if (!tx()) {
2997
+ const latest = this.get("SELECT scopes_json FROM api_keys WHERE id = ? AND org_id = ? AND revoked_at IS NULL LIMIT 1", [keyId, actor.orgId]);
2998
+ return { kind: "stale", scopes: latest ? parseScopes(latest.scopes_json) : current };
2999
+ }
3000
+ return { kind: "updated", scopes };
3001
+ }
3002
+ async enrollPublishScopeByOperator(input) {
3003
+ if (!validOperatorScopeEnrollmentInput(input))
3004
+ return { kind: "invalid" };
3005
+ if (input.expectedScopes.includes("skills:publish"))
3006
+ return { kind: "stale", scopes: input.expectedScopes };
3007
+ const operation = this.get("SELECT org_id, target_id FROM skills_audit_events WHERE action = ? AND operator_operation_id = ? LIMIT 1", ["api_key_scopes_added", input.operationId]);
3008
+ if (operation && (String(operation.org_id) !== input.orgId || String(operation.target_id) !== input.keyId))
3009
+ return { kind: "target_mismatch" };
3010
+ const row = this.get("SELECT id, org_id, name, scopes_json FROM api_keys WHERE id = ? AND revoked_at IS NULL LIMIT 1", [input.keyId]);
3011
+ if (!row)
3012
+ return { kind: "not_found" };
3013
+ if (String(row.org_id) !== input.orgId)
3014
+ return { kind: "target_mismatch" };
3015
+ const current = parseScopes(row.scopes_json);
3016
+ const prior = this.get("SELECT metadata_json FROM skills_audit_events WHERE action = ? AND target_type = ? AND target_id = ? AND json_extract(metadata_json, '$.operator_operation_id') = ? LIMIT 1", ["api_key_scopes_added", "api_key", input.keyId, input.operationId]);
3017
+ if (prior) {
3018
+ try {
3019
+ if (JSON.parse(String(prior.metadata_json)).target_manifest_digest !== input.manifestDigest)
3020
+ return { kind: "target_mismatch" };
3021
+ } catch {
3022
+ return { kind: "target_mismatch" };
3023
+ }
3024
+ return current.includes("skills:publish") ? { kind: "already_applied", scopes: current } : { kind: "stale", scopes: current };
3025
+ }
3026
+ if (current.length !== input.expectedScopes.length || current.some((scope, index) => scope !== input.expectedScopes[index]))
3027
+ return { kind: "stale", scopes: current };
3028
+ const scopes = [...current, "skills:publish"];
3029
+ const tx = this.db.transaction(() => {
3030
+ const changed = this.db.run("UPDATE api_keys SET scopes_json = ? WHERE id = ? AND org_id = ? AND revoked_at IS NULL AND scopes_json = ?", [JSON.stringify(scopes), input.keyId, input.orgId, JSON.stringify(input.expectedScopes)]);
3031
+ if (changed.changes !== 1)
3032
+ return false;
3033
+ this.db.run("INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, operator_operation_id, metadata_json) VALUES (?, NULL, NULL, ?, ?, ?, ?, ?)", [input.orgId, "api_key_scopes_added", "api_key", input.keyId, input.operationId, JSON.stringify({ added: ["skills:publish"], scopes, operator_operation_id: input.operationId, operator_job_id: input.operatorJobId, operator_task_arn: input.operatorTaskArn, target_manifest_digest: input.manifestDigest, station_id: input.stationId })]);
3034
+ return true;
3035
+ });
3036
+ if (!tx()) {
3037
+ const latest = this.get("SELECT scopes_json FROM api_keys WHERE id = ? AND org_id = ? AND revoked_at IS NULL LIMIT 1", [input.keyId, input.orgId]);
3038
+ return { kind: "stale", scopes: latest ? parseScopes(latest.scopes_json) : current };
3039
+ }
3040
+ return { kind: "updated", scopes };
3041
+ }
3042
+ async inspectOperatorScopeTarget(keyId, orgId) {
3043
+ const row = this.get("SELECT org_id, scopes_json FROM api_keys WHERE id = ? AND revoked_at IS NULL LIMIT 1", [keyId]);
3044
+ if (!row)
3045
+ return { kind: "not_found" };
3046
+ if (String(row.org_id) !== orgId)
3047
+ return { kind: "target_mismatch" };
3048
+ return { kind: "found", scopes: parseScopes(row.scopes_json) };
3049
+ }
3050
+ async createRun(input) {
3051
+ if (input.idempotencyKey) {
3052
+ const existing = this.get("SELECT * FROM skills_runs WHERE org_id = ? AND idempotency_key = ? LIMIT 1", [input.principal.orgId, input.idempotencyKey]);
3053
+ if (existing)
3054
+ return rowToRun(existing);
3055
+ }
3056
+ const row = this.get(`INSERT INTO skills_runs (id, org_id, user_id, skill_slug, requested_slug, status, input_json, args_json, idempotency_key, correlation_id, created_at)
3057
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3058
+ RETURNING *`, [
3059
+ runId(),
3060
+ input.principal.orgId,
3061
+ input.principal.userId,
3062
+ input.slug,
3063
+ input.slug,
3064
+ "queued",
3065
+ JSON.stringify(input.input),
3066
+ JSON.stringify(input.args),
3067
+ input.idempotencyKey ?? null,
3068
+ randomUUID4(),
3069
+ nowIso()
3070
+ ]);
3071
+ return rowToRun(row);
3072
+ }
3073
+ async listRuns(principal, limit) {
3074
+ return this.all("SELECT * FROM skills_runs WHERE org_id = ? ORDER BY created_at DESC, rowid DESC LIMIT ?", [principal.orgId, normalizeLimit(limit)]).map(rowToRun);
3075
+ }
3076
+ async getRun(principal, id) {
3077
+ const row = this.get("SELECT * FROM skills_runs WHERE id = ? AND org_id = ? LIMIT 1", [id, principal.orgId]);
3078
+ return row ? rowToRun(row) : null;
3079
+ }
3080
+ async claimNextRun(input) {
3081
+ const lost = new Set;
3082
+ for (let attempt = 0;attempt < CLAIM_ATTEMPTS; attempt += 1) {
3083
+ const claimed = this.claimOnce(input.workerId, lost);
3084
+ if (claimed === "empty")
3085
+ return null;
3086
+ if (claimed)
3087
+ return claimed;
3088
+ }
3089
+ return null;
3090
+ }
3091
+ claimOnce(workerId, lost) {
3092
+ this.db.exec("BEGIN IMMEDIATE");
3093
+ try {
3094
+ const excluded = [...lost];
3095
+ const candidate = this.get(`SELECT id FROM skills_runs
3096
+ WHERE status IN (${CLAIMABLE_STATUSES.map(() => "?").join(", ")})
3097
+ ${excluded.length ? `AND id NOT IN (${excluded.map(() => "?").join(", ")})` : ""}
3098
+ ORDER BY created_at ASC, rowid ASC
3099
+ LIMIT 1`, [...CLAIMABLE_STATUSES, ...excluded]);
3100
+ if (!candidate) {
3101
+ this.db.exec("COMMIT");
3102
+ return "empty";
3103
+ }
3104
+ const id = String(candidate.id);
3105
+ const now = nowIso();
3106
+ const result = this.db.run(`UPDATE skills_runs
3107
+ SET status = 'running', started_at = COALESCE(started_at, ?), locked_by = ?, locked_at = ?,
3108
+ lease_generation = lease_generation + 1
3109
+ WHERE id = ? AND status IN (${CLAIMABLE_STATUSES.map(() => "?").join(", ")})`, [now, workerId, now, id, ...CLAIMABLE_STATUSES]);
3110
+ if (result.changes !== 1) {
3111
+ this.db.exec("ROLLBACK");
3112
+ lost.add(id);
3113
+ return null;
3114
+ }
3115
+ const row = this.get("SELECT * FROM skills_runs WHERE id = ? LIMIT 1", [id]);
3116
+ if (!row) {
3117
+ this.db.exec("ROLLBACK");
3118
+ throw new Error(`claimed run ${id} vanished within its own transaction; refusing to report a lost claim as an empty queue`);
3119
+ }
3120
+ this.db.exec("COMMIT");
3121
+ return rowToRun(row);
3122
+ } catch (error) {
3123
+ this.rollbackQuietly();
3124
+ throw error;
3125
+ }
3126
+ }
3127
+ async updateRun(id, patch) {
3128
+ const current = this.get("SELECT * FROM skills_runs WHERE id = ? LIMIT 1", [id]);
3129
+ if (!current)
3130
+ return null;
3131
+ const run = { ...rowToRun(current), ...patch };
3132
+ const row = this.get(`UPDATE skills_runs
3133
+ SET status = ?, output_type = ?, output_preview = ?, error_code = ?, error_message = ?, started_at = ?, completed_at = ?
3134
+ WHERE id = ?
3135
+ RETURNING *`, [
3136
+ run.status,
3137
+ run.outputType ?? null,
3138
+ run.outputPreview ?? null,
3139
+ run.errorCode ?? null,
3140
+ run.errorMessage ?? null,
3141
+ run.startedAt ?? null,
3142
+ run.completedAt ?? null,
3143
+ id
3144
+ ]);
3145
+ return row ? rowToRun(row) : null;
3146
+ }
3147
+ async transitionRun(id, patch, expectedGeneration) {
3148
+ const current = this.get("SELECT * FROM skills_runs WHERE id = ? LIMIT 1", [id]);
3149
+ if (!current)
3150
+ return null;
3151
+ const stored = rowToRun(current);
3152
+ if (stored.leaseGeneration !== expectedGeneration) {
3153
+ throw new StaleLeaseGenerationError(id, expectedGeneration, stored.leaseGeneration, stored.status);
3154
+ }
3155
+ const run = { ...stored, ...patch };
3156
+ const row = this.get(`UPDATE skills_runs
3157
+ SET status = ?, output_type = ?, output_preview = ?, error_code = ?, error_message = ?, started_at = ?, completed_at = ?,
3158
+ lease_generation = ?
3159
+ WHERE id = ? AND lease_generation = ?
3160
+ RETURNING *`, [
3161
+ run.status,
3162
+ run.outputType ?? null,
3163
+ run.outputPreview ?? null,
3164
+ run.errorCode ?? null,
3165
+ run.errorMessage ?? null,
3166
+ run.startedAt ?? null,
3167
+ run.completedAt ?? null,
3168
+ run.leaseGeneration,
3169
+ id,
3170
+ expectedGeneration
3171
+ ]);
3172
+ return row ? rowToRun(row) : null;
3173
+ }
3174
+ async appendLog(id, orgId, level, message) {
3175
+ const row = this.get(`INSERT INTO skills_run_logs (run_id, org_id, sequence, level, message, created_at)
3176
+ VALUES (?, ?, (SELECT COALESCE(MAX(sequence), 0) + 1 FROM skills_run_logs WHERE run_id = ?), ?, ?, ?)
3177
+ RETURNING *`, [id, orgId, id, level, message, nowIso()]);
3178
+ return rowToLog(row);
3179
+ }
3180
+ async listLogs(principal, id) {
3181
+ return this.all(`SELECT l.* FROM skills_run_logs l
3182
+ JOIN skills_runs r ON r.id = l.run_id AND r.org_id = ?
3183
+ WHERE l.run_id = ?
3184
+ ORDER BY l.sequence ASC`, [principal.orgId, id]).map(rowToLog);
3185
+ }
3186
+ async addArtifact(artifact) {
3187
+ const row = this.get(`INSERT INTO skills_artifacts (id, run_id, org_id, file_name, relative_path, content_type, byte_size, sha256, storage_kind, storage_key, body_text, visibility, expires_at, created_at)
3188
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3189
+ RETURNING *`, [
3190
+ artifact.id,
3191
+ artifact.runId,
3192
+ artifact.orgId,
3193
+ artifact.fileName,
3194
+ artifact.relativePath,
3195
+ artifact.contentType,
3196
+ artifact.byteSize,
3197
+ artifact.sha256,
3198
+ artifact.storageKind,
3199
+ artifact.storageKey ?? null,
3200
+ artifact.bodyText ?? null,
3201
+ artifact.visibility,
3202
+ artifact.expiresAt ?? null,
3203
+ nowIso()
3204
+ ]);
3205
+ return rowToArtifact(row);
3206
+ }
3207
+ async listArtifacts(principal, id) {
3208
+ return this.all(`SELECT a.* FROM skills_artifacts a
3209
+ JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ?
3210
+ WHERE a.run_id = ?
3211
+ ORDER BY a.created_at ASC, a.rowid ASC`, [principal.orgId, id]).map(rowToArtifact);
3212
+ }
3213
+ async getArtifact(principal, id, artifact) {
3214
+ const row = this.get(`SELECT a.* FROM skills_artifacts a
3215
+ JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ?
3216
+ WHERE a.run_id = ? AND a.id = ?
3217
+ LIMIT 1`, [principal.orgId, id, artifact]);
3218
+ return row ? rowToArtifact(row) : null;
3219
+ }
3220
+ async publishSkill(input) {
3221
+ const orgId = input.principal.orgId;
3222
+ const now = nowIso();
3223
+ return this.db.transaction(() => {
3224
+ const previous = this.get("SELECT revision_id, revision_number, bundle_sha256, bundle_byte_size, skill_md, tombstoned_at, source FROM skills_registry WHERE org_id = ? AND slug = ?", [orgId, input.slug]);
3225
+ const previousSha = typeof previous?.bundle_sha256 === "string" ? previous.bundle_sha256 : null;
3226
+ const previousRevisionId = typeof previous?.revision_id === "string" && previous.revision_id ? previous.revision_id : null;
3227
+ const tombstoned = previous?.tombstoned_at != null;
3228
+ if (input.seedBundledOnly && input.expectedRevisionId && !previous) {
3229
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, null);
3230
+ }
3231
+ if (input.seedBundledOnly && previous && (tombstoned || previous.source !== "bundled")) {
3232
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, previousRevisionId);
3233
+ }
3234
+ const carriedSkillMd = typeof input.skillMd === "string" ? input.skillMd : typeof previous?.skill_md === "string" ? previous.skill_md : null;
3235
+ if (previous && !tombstoned && input.expectedRevisionId !== previousRevisionId) {
3236
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, previousRevisionId);
3237
+ }
3238
+ const carriedSha = input.bundle?.sha256 ?? previousSha;
3239
+ if (input.version && carriedSha) {
3240
+ const existingVersion = this.get("SELECT bundle_sha256 FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [orgId, input.slug, input.version]);
3241
+ const existingSha = typeof existingVersion?.bundle_sha256 === "string" ? existingVersion.bundle_sha256 : null;
3242
+ if (existingSha && existingSha !== carriedSha) {
3243
+ throw new SkillVersionExistsError(input.slug, input.version, existingSha, carriedSha);
3244
+ }
3245
+ }
3246
+ const carriedSize = input.bundle?.byteSize ?? (previous?.bundle_byte_size == null ? null : Number(previous.bundle_byte_size));
3247
+ const revisionId = revisionIdOfRecord({
3248
+ slug: input.slug,
3249
+ displayName: input.displayName,
3250
+ description: input.description,
3251
+ category: input.category,
3252
+ tags: input.tags,
3253
+ source: input.source,
3254
+ kind: input.kind,
3255
+ ...input.version ? { version: input.version } : {},
3256
+ ...carriedSkillMd ? { skillMd: carriedSkillMd } : {},
3257
+ ...carriedSha ? { bundleSha256: carriedSha } : {},
3258
+ ...carriedSize === null || carriedSize === undefined ? {} : { bundleByteSize: carriedSize }
3259
+ });
3260
+ if (input.bundle) {
3261
+ this.db.run(`INSERT INTO skills_bundles (org_id, sha256, byte_size, content_type, storage_kind, storage_key, body_blob, created_at)
3262
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3263
+ ON CONFLICT (org_id, sha256) DO UPDATE SET
3264
+ byte_size = excluded.byte_size,
3265
+ content_type = excluded.content_type,
3266
+ storage_kind = excluded.storage_kind,
3267
+ storage_key = excluded.storage_key,
3268
+ body_blob = excluded.body_blob`, [
3269
+ orgId,
3270
+ input.bundle.sha256,
3271
+ input.bundle.byteSize,
3272
+ input.bundle.contentType,
3273
+ input.bundle.storageKind,
3274
+ input.bundle.storageKey ?? null,
3275
+ input.bundle.bytes ?? null,
3276
+ now
3277
+ ]);
3278
+ }
3279
+ const row = this.get(`INSERT INTO skills_registry (org_id, slug, display_name, description, category, tags_json, source, kind, version, skill_md,
3280
+ bundle_sha256, bundle_byte_size, published_by_user_id, revision_id, revision_number, created_at, updated_at)
3281
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
3282
+ ON CONFLICT (org_id, slug) DO UPDATE SET
3283
+ display_name = excluded.display_name,
3284
+ description = excluded.description,
3285
+ category = excluded.category,
3286
+ tags_json = excluded.tags_json,
3287
+ source = excluded.source,
3288
+ kind = excluded.kind,
3289
+ version = excluded.version,
3290
+ skill_md = excluded.skill_md,
3291
+ -- COALESCE, not a straight assignment. A publish that carries no bundle is a
3292
+ -- metadata update, not an instruction to discard the stored one: the plain
3293
+ -- assignment nulled bundle_sha256 and then handed the old digest to orphan
3294
+ -- collection, so re-publishing metadata over an existing skill deleted its
3295
+ -- tarball and left every client with a 404.
3296
+ bundle_sha256 = COALESCE(excluded.bundle_sha256, skills_registry.bundle_sha256),
3297
+ bundle_byte_size = COALESCE(excluded.bundle_byte_size, skills_registry.bundle_byte_size),
3298
+ published_by_user_id = excluded.published_by_user_id,
3299
+ revision_id = excluded.revision_id,
3300
+ -- Current + 1, not the inserted 1: on the update path the ACTUAL row's
3301
+ -- counter is the truth (it may have advanced since the pre-read, which is
3302
+ -- exactly what the WHERE guard below detects).
3303
+ revision_number = skills_registry.revision_number + 1,
3304
+ tombstoned_at = NULL,
3305
+ tombstone_purge_after = NULL,
3306
+ updated_at = excluded.updated_at
3307
+ WHERE (skills_registry.tombstoned_at IS NOT NULL OR skills_registry.revision_id = ?)
3308
+ AND (? = 0 OR (skills_registry.tombstoned_at IS NULL AND skills_registry.source = 'bundled'
3309
+ AND skills_registry.revision_id = ?))
3310
+ RETURNING *`, [
3311
+ orgId,
3312
+ input.slug,
3313
+ input.displayName,
3314
+ input.description,
3315
+ input.category,
3316
+ JSON.stringify(input.tags),
3317
+ input.source,
3318
+ input.kind,
3319
+ input.version ?? null,
3320
+ carriedSkillMd,
3321
+ input.bundle?.sha256 ?? null,
3322
+ input.bundle?.byteSize ?? null,
3323
+ input.principal.userId,
3324
+ revisionId,
3325
+ now,
3326
+ now,
3327
+ input.expectedRevisionId ?? NO_REVISION_SENTINEL,
3328
+ input.seedBundledOnly ? 1 : 0,
3329
+ input.expectedRevisionId ?? NO_REVISION_SENTINEL
3330
+ ]);
3331
+ if (!row) {
3332
+ const current = this.get("SELECT revision_id FROM skills_registry WHERE org_id = ? AND slug = ?", [orgId, input.slug]);
3333
+ const currentId = typeof current?.revision_id === "string" ? current.revision_id : null;
3334
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, currentId);
3335
+ }
3336
+ if (input.version && carriedSha) {
3337
+ this.db.run(`INSERT OR IGNORE INTO skills_versions (org_id, slug, version, bundle_sha256, bundle_byte_size, storage_kind, storage_key, manifest_json, published_by_user_id, created_at)
3338
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
3339
+ orgId,
3340
+ input.slug,
3341
+ input.version,
3342
+ carriedSha,
3343
+ carriedSize ?? 0,
3344
+ input.versionStorage?.storageKind ?? "db",
3345
+ input.versionStorage?.storageKey ?? null,
3346
+ JSON.stringify(input.versionManifest ?? {}),
3347
+ input.principal.userId ?? null,
3348
+ now
3349
+ ]);
3350
+ }
3351
+ if (previousSha && input.bundle && previousSha !== input.bundle.sha256)
3352
+ this.collectOrphanBundle(orgId, previousSha);
3353
+ this.db.run("DELETE FROM skills_tags WHERE org_id = ? AND slug = ?", [orgId, input.slug]);
3354
+ const insertTag = this.db.prepare("INSERT OR IGNORE INTO skills_tags (org_id, slug, tag) VALUES (?, ?, ?)");
3355
+ for (const tag of input.tags) {
3356
+ if (!tag.trim())
3357
+ continue;
3358
+ insertTag.run(orgId, input.slug, tag);
3359
+ }
3360
+ return rowToSkill(row);
3361
+ })();
3362
+ }
3363
+ async listSkills(principal) {
3364
+ await this.purgeExpiredTombstones(principal);
3365
+ return this.all("SELECT * FROM skills_registry WHERE org_id = ? AND tombstoned_at IS NULL ORDER BY slug ASC", [principal.orgId]).map(rowToSkill);
3366
+ }
3367
+ async getSkill(principal, slug) {
3368
+ const row = this.get("SELECT * FROM skills_registry WHERE org_id = ? AND slug = ? LIMIT 1", [principal.orgId, slug]);
3369
+ return row ? rowToSkill(row) : null;
3370
+ }
3371
+ async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
3372
+ return this.db.transaction(() => {
3373
+ const rowCurrent = this.get("SELECT * FROM skills_registry WHERE org_id=? AND slug=? LIMIT 1", [principal.orgId, slug]);
3374
+ const current = rowCurrent ? rowToSkill(rowCurrent) : null;
3375
+ if (!current || current.tombstonedAt)
3376
+ return null;
3377
+ if (expectedRevisionId !== current.revisionId)
3378
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
3379
+ if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
3380
+ const profiles = this.db.query("SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=?").all(principal.orgId);
3381
+ const referencing = profiles.filter((profile2) => {
3382
+ const selections2 = typeof profile2.selections_json === "string" ? JSON.parse(profile2.selections_json) : profile2.selections_json;
3383
+ return Array.isArray(selections2) && selections2.some((selection) => selection?.slug === slug);
3384
+ }).map((profile2) => String(profile2.profile_id)).sort();
3385
+ if (referencing.length)
3386
+ throw new SkillLifecycleConflictError(slug, referencing);
3387
+ }
3388
+ const next2 = { ...current, lifecycle: patch.lifecycle, updatedAt: nowIso(), ...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {} };
3389
+ if (patch.lifecycle === "active") {
3390
+ delete next2.archivedAt;
3391
+ delete next2.archiveReason;
3392
+ delete next2.replacementSlug;
3393
+ }
3394
+ const row = this.get("UPDATE skills_registry SET lifecycle=?, archived_at=?, archive_reason=?, replacement_slug=?, revision_id=?, revision_number=revision_number+1, updated_at=? WHERE org_id=? AND slug=? AND tombstoned_at IS NULL AND revision_id=? RETURNING *", [next2.lifecycle, next2.archivedAt ?? null, next2.archiveReason ?? null, next2.replacementSlug ?? null, revisionIdOfRecord(next2), next2.updatedAt, principal.orgId, slug, current.revisionId]);
3395
+ if (!row)
3396
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, this.getSkillSync(principal, slug)?.revisionId ?? null);
3397
+ return rowToSkill(row);
3398
+ }).immediate();
3399
+ }
3400
+ getSkillSync(principal, slug) {
3401
+ const row = this.get("SELECT * FROM skills_registry WHERE org_id=? AND slug=? LIMIT 1", [principal.orgId, slug]);
3402
+ return row ? rowToSkill(row) : null;
3403
+ }
3404
+ async updateSkill(principal, slug, patch, expectedRevisionId) {
3405
+ const current = await this.getSkill(principal, slug);
3406
+ if (!current || current.tombstonedAt)
3407
+ return null;
3408
+ if (expectedRevisionId !== current.revisionId) {
3409
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
3410
+ }
3411
+ const next2 = { ...current, ...patch };
3412
+ return this.db.transaction(() => {
3413
+ const revisionId = revisionIdOfRecord(next2);
3414
+ const row = this.get(`UPDATE skills_registry
3415
+ SET display_name = ?, description = ?, category = ?, tags_json = ?, kind = ?, version = ?, skill_md = ?,
3416
+ revision_id = ?, revision_number = revision_number + 1, updated_at = ?
3417
+ WHERE org_id = ? AND slug = ? AND tombstoned_at IS NULL AND revision_id = ?
3418
+ RETURNING *`, [
3419
+ next2.displayName,
3420
+ next2.description,
3421
+ next2.category,
3422
+ JSON.stringify(next2.tags),
3423
+ next2.kind,
3424
+ next2.version ?? null,
3425
+ next2.skillMd ?? null,
3426
+ revisionId,
3427
+ nowIso(),
3428
+ principal.orgId,
3429
+ slug,
3430
+ current.revisionId
3431
+ ]);
3432
+ if (!row) {
3433
+ const nowRow = this.get("SELECT revision_id, tombstoned_at FROM skills_registry WHERE org_id = ? AND slug = ? LIMIT 1", [
3434
+ principal.orgId,
3435
+ slug
3436
+ ]);
3437
+ if (nowRow && nowRow.tombstoned_at == null) {
3438
+ const currentId = typeof nowRow.revision_id === "string" ? nowRow.revision_id : null;
3439
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, currentId);
3440
+ }
3441
+ return null;
3442
+ }
3443
+ this.db.run("DELETE FROM skills_tags WHERE org_id = ? AND slug = ?", [principal.orgId, slug]);
3444
+ const insertTag = this.db.prepare("INSERT OR IGNORE INTO skills_tags (org_id, slug, tag) VALUES (?, ?, ?)");
3445
+ for (const tag of next2.tags) {
3446
+ if (!tag.trim())
3447
+ continue;
3448
+ insertTag.run(principal.orgId, slug, tag);
3449
+ }
3450
+ return rowToSkill(row);
3451
+ })();
3452
+ }
3453
+ async deleteSkill(principal, slug, tombstoneWindowMs) {
3454
+ return this.db.transaction(() => {
3455
+ const existing = this.get("SELECT tombstoned_at FROM skills_registry WHERE org_id = ? AND slug = ?", [principal.orgId, slug]);
3456
+ if (!existing)
3457
+ return null;
3458
+ if (existing.tombstoned_at != null) {
3459
+ const row2 = this.get("SELECT * FROM skills_registry WHERE org_id = ? AND slug = ? LIMIT 1", [principal.orgId, slug]);
3460
+ return rowToSkill(row2);
3461
+ }
3462
+ const tombstonedAt = nowIso();
3463
+ const purgeAfter = new Date(Date.now() + tombstoneWindowMs).toISOString();
3464
+ const row = this.get(`UPDATE skills_registry
3465
+ SET tombstoned_at = ?, tombstone_purge_after = ?, updated_at = ?
3466
+ WHERE org_id = ? AND slug = ?
3467
+ RETURNING *`, [tombstonedAt, purgeAfter, tombstonedAt, principal.orgId, slug]);
3468
+ return rowToSkill(row);
3469
+ })();
3470
+ }
3471
+ async purgeExpiredTombstones(principal) {
3472
+ return this.db.transaction(() => {
3473
+ const now = nowIso();
3474
+ const expired = this.all("SELECT * FROM skills_registry WHERE org_id = ? AND tombstoned_at IS NOT NULL AND tombstone_purge_after <= ?", [principal.orgId, now]);
3475
+ const purged = [];
3476
+ for (const row of expired) {
3477
+ const record = rowToSkill(row);
3478
+ this.db.run("DELETE FROM skills_registry WHERE org_id = ? AND slug = ?", [principal.orgId, record.slug]);
3479
+ this.db.run("DELETE FROM skills_tags WHERE org_id = ? AND slug = ?", [principal.orgId, record.slug]);
3480
+ if (record.bundleSha256)
3481
+ this.collectOrphanBundle(principal.orgId, record.bundleSha256);
3482
+ purged.push(record);
3483
+ }
3484
+ return purged;
3485
+ })();
3486
+ }
3487
+ async getSkillBundle(principal, sha256) {
3488
+ const row = this.get("SELECT * FROM skills_bundles WHERE org_id = ? AND sha256 = ? LIMIT 1", [principal.orgId, sha256]);
3489
+ return row ? rowToSkillBundle(row) : null;
3490
+ }
3491
+ async listSkillVersions(principal, slug) {
3492
+ return this.all("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? ORDER BY created_at DESC, version DESC", [principal.orgId, slug]).map(rowToSkillVersion);
3493
+ }
3494
+ async getSkillVersion(principal, slug, version) {
3495
+ const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version]);
3496
+ return row ? rowToSkillVersion(row) : null;
3497
+ }
3498
+ async pinSkill(principal, slug, metadata = {}) {
3499
+ const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
3500
+ VALUES (?, ?, ?, ?, ?)
3501
+ ON CONFLICT (org_id, principal, slug) DO UPDATE SET
3502
+ pinned_at = excluded.pinned_at,
3503
+ metadata_json = excluded.metadata_json
3504
+ RETURNING *`, [principal.orgId, principal.apiKeyId, slug, nowIso(), JSON.stringify(metadata)]);
3505
+ return rowToPin(row);
3506
+ }
3507
+ async unpinSkill(principal, slug) {
3508
+ const result = this.db.run("DELETE FROM skills_pins WHERE org_id = ? AND principal = ? AND slug = ?", [principal.orgId, principal.apiKeyId, slug]);
3509
+ return result.changes > 0;
3510
+ }
3511
+ async listPins(principal) {
3512
+ return this.all("SELECT * FROM skills_pins WHERE org_id = ? AND principal = ? ORDER BY slug ASC", [principal.orgId, principal.apiKeyId]).map(rowToPin);
3513
+ }
3514
+ async listTags(principal) {
3515
+ await this.purgeExpiredTombstones(principal);
3516
+ return this.all("SELECT DISTINCT t.tag FROM skills_tags t JOIN skills_registry s ON s.org_id=t.org_id AND s.slug=t.slug WHERE t.org_id = ? AND s.lifecycle='active' ORDER BY t.tag ASC", [principal.orgId]).map((row) => String(row.tag));
3517
+ }
3518
+ async listSkillsByTag(principal, tag) {
3519
+ await this.purgeExpiredTombstones(principal);
3520
+ return this.all(`SELECT s.* FROM skills_registry s
3521
+ JOIN skills_tags t ON t.org_id = s.org_id AND t.slug = s.slug
3522
+ WHERE t.org_id = ? AND t.tag = ? AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
3523
+ ORDER BY s.slug ASC`, [principal.orgId, tag]).map(rowToSkill);
3524
+ }
3525
+ async listPinsByTag(principal, tag) {
3526
+ await this.purgeExpiredTombstones(principal);
3527
+ return this.all(`SELECT p.* FROM skills_pins p
3528
+ JOIN skills_tags t ON t.org_id = p.org_id AND t.slug = p.slug
3529
+ JOIN skills_registry s ON s.org_id = p.org_id AND s.slug = p.slug
3530
+ WHERE p.org_id = ? AND p.principal = ? AND t.tag = ? AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
3531
+ ORDER BY p.slug ASC`, [principal.orgId, principal.apiKeyId, tag]).map(rowToPin);
3532
+ }
3533
+ async listPublishedSlugs(principal) {
3534
+ return this.all("SELECT slug FROM skills_registry WHERE org_id = ? AND tombstoned_at IS NULL ORDER BY slug ASC", [principal.orgId]).map((row) => String(row.slug));
3535
+ }
3536
+ collectOrphanBundle(orgId, sha256) {
3537
+ const referenced = this.get("SELECT 1 AS present FROM skills_registry WHERE org_id = ? AND bundle_sha256 = ? LIMIT 1", [orgId, sha256]) ?? this.get("SELECT 1 AS present FROM skills_versions WHERE org_id = ? AND bundle_sha256 = ? LIMIT 1", [orgId, sha256]);
3538
+ if (referenced)
3539
+ return;
3540
+ this.db.run("DELETE FROM skills_bundles WHERE org_id = ? AND sha256 = ?", [orgId, sha256]);
3541
+ }
3542
+ get(sql, params) {
3543
+ return this.db.query(sql).get(...params) ?? null;
3544
+ }
3545
+ all(sql, params) {
3546
+ return this.db.query(sql).all(...params);
3547
+ }
3548
+ rollbackQuietly() {
3549
+ try {
3550
+ this.db.exec("ROLLBACK");
3551
+ } catch {}
3552
+ }
3553
+ }
3554
+ function applySqliteMigrations(db, migrationsDir = resolveMigrationsDir("sqlite")) {
3555
+ db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
3556
+ version text PRIMARY KEY,
3557
+ applied_at text NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
3558
+ )`);
3559
+ const files = readdirSync(migrationsDir).filter((name) => name.endsWith(".sql")).sort();
3560
+ if (files.length === 0) {
3561
+ throw new Error(`no .sql migrations found in ${migrationsDir}`);
3562
+ }
3563
+ const appliedNow = [];
3564
+ for (const file of files) {
3565
+ const version = file.replace(/\.sql$/, "");
3566
+ const text = readFileSync2(join4(migrationsDir, file), "utf8");
3567
+ const apply = db.transaction(() => {
3568
+ const already = db.query("SELECT 1 AS present FROM schema_migrations WHERE version = ? LIMIT 1").get(version);
3569
+ if (already)
3570
+ return false;
3571
+ db.exec(text);
3572
+ db.run("INSERT INTO schema_migrations (version) VALUES (?)", [version]);
3573
+ return true;
3574
+ });
3575
+ if (apply.immediate())
3576
+ appliedNow.push(version);
3577
+ }
3578
+ return appliedNow;
3579
+ }
3580
+ function enableWalMode(db, attempts = 50, pauseMs = 10) {
3581
+ for (let attempt = 0;attempt < attempts; attempt += 1) {
3582
+ if (journalMode(db) === "wal")
3583
+ return true;
3584
+ try {
3585
+ if (String(db.query("PRAGMA journal_mode = WAL").get()?.journal_mode).toLowerCase() === "wal") {
3586
+ return true;
3587
+ }
3588
+ } catch {}
3589
+ Bun.sleepSync(pauseMs);
3590
+ }
3591
+ return false;
3592
+ }
3593
+ function journalMode(db) {
3594
+ try {
3595
+ return String(db.query("PRAGMA journal_mode").get()?.journal_mode ?? "").toLowerCase();
3596
+ } catch {
3597
+ return "";
3598
+ }
3599
+ }
3600
+ function openDatabase(path) {
3601
+ try {
3602
+ return new Database(path, { create: true, readwrite: true });
3603
+ } catch (error) {
3604
+ const reason = error instanceof Error ? error.message : String(error);
3605
+ throw new Error(`cannot open the skills database at ${path}: ${reason}. ` + "Check that the directory exists and is writable. Set HASNA_SKILLS_DATABASE_URL to choose a " + "different path or a postgres:// URL, or HASNA_SKILLS_DIR to relocate the whole data directory.", { cause: error });
3606
+ }
3607
+ }
3608
+ function parseScopes(value) {
3609
+ if (Array.isArray(value))
3610
+ return value.map(String);
3611
+ if (typeof value !== "string")
3612
+ return [];
3613
+ try {
3614
+ const parsed = JSON.parse(value);
3615
+ return Array.isArray(parsed) ? parsed.map(String) : [];
3616
+ } catch {
3617
+ return [];
3618
+ }
3619
+ }
3620
+
3621
+ // src/server/store.ts
3622
+ function recordFieldsOf(input, carriedBundle, carriedSkillMd) {
3623
+ return {
3624
+ slug: input.slug,
3625
+ displayName: input.displayName,
3626
+ description: input.description,
3627
+ category: input.category,
3628
+ tags: input.tags,
3629
+ source: input.source,
3630
+ kind: input.kind,
3631
+ ...input.version ? { version: input.version } : {},
3632
+ ...carriedSkillMd ? { skillMd: carriedSkillMd } : {},
3633
+ bundleSha256: input.bundle?.sha256 ?? carriedBundle.bundleSha256,
3634
+ bundleByteSize: input.bundle?.byteSize ?? carriedBundle.bundleByteSize
3635
+ };
3636
+ }
3637
+ function resolvePoolMax(env = process.env) {
3638
+ const parsed = Number.parseInt(env.HASNA_SKILLS_DATABASE_POOL_MAX || env.SKILLS_DATABASE_POOL_MAX || "", 10);
3639
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 4;
3640
+ }
3641
+ var LOG_SEQUENCE_ATTEMPTS = 12;
3642
+ async function createStore(options = {}) {
3643
+ const target = resolveDatabaseTarget(options.databaseUrl);
3644
+ const store = instantiateStore(target, options.sqlite);
3645
+ await store.verifyConnectivity?.();
3646
+ if (options.bootstrapApiKey && store.ensureBootstrapApiKey) {
3647
+ await store.ensureBootstrapApiKey(options.bootstrapApiKey);
3648
+ }
3649
+ return store;
3650
+ }
3651
+ function instantiateStore(target, sqliteOptions) {
3652
+ switch (target.kind) {
3653
+ case "postgres":
3654
+ return new PostgresSkillsStore(target.url);
3655
+ case "sqlite":
3656
+ return new SqliteSkillsStore(target.path, sqliteOptions);
3657
+ case "memory":
3658
+ return new MemorySkillsStore;
3659
+ }
3660
+ }
3661
+
3662
+ class MemorySkillsStore {
3663
+ selectionStore = new MemorySkillSelectionStore;
3664
+ executionGrantStore = new MemoryExecutionGrantStore;
3665
+ backend = { kind: "memory", durable: false, label: "memory (non-durable)" };
3666
+ apiKeys = new Map;
3667
+ runs = new Map;
3668
+ logs = new Map;
3669
+ artifacts = new Map;
3670
+ idempotency = new Map;
3671
+ skills = new Map;
3672
+ bundles = new Map;
3673
+ versions = new Map;
3674
+ pins = new Map;
3675
+ operatorEnrollments = new Map;
3676
+ constructor(apiKeys = []) {
3677
+ for (const key of apiKeys)
3678
+ this.addApiKey(key.token, key.principal);
3679
+ }
3680
+ addApiKey(token, principal) {
3681
+ const resolved = publicPrincipal(principal);
3682
+ this.apiKeys.set(hashApiKey(token), resolved);
3683
+ return resolved;
3684
+ }
3685
+ async ensureBootstrapApiKey(token, principal) {
3686
+ this.addApiKey(token, principal);
3687
+ }
3688
+ async authenticateApiKeyHash(hash) {
3689
+ return this.apiKeys.get(hash) ?? null;
3690
+ }
3691
+ async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
3692
+ for (const [hash, target] of this.apiKeys) {
3693
+ if (target.apiKeyId !== keyId || target.orgId !== actor.orgId)
3694
+ continue;
3695
+ if (target.scopes.length !== expectedScopes.length || target.scopes.some((scope, index) => scope !== expectedScopes[index])) {
3696
+ return { kind: "stale", scopes: [...target.scopes] };
3697
+ }
3698
+ const scopes = [...target.scopes, ...addScopes.filter((scope) => !target.scopes.includes(scope))];
3699
+ const updated = { ...target, scopes };
3700
+ this.apiKeys.set(hash, updated);
3701
+ return { kind: "updated", scopes };
3702
+ }
3703
+ return { kind: "not_found" };
3704
+ }
3705
+ async enrollPublishScopeByOperator(input) {
3706
+ if (!validOperatorScopeEnrollmentInput(input))
3707
+ return { kind: "invalid" };
3708
+ const previous = this.operatorEnrollments.get(input.operationId);
3709
+ if (previous) {
3710
+ if (previous.keyId !== input.keyId || previous.manifestDigest !== input.manifestDigest)
3711
+ return { kind: "target_mismatch" };
3712
+ for (const target of this.apiKeys.values()) {
3713
+ if (target.apiKeyId === input.keyId && target.orgId === input.orgId && target.scopes.includes("skills:publish")) {
3714
+ return { kind: "already_applied", scopes: [...target.scopes] };
3715
+ }
3716
+ }
3717
+ return { kind: "stale", scopes: [] };
3718
+ }
3719
+ for (const [hash, target] of this.apiKeys) {
3720
+ if (target.apiKeyId !== input.keyId || target.orgId !== input.orgId)
3721
+ continue;
3722
+ if (target.scopes.length !== input.expectedScopes.length || target.scopes.some((scope, index) => scope !== input.expectedScopes[index])) {
3723
+ return { kind: "stale", scopes: [...target.scopes] };
3724
+ }
3725
+ const scopes = target.scopes.includes("skills:publish") ? [...target.scopes] : [...target.scopes, "skills:publish"];
3726
+ this.apiKeys.set(hash, { ...target, scopes });
3727
+ this.operatorEnrollments.set(input.operationId, { keyId: input.keyId, manifestDigest: input.manifestDigest });
3728
+ return { kind: "updated", scopes };
3729
+ }
3730
+ return { kind: "not_found" };
3731
+ }
3732
+ async inspectOperatorScopeTarget(keyId, orgId) {
3733
+ for (const target of this.apiKeys.values()) {
3734
+ if (target.apiKeyId !== keyId)
3735
+ continue;
3736
+ return target.orgId === orgId ? { kind: "found", scopes: [...target.scopes] } : { kind: "target_mismatch" };
3737
+ }
3738
+ return { kind: "not_found" };
3739
+ }
3740
+ async createRun(input) {
3741
+ const idemKey = input.idempotencyKey?.trim();
3742
+ const idemMapKey = idemKey ? `${input.principal.orgId}:${idemKey}` : undefined;
3743
+ if (idemMapKey) {
3744
+ const existing = this.idempotency.get(idemMapKey);
3745
+ if (existing)
3746
+ return this.runs.get(existing);
3747
+ }
3748
+ const now = nowIso();
3749
+ const run = {
3750
+ id: runId(),
3751
+ orgId: input.principal.orgId,
3752
+ userId: input.principal.userId,
3753
+ skill: input.slug,
3754
+ requestedSlug: input.slug,
3755
+ status: "queued",
3756
+ input: input.input,
3757
+ args: input.args,
3758
+ ...idemKey ? { idempotencyKey: idemKey } : {},
3759
+ correlationId: randomUUID5(),
3760
+ costCents: 0,
3761
+ leaseGeneration: 0,
3762
+ createdAt: now
3763
+ };
3764
+ this.runs.set(run.id, run);
3765
+ this.logs.set(run.id, []);
3766
+ this.artifacts.set(run.id, []);
3767
+ if (idemMapKey)
3768
+ this.idempotency.set(idemMapKey, run.id);
3769
+ return run;
3770
+ }
3771
+ async listRuns(principal, limit) {
3772
+ return Array.from(this.runs.values()).filter((run) => run.orgId === principal.orgId).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, normalizeLimit(limit));
3773
+ }
3774
+ async getRun(principal, runId2) {
3775
+ const run = this.runs.get(runId2);
3776
+ return run && run.orgId === principal.orgId ? run : null;
3777
+ }
3778
+ async claimNextRun(_input) {
3779
+ const run = Array.from(this.runs.values()).filter((candidate) => candidate.status === "queued" || candidate.status === "retrying").sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
3780
+ if (!run)
3781
+ return null;
3782
+ return this.patchRun(run.id, { status: "running", startedAt: run.startedAt ?? nowIso(), leaseGeneration: run.leaseGeneration + 1 });
3783
+ }
3784
+ async updateRun(runId2, patch) {
3785
+ return this.patchRun(runId2, patch);
3786
+ }
3787
+ async transitionRun(runId2, patch, expectedGeneration) {
3788
+ const run = this.runs.get(runId2);
3789
+ if (!run)
3790
+ return null;
3791
+ if (run.leaseGeneration !== expectedGeneration) {
3792
+ throw new StaleLeaseGenerationError(runId2, expectedGeneration, run.leaseGeneration, run.status);
3793
+ }
3794
+ return this.patchRun(runId2, patch);
3795
+ }
3796
+ async appendLog(runId2, orgId, level, message) {
3797
+ const entries = this.logs.get(runId2) ?? [];
3798
+ const log = { runId: runId2, sequence: entries.length + 1, level, message, createdAt: nowIso() };
3799
+ entries.push(log);
3800
+ this.logs.set(runId2, entries);
3801
+ return log;
3802
+ }
3803
+ async listLogs(principal, runId2) {
3804
+ const run = await this.getRun(principal, runId2);
3805
+ return run ? [...this.logs.get(runId2) ?? []] : [];
3806
+ }
3807
+ async addArtifact(artifact) {
3808
+ const next2 = { ...artifact, createdAt: nowIso() };
3809
+ const artifacts = this.artifacts.get(artifact.runId) ?? [];
3810
+ artifacts.push(next2);
3811
+ this.artifacts.set(artifact.runId, artifacts);
3812
+ return next2;
3813
+ }
3814
+ async listArtifacts(principal, runId2) {
3815
+ const run = await this.getRun(principal, runId2);
3816
+ return run ? [...this.artifacts.get(runId2) ?? []] : [];
3817
+ }
3818
+ async getArtifact(principal, runId2, id) {
3819
+ const artifacts = await this.listArtifacts(principal, runId2);
3820
+ return artifacts.find((artifact) => artifact.id === id) ?? null;
3821
+ }
3822
+ async publishSkill(input) {
3823
+ const key = skillKey(input.principal.orgId, input.slug);
3824
+ const now = nowIso();
3825
+ const previous = this.skills.get(key);
3826
+ if (input.seedBundledOnly && input.expectedRevisionId && !previous) {
3827
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, null);
3828
+ }
3829
+ if (input.seedBundledOnly && previous && (previous.tombstonedAt || previous.source !== "bundled")) {
3830
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, previous.revisionId);
3831
+ }
3832
+ if (previous && !previous.tombstonedAt && input.expectedRevisionId !== previous.revisionId) {
3833
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, previous.revisionId);
3834
+ }
3835
+ const versionSha = input.bundle?.sha256 ?? previous?.bundleSha256;
3836
+ if (input.version && versionSha) {
3837
+ const existing = this.versions.get(versionKey(input.principal.orgId, input.slug, input.version));
3838
+ if (existing && existing.bundleSha256 !== versionSha) {
3839
+ throw new SkillVersionExistsError(input.slug, input.version, existing.bundleSha256, versionSha);
3840
+ }
3841
+ }
3842
+ if (input.bundle) {
3843
+ const bundleMapKey = skillKey(input.principal.orgId, input.bundle.sha256);
3844
+ this.bundles.set(bundleMapKey, {
3845
+ ...this.bundles.get(bundleMapKey),
3846
+ ...input.bundle,
3847
+ orgId: input.principal.orgId,
3848
+ createdAt: this.bundles.get(bundleMapKey)?.createdAt ?? now
3849
+ });
3850
+ }
3851
+ const carriedBundle = !input.bundle && previous?.bundleSha256 ? { bundleSha256: previous.bundleSha256, ...previous.bundleByteSize === undefined ? {} : { bundleByteSize: previous.bundleByteSize } } : {};
3852
+ const carriedSkillMd = typeof input.skillMd === "string" ? input.skillMd : previous?.skillMd;
3853
+ const record = {
3854
+ orgId: input.principal.orgId,
3855
+ slug: input.slug,
3856
+ displayName: input.displayName,
3857
+ description: input.description,
3858
+ category: input.category,
3859
+ tags: [...input.tags],
3860
+ source: input.source,
3861
+ kind: input.kind,
3862
+ ...input.version ? { version: input.version } : {},
3863
+ ...carriedSkillMd ? { skillMd: carriedSkillMd } : {},
3864
+ ...input.bundle ? { bundleSha256: input.bundle.sha256, bundleByteSize: input.bundle.byteSize } : carriedBundle,
3865
+ publishedByUserId: input.principal.userId,
3866
+ createdAt: previous?.createdAt ?? now,
3867
+ updatedAt: now,
3868
+ revisionId: revisionIdOfRecord(recordFieldsOf(input, carriedBundle, carriedSkillMd)),
3869
+ revisionNumber: (previous?.revisionNumber ?? 0) + 1,
3870
+ lifecycle: previous?.lifecycle ?? "active",
3871
+ ...previous?.archivedAt ? { archivedAt: previous.archivedAt } : {},
3872
+ ...previous?.archiveReason ? { archiveReason: previous.archiveReason } : {},
3873
+ ...previous?.replacementSlug ? { replacementSlug: previous.replacementSlug } : {}
3874
+ };
3875
+ this.skills.set(key, record);
3876
+ if (input.version && versionSha && !this.versions.has(versionKey(input.principal.orgId, input.slug, input.version))) {
3877
+ this.versions.set(versionKey(input.principal.orgId, input.slug, input.version), {
3878
+ orgId: input.principal.orgId,
3879
+ slug: input.slug,
3880
+ version: input.version,
3881
+ bundleSha256: versionSha,
3882
+ bundleByteSize: input.bundle?.byteSize ?? previous?.bundleByteSize ?? 0,
3883
+ storageKind: input.versionStorage?.storageKind ?? "db",
3884
+ ...input.versionStorage?.storageKey ? { storageKey: input.versionStorage.storageKey } : {},
3885
+ manifest: { ...input.versionManifest ?? {} },
3886
+ ...input.principal.userId ? { publishedByUserId: input.principal.userId } : {},
3887
+ createdAt: now
3888
+ });
3889
+ }
3890
+ if (previous?.bundleSha256 && input.bundle && previous.bundleSha256 !== input.bundle.sha256) {
3891
+ this.collectOrphanBundle(input.principal.orgId, previous.bundleSha256);
3892
+ }
3893
+ return record;
3894
+ }
3895
+ async listSkillVersions(principal, slug) {
3896
+ return Array.from(this.versions.values()).filter((v) => v.orgId === principal.orgId && v.slug === slug).sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.version.localeCompare(a.version));
3897
+ }
3898
+ async getSkillVersion(principal, slug, version) {
3899
+ const found = this.versions.get(versionKey(principal.orgId, slug, version));
3900
+ return found && found.orgId === principal.orgId ? found : null;
3901
+ }
3902
+ async listSkills(principal) {
3903
+ return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).sort((a, b) => a.slug.localeCompare(b.slug));
3904
+ }
3905
+ async getSkill(principal, slug) {
3906
+ const skill = this.skills.get(skillKey(principal.orgId, slug));
3907
+ return skill && skill.orgId === principal.orgId ? skill : null;
3908
+ }
3909
+ async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
3910
+ const current = await this.getSkill(principal, slug);
3911
+ if (!current || current.tombstonedAt)
3912
+ return null;
3913
+ if (expectedRevisionId !== current.revisionId)
3914
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
3915
+ if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
3916
+ const profiles = await this.selectionStore.profilesReferencingSkill(principal, slug);
3917
+ if (profiles.length)
3918
+ throw new SkillLifecycleConflictError(slug, profiles);
3919
+ }
3920
+ const next2 = {
3921
+ ...current,
3922
+ lifecycle: patch.lifecycle,
3923
+ updatedAt: nowIso(),
3924
+ revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
3925
+ revisionNumber: current.revisionNumber + 1,
3926
+ ...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {}
3927
+ };
3928
+ if (patch.lifecycle === "active") {
3929
+ delete next2.archivedAt;
3930
+ delete next2.archiveReason;
3931
+ delete next2.replacementSlug;
3932
+ }
3933
+ this.skills.set(skillKey(principal.orgId, slug), next2);
3934
+ return next2;
3935
+ }
3936
+ async updateSkill(principal, slug, patch, expectedRevisionId) {
3937
+ const current = await this.getSkill(principal, slug);
3938
+ if (!current || current.tombstonedAt)
3939
+ return null;
3940
+ if (expectedRevisionId !== current.revisionId) {
3941
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
3942
+ }
3943
+ const latest = this.skills.get(skillKey(principal.orgId, slug));
3944
+ if (!latest || latest.tombstonedAt)
3945
+ return null;
3946
+ if (latest.revisionId !== current.revisionId) {
3947
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, latest.revisionId);
3948
+ }
3949
+ const next2 = {
3950
+ ...latest,
3951
+ ...patch,
3952
+ updatedAt: nowIso(),
3953
+ revisionId: revisionIdOfRecord({ ...latest, ...patch }),
3954
+ revisionNumber: latest.revisionNumber + 1
3955
+ };
3956
+ this.skills.set(skillKey(principal.orgId, slug), next2);
3957
+ return next2;
3958
+ }
3959
+ async deleteSkill(principal, slug, tombstoneWindowMs) {
3960
+ const current = await this.getSkill(principal, slug);
3961
+ if (!current)
3962
+ return null;
3963
+ if (!current.tombstonedAt) {
3964
+ const tombstoned = nowIso();
3965
+ const purgeAfter = new Date(Date.now() + tombstoneWindowMs).toISOString();
3966
+ const next2 = { ...current, tombstonedAt: tombstoned, tombstonePurgeAfter: purgeAfter, updatedAt: tombstoned };
3967
+ this.skills.set(skillKey(principal.orgId, slug), next2);
3968
+ return next2;
3969
+ }
3970
+ return current;
3971
+ }
3972
+ async purgeExpiredTombstones(principal) {
3973
+ const now = nowIso();
3974
+ const purged = [];
3975
+ for (const [key, skill] of this.skills) {
3976
+ if (skill.orgId !== principal.orgId || !skill.tombstonedAt || !skill.tombstonePurgeAfter)
3977
+ continue;
3978
+ if (skill.tombstonePurgeAfter > now)
3979
+ continue;
3980
+ this.skills.delete(key);
3981
+ if (skill.bundleSha256)
3982
+ this.collectOrphanBundle(principal.orgId, skill.bundleSha256);
3983
+ purged.push(skill);
3984
+ }
3985
+ return purged;
3986
+ }
3987
+ async getSkillBundle(principal, sha256) {
3988
+ const bundle = this.bundles.get(skillKey(principal.orgId, sha256));
3989
+ return bundle && bundle.orgId === principal.orgId ? bundle : null;
3990
+ }
3991
+ async pinSkill(principal, slug, metadata = {}) {
3992
+ const pin = { orgId: principal.orgId, principal: principal.apiKeyId, slug, pinnedAt: nowIso(), metadata: { ...metadata } };
3993
+ this.pins.set(pinKey(principal.orgId, principal.apiKeyId, slug), pin);
3994
+ return pin;
3995
+ }
3996
+ async unpinSkill(principal, slug) {
3997
+ return this.pins.delete(pinKey(principal.orgId, principal.apiKeyId, slug));
3998
+ }
3999
+ async listPins(principal) {
4000
+ return Array.from(this.pins.values()).filter((pin) => pin.orgId === principal.orgId && pin.principal === principal.apiKeyId).sort((a, b) => a.slug.localeCompare(b.slug));
4001
+ }
4002
+ async listTags(principal) {
4003
+ await this.purgeExpiredTombstones(principal);
4004
+ const tags = new Set;
4005
+ for (const skill of this.skills.values()) {
4006
+ if (skill.orgId !== principal.orgId || skill.lifecycle !== "active")
4007
+ continue;
4008
+ for (const tag of skill.tags) {
4009
+ if (tag.trim())
4010
+ tags.add(tag);
4011
+ }
4012
+ }
4013
+ return [...tags].sort();
4014
+ }
4015
+ async listSkillsByTag(principal, tag) {
4016
+ await this.purgeExpiredTombstones(principal);
4017
+ return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt && skill.lifecycle === "active" && skill.tags.includes(tag)).sort((a, b) => a.slug.localeCompare(b.slug));
4018
+ }
4019
+ async listPinsByTag(principal, tag) {
4020
+ await this.purgeExpiredTombstones(principal);
4021
+ const taggedSlugs = new Set;
4022
+ for (const skill of this.skills.values()) {
4023
+ if (skill.orgId === principal.orgId && !skill.tombstonedAt && skill.lifecycle === "active" && skill.tags.includes(tag))
4024
+ taggedSlugs.add(skill.slug);
4025
+ }
4026
+ return Array.from(this.pins.values()).filter((pin) => pin.orgId === principal.orgId && pin.principal === principal.apiKeyId && taggedSlugs.has(pin.slug)).sort((a, b) => a.slug.localeCompare(b.slug));
4027
+ }
4028
+ async listPublishedSlugs(principal) {
4029
+ return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).map((skill) => skill.slug).sort();
4030
+ }
4031
+ collectOrphanBundle(orgId, sha256) {
4032
+ const referenced = Array.from(this.skills.values()).some((skill) => skill.orgId === orgId && skill.bundleSha256 === sha256) || Array.from(this.versions.values()).some((v) => v.orgId === orgId && v.bundleSha256 === sha256);
4033
+ if (!referenced)
4034
+ this.bundles.delete(skillKey(orgId, sha256));
4035
+ }
4036
+ patchRun(runId2, patch) {
4037
+ const run = this.runs.get(runId2);
4038
+ if (!run)
4039
+ return null;
4040
+ const next2 = { ...run, ...patch };
4041
+ this.runs.set(runId2, next2);
4042
+ return next2;
4043
+ }
4044
+ }
4045
+ function skillKey(orgId, slug) {
4046
+ return `${orgId.length}:${orgId}:${slug}`;
4047
+ }
4048
+ function pinKey(orgId, principal, slug) {
4049
+ return `${orgId.length}:${orgId}:${principal.length}:${principal}:${slug}`;
4050
+ }
4051
+ function versionKey(orgId, slug, version) {
4052
+ return `${orgId.length}:${orgId}:${slug.length}:${slug}:${version}`;
4053
+ }
4054
+
4055
+ class PostgresSkillsStore {
4056
+ get selectionStore() {
4057
+ return new PostgresSkillSelectionStore(this.sql);
4058
+ }
4059
+ get executionGrantStore() {
4060
+ return new PostgresExecutionGrantStore(this.sql);
4061
+ }
4062
+ backend = { kind: "postgres", durable: true, label: "postgres" };
4063
+ sql;
4064
+ constructor(databaseUrl) {
4065
+ const bunWithSql = Bun;
4066
+ this.sql = new bunWithSql.SQL(databaseUrl, { max: resolvePoolMax() });
4067
+ }
4068
+ async verifyConnectivity() {
4069
+ try {
4070
+ await this.sql`SELECT 1`;
4071
+ } catch (error) {
4072
+ throw new Error("cannot reach the configured Postgres database. The server will not start with an unreachable " + "database rather than fall back to another backend and silently split your data across two stores. " + `Check HASNA_SKILLS_DATABASE_URL and that the instance is accepting connections. Driver reported: ${connectionFailureSummary(error)}`);
4073
+ }
4074
+ try {
4075
+ await this.sql`SELECT 1 FROM api_keys LIMIT 0`;
4076
+ } catch (error) {
4077
+ throw new Error("the configured Postgres database is reachable but has no skills schema. Run `skills-migrate` against it " + "before starting the server - unlike SQLite, Postgres is not migrated automatically, so that several " + `replicas cannot race to migrate a shared database. Driver reported: ${connectionFailureSummary(error)}`);
4078
+ }
4079
+ await this.backfillLegacyRevisions();
4080
+ }
4081
+ async backfillLegacyRevisions() {
4082
+ const rows = await this.sql`SELECT * FROM skills_registry WHERE revision_id = ${""}`;
4083
+ for (const row of rows) {
4084
+ const record = rowToSkill(row);
4085
+ await this.sql`UPDATE skills_registry SET revision_id = ${revisionIdOfRecord(record)} WHERE org_id = ${record.orgId} AND slug = ${record.slug}`;
4086
+ }
4087
+ }
4088
+ async close() {
4089
+ await this.sql.close?.();
4090
+ }
4091
+ async ensureBootstrapApiKey(token, principal) {
4092
+ const resolved = publicPrincipal(principal);
4093
+ await this.sql`
4094
+ INSERT INTO organizations (id, slug, name)
4095
+ VALUES (${resolved.orgId}, ${resolved.orgSlug}, ${resolved.orgName})
4096
+ ON CONFLICT (id) DO UPDATE SET slug = EXCLUDED.slug, name = EXCLUDED.name
4097
+ `;
4098
+ await this.sql`
4099
+ INSERT INTO users (id, email, name)
4100
+ VALUES (${resolved.userId}, ${resolved.email}, ${resolved.email})
4101
+ ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email
4102
+ `;
4103
+ await this.sql`
4104
+ INSERT INTO organization_members (org_id, user_id, role)
4105
+ VALUES (${resolved.orgId}, ${resolved.userId}, ${resolved.role})
4106
+ ON CONFLICT (org_id, user_id) DO UPDATE SET role = EXCLUDED.role
4107
+ `;
4108
+ await this.sql`
4109
+ INSERT INTO api_keys (id, org_id, user_id, name, key_hash, scopes_json)
4110
+ VALUES (${resolved.apiKeyId}, ${resolved.orgId}, ${resolved.userId}, ${"bootstrap"}, ${hashApiKey(token)}, ${JSON.stringify(resolved.scopes)}::jsonb)
4111
+ ON CONFLICT (key_hash) DO NOTHING
4112
+ `;
4113
+ }
4114
+ async authenticateApiKeyHash(hash) {
4115
+ const rows = await this.sql`
4116
+ SELECT k.id AS api_key_id, k.scopes_json, o.id AS org_id, o.slug AS org_slug, o.name AS org_name,
4117
+ u.id AS user_id, u.email, m.role
4118
+ FROM api_keys k
4119
+ JOIN organizations o ON o.id = k.org_id
4120
+ JOIN users u ON u.id = k.user_id
4121
+ LEFT JOIN organization_members m ON m.org_id = k.org_id AND m.user_id = k.user_id
4122
+ WHERE k.key_hash = ${hash} AND k.revoked_at IS NULL
4123
+ LIMIT 1
4124
+ `;
4125
+ const row = rows[0];
4126
+ if (!row)
4127
+ return null;
4128
+ await this.sql`UPDATE api_keys SET last_used_at = now() WHERE id = ${String(row.api_key_id)}`;
4129
+ return {
4130
+ apiKeyId: String(row.api_key_id),
4131
+ orgId: String(row.org_id),
4132
+ orgSlug: String(row.org_slug),
4133
+ orgName: String(row.org_name),
4134
+ userId: String(row.user_id),
4135
+ email: String(row.email),
4136
+ role: typeof row.role === "string" ? row.role : "member",
4137
+ scopes: parseJsonArray(row.scopes_json)
4138
+ };
4139
+ }
4140
+ async updateApiKeyScopes(actor, keyId, expectedScopes, addScopes) {
4141
+ return this.sql.begin(async (tx) => {
4142
+ const rows = await tx`
4143
+ SELECT scopes_json FROM api_keys
4144
+ WHERE id = ${keyId} AND org_id = ${actor.orgId} AND revoked_at IS NULL
4145
+ LIMIT 1
4146
+ `;
4147
+ const row = rows[0];
4148
+ if (!row)
4149
+ return { kind: "not_found" };
4150
+ const current = parseJsonArray(row.scopes_json);
4151
+ if (current.length !== expectedScopes.length || current.some((scope, index) => scope !== expectedScopes[index])) {
4152
+ return { kind: "stale", scopes: current };
4153
+ }
4154
+ const updated = await this.casUpdateApiKeyScopes(tx, keyId, actor.orgId, expectedScopes, addScopes);
4155
+ if (!updated[0]) {
4156
+ const latest = await tx`SELECT scopes_json FROM api_keys WHERE id = ${keyId} AND org_id = ${actor.orgId} AND revoked_at IS NULL LIMIT 1`;
4157
+ return { kind: "stale", scopes: latest[0] ? parseJsonArray(latest[0].scopes_json) : current };
4158
+ }
4159
+ await tx`
4160
+ INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, metadata_json)
4161
+ VALUES (${actor.orgId}, ${actor.userId}, ${actor.apiKeyId}, ${"api_key_scopes_added"}, ${"api_key"}, ${keyId}, ${JSON.stringify({ added: addScopes, scopes: parseJsonArray(updated[0].scopes_json) })}::jsonb)
4162
+ `;
4163
+ return { kind: "updated", scopes: parseJsonArray(updated[0].scopes_json) };
4164
+ });
4165
+ }
4166
+ async casUpdateApiKeyScopes(tx, keyId, orgId, expectedScopes, addScopes) {
4167
+ const current = expectedScopes;
4168
+ const scopes = [...current, ...addScopes.filter((scope) => !current.includes(scope))];
4169
+ return tx`
4170
+ UPDATE api_keys SET scopes_json = ${JSON.stringify(scopes)}::jsonb
4171
+ WHERE id = ${keyId} AND org_id = ${orgId} AND revoked_at IS NULL
4172
+ AND scopes_json = ${JSON.stringify(expectedScopes)}::jsonb
4173
+ RETURNING scopes_json
4174
+ `;
4175
+ }
4176
+ async enrollPublishScopeByOperator(input) {
4177
+ if (!validOperatorScopeEnrollmentInput(input))
4178
+ return { kind: "invalid" };
4179
+ if (input.expectedScopes.includes("skills:publish"))
4180
+ return { kind: "stale", scopes: input.expectedScopes };
4181
+ return this.sql.begin(async (tx) => {
4182
+ const operation = await tx`
4183
+ SELECT org_id, target_id FROM skills_audit_events
4184
+ WHERE action = ${"api_key_scopes_added"} AND operator_operation_id = ${input.operationId}
4185
+ LIMIT 1
4186
+ `;
4187
+ if (operation[0] && (String(operation[0].org_id) !== input.orgId || String(operation[0].target_id) !== input.keyId))
4188
+ return { kind: "target_mismatch" };
4189
+ const rows = await tx`
4190
+ SELECT id, org_id, name, scopes_json FROM api_keys
4191
+ WHERE id = ${input.keyId} AND revoked_at IS NULL
4192
+ FOR UPDATE
4193
+ `;
4194
+ const row = rows[0];
4195
+ if (!row)
4196
+ return { kind: "not_found" };
4197
+ if (String(row.org_id) !== input.orgId)
4198
+ return { kind: "target_mismatch" };
4199
+ const current = parseJsonArray(row.scopes_json);
4200
+ const prior = await tx`
4201
+ SELECT metadata_json, metadata_json->>'target_manifest_digest' AS manifest_digest FROM skills_audit_events
4202
+ WHERE action = ${"api_key_scopes_added"} AND target_type = ${"api_key"} AND target_id = ${input.keyId}
4203
+ AND operator_operation_id = ${input.operationId}
4204
+ LIMIT 1
4205
+ `;
4206
+ if (prior[0]) {
4207
+ const priorMetadata = typeof prior[0].metadata_json === "string" ? JSON.parse(prior[0].metadata_json) : prior[0].metadata_json;
4208
+ const priorDigest = prior[0].manifest_digest ?? priorMetadata?.target_manifest_digest;
4209
+ if (String(priorDigest) !== input.manifestDigest)
4210
+ return { kind: "target_mismatch" };
4211
+ if (!current.includes("skills:publish"))
4212
+ return { kind: "stale", scopes: current };
4213
+ return { kind: "already_applied", scopes: current };
4214
+ }
4215
+ if (current.length !== input.expectedScopes.length || current.some((scope, index) => scope !== input.expectedScopes[index])) {
4216
+ return { kind: "stale", scopes: current };
4217
+ }
4218
+ const updated = await this.casUpdateApiKeyScopes(tx, input.keyId, input.orgId, input.expectedScopes, ["skills:publish"]);
4219
+ if (!updated[0])
4220
+ return { kind: "stale", scopes: current };
4221
+ const scopes = parseJsonArray(updated[0].scopes_json);
4222
+ await tx`
4223
+ INSERT INTO skills_audit_events (org_id, user_id, api_key_id, action, target_type, target_id, operator_operation_id, metadata_json)
4224
+ VALUES (${input.orgId}, NULL, NULL, ${"api_key_scopes_added"}, ${"api_key"}, ${input.keyId}, ${input.operationId}, ${JSON.stringify({
4225
+ added: ["skills:publish"],
4226
+ scopes,
4227
+ operator_operation_id: input.operationId,
4228
+ operator_job_id: input.operatorJobId,
4229
+ operator_task_arn: input.operatorTaskArn,
4230
+ target_manifest_digest: input.manifestDigest,
4231
+ station_id: input.stationId
4232
+ })}::jsonb)
4233
+ `;
4234
+ return { kind: "updated", scopes };
4235
+ });
4236
+ }
4237
+ async inspectOperatorScopeTarget(keyId, orgId) {
4238
+ const rows = await this.sql`SELECT org_id, scopes_json FROM api_keys WHERE id = ${keyId} AND revoked_at IS NULL LIMIT 1`;
4239
+ const row = rows[0];
4240
+ if (!row)
4241
+ return { kind: "not_found" };
4242
+ if (String(row.org_id) !== orgId)
4243
+ return { kind: "target_mismatch" };
4244
+ return { kind: "found", scopes: parseJsonArray(row.scopes_json) };
4245
+ }
4246
+ async withContext(orgId, worker, fn) {
4247
+ return this.sql.begin(async (tx) => {
4248
+ await tx`SELECT set_config('app.skills_org_id', ${orgId ?? ""}, true)`;
4249
+ await tx`SELECT set_config('app.skills_claim_context', ${worker ? "worker" : ""}, true)`;
4250
+ return await fn(tx);
4251
+ });
4252
+ }
4253
+ async createRun(input) {
4254
+ return this.withContext(input.principal.orgId, false, async (tx) => {
4255
+ if (input.idempotencyKey) {
4256
+ const existing = await tx`
4257
+ SELECT * FROM skills_runs
4258
+ WHERE org_id = ${input.principal.orgId} AND idempotency_key = ${input.idempotencyKey}
4259
+ LIMIT 1
4260
+ `;
4261
+ if (existing[0])
4262
+ return rowToRun(existing[0]);
4263
+ }
4264
+ const id = runId();
4265
+ const rows = await tx`
4266
+ INSERT INTO skills_runs (id, org_id, user_id, skill_slug, requested_slug, status, input_json, args_json, idempotency_key, correlation_id)
4267
+ VALUES (${id}, ${input.principal.orgId}, ${input.principal.userId}, ${input.slug}, ${input.slug}, ${"queued"}, ${JSON.stringify(input.input)}::jsonb, ${JSON.stringify(input.args)}::jsonb, ${input.idempotencyKey ?? null}, ${randomUUID5()})
4268
+ RETURNING *
4269
+ `;
4270
+ return rowToRun(rows[0]);
4271
+ });
4272
+ }
4273
+ async listRuns(principal, limit) {
4274
+ return this.withContext(principal.orgId, false, async (tx) => {
4275
+ const rows = await tx`
4276
+ SELECT * FROM skills_runs WHERE org_id = ${principal.orgId}
4277
+ ORDER BY created_at DESC LIMIT ${normalizeLimit(limit)}
4278
+ `;
4279
+ return rows.map(rowToRun);
4280
+ });
4281
+ }
4282
+ async getRun(principal, runId2) {
4283
+ return this.withContext(principal.orgId, false, async (tx) => {
4284
+ const rows = await tx`
4285
+ SELECT * FROM skills_runs WHERE id = ${runId2} AND org_id = ${principal.orgId} LIMIT 1
4286
+ `;
4287
+ return rows[0] ? rowToRun(rows[0]) : null;
4288
+ });
4289
+ }
4290
+ async claimNextRun(input) {
4291
+ return this.withContext(null, true, async (tx) => {
4292
+ const rows = await tx`
4293
+ UPDATE skills_runs
4294
+ SET status = ${"running"}, started_at = COALESCE(started_at, now()), locked_by = ${input.workerId}, locked_at = now(),
4295
+ lease_generation = lease_generation + 1
4296
+ WHERE id = (
4297
+ SELECT id FROM skills_runs
4298
+ WHERE status IN (${"queued"}, ${"retrying"})
4299
+ ORDER BY created_at ASC
4300
+ FOR UPDATE SKIP LOCKED
4301
+ LIMIT 1
4302
+ )
4303
+ RETURNING *
4304
+ `;
4305
+ return rows[0] ? rowToRun(rows[0]) : null;
4306
+ });
4307
+ }
4308
+ async updateRun(runId2, patch) {
4309
+ return this.withContext(null, true, async (tx) => {
4310
+ const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId2} LIMIT 1`;
4311
+ if (!current[0])
4312
+ return null;
4313
+ const run = { ...rowToRun(current[0]), ...patch };
4314
+ const rows = await tx`
4315
+ UPDATE skills_runs
4316
+ SET status = ${run.status},
4317
+ output_type = ${run.outputType ?? null},
4318
+ output_preview = ${run.outputPreview ?? null},
4319
+ error_code = ${run.errorCode ?? null},
4320
+ error_message = ${run.errorMessage ?? null},
4321
+ started_at = ${run.startedAt ?? null},
4322
+ completed_at = ${run.completedAt ?? null}
4323
+ WHERE id = ${runId2}
4324
+ RETURNING *
4325
+ `;
4326
+ return rows[0] ? rowToRun(rows[0]) : null;
4327
+ });
4328
+ }
4329
+ async transitionRun(runId2, patch, expectedGeneration) {
4330
+ return this.withContext(null, true, async (tx) => {
4331
+ const current = await tx`SELECT * FROM skills_runs WHERE id = ${runId2} LIMIT 1`;
4332
+ if (!current[0])
4333
+ return null;
4334
+ const stored = rowToRun(current[0]);
4335
+ if (stored.leaseGeneration !== expectedGeneration) {
4336
+ throw new StaleLeaseGenerationError(runId2, expectedGeneration, stored.leaseGeneration, stored.status);
4337
+ }
4338
+ const run = { ...stored, ...patch };
4339
+ const rows = await tx`
4340
+ UPDATE skills_runs
4341
+ SET status = ${run.status},
4342
+ output_type = ${run.outputType ?? null},
4343
+ output_preview = ${run.outputPreview ?? null},
4344
+ error_code = ${run.errorCode ?? null},
4345
+ error_message = ${run.errorMessage ?? null},
4346
+ started_at = ${run.startedAt ?? null},
4347
+ completed_at = ${run.completedAt ?? null},
4348
+ lease_generation = ${run.leaseGeneration}
4349
+ WHERE id = ${runId2} AND lease_generation = ${expectedGeneration}
4350
+ RETURNING *
4351
+ `;
4352
+ return rows[0] ? rowToRun(rows[0]) : null;
4353
+ });
4354
+ }
4355
+ async appendLog(runId2, orgId, level, message) {
4356
+ let lastError;
4357
+ for (let attempt = 0;attempt < LOG_SEQUENCE_ATTEMPTS; attempt += 1) {
4358
+ try {
4359
+ const rows = await this.withContext(orgId, true, async (tx) => {
4360
+ return await tx`
4361
+ INSERT INTO skills_run_logs (run_id, org_id, sequence, level, message)
4362
+ VALUES (
4363
+ ${runId2},
4364
+ ${orgId},
4365
+ (SELECT COALESCE(MAX(sequence), 0) + 1 FROM skills_run_logs WHERE run_id = ${runId2}),
4366
+ ${level},
4367
+ ${message}
4368
+ )
4369
+ RETURNING *
4370
+ `;
4371
+ });
4372
+ return rowToLog(rows[0]);
4373
+ } catch (error) {
4374
+ if (!isUniqueViolation(error))
4375
+ throw error;
4376
+ lastError = error;
4377
+ }
4378
+ }
4379
+ throw lastError;
4380
+ }
4381
+ async listLogs(principal, runId2) {
4382
+ return this.withContext(principal.orgId, false, async (tx) => {
4383
+ const rows = await tx`
4384
+ SELECT l.* FROM skills_run_logs l
4385
+ JOIN skills_runs r ON r.id = l.run_id AND r.org_id = ${principal.orgId}
4386
+ WHERE l.run_id = ${runId2}
4387
+ ORDER BY l.sequence ASC
4388
+ `;
4389
+ return rows.map(rowToLog);
4390
+ });
4391
+ }
4392
+ async addArtifact(artifact) {
4393
+ return this.withContext(artifact.orgId, true, async (tx) => {
4394
+ const rows = await tx`
4395
+ INSERT INTO skills_artifacts (id, run_id, org_id, file_name, relative_path, content_type, byte_size, sha256, storage_kind, storage_key, body_text, visibility, expires_at)
4396
+ VALUES (${artifact.id}, ${artifact.runId}, ${artifact.orgId}, ${artifact.fileName}, ${artifact.relativePath}, ${artifact.contentType}, ${artifact.byteSize}, ${artifact.sha256}, ${artifact.storageKind}, ${artifact.storageKey ?? null}, ${artifact.bodyText ?? null}, ${artifact.visibility}, ${artifact.expiresAt ?? null})
4397
+ RETURNING *
4398
+ `;
4399
+ return rowToArtifact(rows[0]);
4400
+ });
4401
+ }
4402
+ async listArtifacts(principal, runId2) {
4403
+ return this.withContext(principal.orgId, false, async (tx) => {
4404
+ const rows = await tx`
4405
+ SELECT a.* FROM skills_artifacts a
4406
+ JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ${principal.orgId}
4407
+ WHERE a.run_id = ${runId2}
4408
+ ORDER BY a.created_at ASC
4409
+ `;
4410
+ return rows.map(rowToArtifact);
4411
+ });
4412
+ }
4413
+ async getArtifact(principal, runId2, artifactId2) {
4414
+ return this.withContext(principal.orgId, false, async (tx) => {
4415
+ const rows = await tx`
4416
+ SELECT a.* FROM skills_artifacts a
4417
+ JOIN skills_runs r ON r.id = a.run_id AND r.org_id = ${principal.orgId}
4418
+ WHERE a.run_id = ${runId2} AND a.id = ${artifactId2}
4419
+ LIMIT 1
4420
+ `;
4421
+ return rows[0] ? rowToArtifact(rows[0]) : null;
4422
+ });
4423
+ }
4424
+ async publishSkill(input) {
4425
+ const orgId = input.principal.orgId;
4426
+ return await this.sql.begin(async (tx) => {
4427
+ const previousRows = await tx`
4428
+ SELECT revision_id, revision_number, bundle_sha256, bundle_byte_size, skill_md, tombstoned_at, source
4429
+ FROM skills_registry WHERE org_id = ${orgId} AND slug = ${input.slug} LIMIT 1
4430
+ `;
4431
+ const previous = previousRows[0];
4432
+ const previousSha = typeof previous?.bundle_sha256 === "string" ? String(previous.bundle_sha256) : null;
4433
+ const previousRevisionId = typeof previous?.revision_id === "string" && previous.revision_id ? String(previous.revision_id) : null;
4434
+ const tombstoned = previous?.tombstoned_at != null;
4435
+ if (input.seedBundledOnly && input.expectedRevisionId && !previous) {
4436
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, null);
4437
+ }
4438
+ if (input.seedBundledOnly && previous && (tombstoned || previous.source !== "bundled")) {
4439
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, previousRevisionId);
4440
+ }
4441
+ const carriedSkillMd = typeof input.skillMd === "string" ? input.skillMd : typeof previous?.skill_md === "string" ? String(previous.skill_md) : null;
4442
+ if (previous && !tombstoned && input.expectedRevisionId !== previousRevisionId) {
4443
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, previousRevisionId);
4444
+ }
4445
+ const carriedSha = input.bundle?.sha256 ?? previousSha;
4446
+ if (input.version && carriedSha) {
4447
+ const existingVersion = await tx`
4448
+ SELECT bundle_sha256 FROM skills_versions WHERE org_id = ${orgId} AND slug = ${input.slug} AND version = ${input.version} LIMIT 1
4449
+ `;
4450
+ const existingSha = existingVersion[0] && typeof existingVersion[0].bundle_sha256 === "string" ? String(existingVersion[0].bundle_sha256) : null;
4451
+ if (existingSha && existingSha !== carriedSha) {
4452
+ throw new SkillVersionExistsError(input.slug, input.version, existingSha, carriedSha);
4453
+ }
4454
+ }
4455
+ const carriedSize = input.bundle?.byteSize ?? (previous?.bundle_byte_size == null ? null : Number(previous.bundle_byte_size));
4456
+ const revisionId = revisionIdOfRecord({
4457
+ slug: input.slug,
4458
+ displayName: input.displayName,
4459
+ description: input.description,
4460
+ category: input.category,
4461
+ tags: input.tags,
4462
+ source: input.source,
4463
+ kind: input.kind,
4464
+ ...input.version ? { version: input.version } : {},
4465
+ ...carriedSkillMd ? { skillMd: carriedSkillMd } : {},
4466
+ ...carriedSha ? { bundleSha256: carriedSha } : {},
4467
+ ...carriedSize === null || carriedSize === undefined ? {} : { bundleByteSize: carriedSize }
4468
+ });
4469
+ if (input.bundle) {
4470
+ await tx`
4471
+ INSERT INTO skills_bundles (org_id, sha256, byte_size, content_type, storage_kind, storage_key, body_blob)
4472
+ VALUES (${orgId}, ${input.bundle.sha256}, ${input.bundle.byteSize}, ${input.bundle.contentType}, ${input.bundle.storageKind}, ${input.bundle.storageKey ?? null}, ${input.bundle.bytes ?? null})
4473
+ ON CONFLICT (org_id, sha256) DO UPDATE SET
4474
+ byte_size = EXCLUDED.byte_size,
4475
+ content_type = EXCLUDED.content_type,
4476
+ storage_kind = EXCLUDED.storage_kind,
4477
+ storage_key = EXCLUDED.storage_key,
4478
+ body_blob = EXCLUDED.body_blob
4479
+ `;
4480
+ }
4481
+ if (input.seedBundledOnly) {
4482
+ const seedRows = await tx`SELECT revision_id FROM skills_registry WHERE org_id = ${orgId} AND slug = ${input.slug} FOR UPDATE`;
4483
+ if (input.expectedRevisionId && !seedRows[0]) {
4484
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, null);
4485
+ }
4486
+ }
4487
+ const rows = await tx`
4488
+ INSERT INTO skills_registry (org_id, slug, display_name, description, category, tags_json, source, kind, version, skill_md,
4489
+ bundle_sha256, bundle_byte_size, published_by_user_id, revision_id, revision_number, updated_at)
4490
+ VALUES (${orgId}, ${input.slug}, ${input.displayName}, ${input.description}, ${input.category}, ${JSON.stringify(input.tags)}::jsonb,
4491
+ ${input.source}, ${input.kind}, ${input.version ?? null}, ${carriedSkillMd},
4492
+ ${input.bundle?.sha256 ?? null}, ${input.bundle?.byteSize ?? null}, ${input.principal.userId}, ${revisionId}, 1, now())
4493
+ ON CONFLICT (org_id, slug) DO UPDATE SET
4494
+ display_name = EXCLUDED.display_name,
4495
+ description = EXCLUDED.description,
4496
+ category = EXCLUDED.category,
4497
+ tags_json = EXCLUDED.tags_json,
4498
+ source = EXCLUDED.source,
4499
+ kind = EXCLUDED.kind,
4500
+ version = EXCLUDED.version,
4501
+ skill_md = EXCLUDED.skill_md,
4502
+ -- COALESCE: see the SQLite twin. A bundle-less publish is a metadata update,
4503
+ -- not an instruction to discard the stored tarball.
4504
+ bundle_sha256 = COALESCE(EXCLUDED.bundle_sha256, skills_registry.bundle_sha256),
4505
+ bundle_byte_size = COALESCE(EXCLUDED.bundle_byte_size, skills_registry.bundle_byte_size),
4506
+ published_by_user_id = EXCLUDED.published_by_user_id,
4507
+ revision_id = EXCLUDED.revision_id,
4508
+ -- Current + 1, not EXCLUDED.revision_number: the insert path minted 1, but on
4509
+ -- the update path the ACTUAL row's counter is the truth (it may have advanced
4510
+ -- since the pre-read, which is exactly what the WHERE guard below detects).
4511
+ revision_number = skills_registry.revision_number + 1,
4512
+ tombstoned_at = NULL,
4513
+ tombstone_purge_after = NULL,
4514
+ updated_at = EXCLUDED.updated_at
4515
+ WHERE (skills_registry.tombstoned_at IS NOT NULL
4516
+ OR skills_registry.revision_id = ${input.expectedRevisionId ?? NO_REVISION_SENTINEL2})
4517
+ AND (NOT ${Boolean(input.seedBundledOnly)} OR
4518
+ (skills_registry.tombstoned_at IS NULL AND skills_registry.source = 'bundled'
4519
+ AND skills_registry.revision_id = ${input.expectedRevisionId ?? NO_REVISION_SENTINEL2}))
4520
+ RETURNING *
4521
+ `;
4522
+ if (!rows[0]) {
4523
+ const current = await tx`SELECT revision_id FROM skills_registry WHERE org_id = ${orgId} AND slug = ${input.slug} LIMIT 1`;
4524
+ const currentId = current[0] && typeof current[0].revision_id === "string" ? String(current[0].revision_id) : null;
4525
+ throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, currentId);
4526
+ }
4527
+ if (input.version && carriedSha) {
4528
+ await tx`
4529
+ INSERT INTO skills_versions (org_id, slug, version, bundle_sha256, bundle_byte_size, storage_kind, storage_key, manifest_json, published_by_user_id)
4530
+ VALUES (${orgId}, ${input.slug}, ${input.version}, ${carriedSha}, ${carriedSize ?? 0}, ${input.versionStorage?.storageKind ?? "db"},
4531
+ ${input.versionStorage?.storageKey ?? null}, ${JSON.stringify(input.versionManifest ?? {})}::jsonb, ${input.principal.userId})
4532
+ ON CONFLICT (org_id, slug, version) DO NOTHING
4533
+ `;
4534
+ }
4535
+ if (previousSha && input.bundle && previousSha !== input.bundle.sha256) {
4536
+ await tx`
4537
+ DELETE FROM skills_bundles
4538
+ WHERE org_id = ${orgId} AND sha256 = ${previousSha}
4539
+ AND NOT EXISTS (SELECT 1 FROM skills_registry WHERE org_id = ${orgId} AND bundle_sha256 = ${previousSha})
4540
+ AND NOT EXISTS (SELECT 1 FROM skills_versions WHERE org_id = ${orgId} AND bundle_sha256 = ${previousSha})
4541
+ `;
4542
+ }
4543
+ await tx`DELETE FROM skills_tags WHERE org_id = ${orgId} AND slug = ${input.slug}`;
4544
+ for (const tag of input.tags) {
4545
+ if (!tag.trim())
4546
+ continue;
4547
+ await tx`
4548
+ INSERT INTO skills_tags (org_id, slug, tag) VALUES (${orgId}, ${input.slug}, ${tag})
4549
+ ON CONFLICT DO NOTHING
4550
+ `;
4551
+ }
4552
+ return rowToSkill(rows[0]);
4553
+ });
4554
+ }
4555
+ async listSkills(principal) {
4556
+ await this.purgeExpiredTombstones(principal);
4557
+ const rows = await this.sql`
4558
+ SELECT * FROM skills_registry WHERE org_id = ${principal.orgId} AND tombstoned_at IS NULL ORDER BY slug ASC
4559
+ `;
4560
+ return rows.map(rowToSkill);
4561
+ }
4562
+ async getSkill(principal, slug) {
4563
+ const rows = await this.sql`SELECT * FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${slug} LIMIT 1`;
4564
+ return rows[0] ? rowToSkill(rows[0]) : null;
4565
+ }
4566
+ async setSkillLifecycle(principal, slug, patch, expectedRevisionId) {
4567
+ return this.sql.begin(async (tx) => {
4568
+ const currentRows = await tx`SELECT * FROM skills_registry WHERE org_id=${principal.orgId} AND slug=${slug} LIMIT 1 FOR UPDATE`;
4569
+ const current = currentRows[0] ? rowToSkill(currentRows[0]) : null;
4570
+ if (!current || current.tombstonedAt)
4571
+ return null;
4572
+ if (expectedRevisionId !== current.revisionId)
4573
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
4574
+ if (patch.lifecycle === "archived" && current.lifecycle !== "archived") {
4575
+ const profiles = await tx`SELECT profile_id,selections_json FROM skills_profiles WHERE org_id=${principal.orgId}`;
4576
+ const referencing = profiles.filter((profile2) => {
4577
+ const selections2 = typeof profile2.selections_json === "string" ? JSON.parse(profile2.selections_json) : profile2.selections_json;
4578
+ return Array.isArray(selections2) && selections2.some((selection) => selection?.slug === slug);
4579
+ }).map((profile2) => String(profile2.profile_id)).sort();
4580
+ if (referencing.length)
4581
+ throw new SkillLifecycleConflictError(slug, referencing);
4582
+ }
4583
+ const next2 = {
4584
+ ...current,
4585
+ lifecycle: patch.lifecycle,
4586
+ updatedAt: nowIso(),
4587
+ revisionId: revisionIdOfRecord({ ...current, lifecycle: patch.lifecycle, archiveReason: patch.reason, replacementSlug: patch.replacementSlug }),
4588
+ revisionNumber: current.revisionNumber + 1,
4589
+ ...patch.lifecycle === "archived" ? { archivedAt: current.archivedAt ?? nowIso(), ...patch.reason ? { archiveReason: patch.reason } : {}, ...patch.replacementSlug ? { replacementSlug: patch.replacementSlug } : {} } : {}
4590
+ };
4591
+ if (patch.lifecycle === "active") {
4592
+ delete next2.archivedAt;
4593
+ delete next2.archiveReason;
4594
+ delete next2.replacementSlug;
4595
+ }
4596
+ const rows = await tx`UPDATE skills_registry SET lifecycle=${next2.lifecycle}, archived_at=${next2.archivedAt ?? null}, archive_reason=${next2.archiveReason ?? null}, replacement_slug=${next2.replacementSlug ?? null}, revision_id=${next2.revisionId}, revision_number=revision_number+1, updated_at=${next2.updatedAt} WHERE org_id=${principal.orgId} AND slug=${slug} AND tombstoned_at IS NULL AND revision_id=${current.revisionId} RETURNING *`;
4597
+ if (!rows[0])
4598
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, (await this.getSkill(principal, slug))?.revisionId ?? null);
4599
+ return rowToSkill(rows[0]);
4600
+ });
4601
+ }
4602
+ async updateSkill(principal, slug, patch, expectedRevisionId) {
4603
+ const current = await this.getSkill(principal, slug);
4604
+ if (!current || current.tombstonedAt)
4605
+ return null;
4606
+ if (expectedRevisionId !== current.revisionId) {
4607
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
4608
+ }
4609
+ const next2 = { ...current, ...patch };
4610
+ return await this.sql.begin(async (tx) => {
4611
+ const revisionId = revisionIdOfRecord(next2);
4612
+ const updated = await tx`
4613
+ UPDATE skills_registry
4614
+ SET display_name = ${next2.displayName}, description = ${next2.description}, category = ${next2.category},
4615
+ tags_json = ${JSON.stringify(next2.tags)}::jsonb, kind = ${next2.kind}, version = ${next2.version ?? null},
4616
+ skill_md = ${next2.skillMd ?? null}, revision_id = ${revisionId}, revision_number = revision_number + 1, updated_at = now()
4617
+ WHERE org_id = ${principal.orgId} AND slug = ${slug} AND tombstoned_at IS NULL AND revision_id = ${current.revisionId}
4618
+ RETURNING *
4619
+ `;
4620
+ if (!updated[0]) {
4621
+ const nowRows = await tx`
4622
+ SELECT revision_id, tombstoned_at FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${slug} LIMIT 1
4623
+ `;
4624
+ if (nowRows[0] && nowRows[0].tombstoned_at == null) {
4625
+ const currentId = String(nowRows[0].revision_id);
4626
+ throw new SkillRevisionConflictError(slug, expectedRevisionId, currentId);
4627
+ }
4628
+ return null;
4629
+ }
4630
+ await tx`DELETE FROM skills_tags WHERE org_id = ${principal.orgId} AND slug = ${slug}`;
4631
+ for (const tag of next2.tags) {
4632
+ if (!tag.trim())
4633
+ continue;
4634
+ await tx`
4635
+ INSERT INTO skills_tags (org_id, slug, tag) VALUES (${principal.orgId}, ${slug}, ${tag})
4636
+ ON CONFLICT DO NOTHING
4637
+ `;
4638
+ }
4639
+ return rowToSkill(updated[0]);
4640
+ });
4641
+ }
4642
+ async deleteSkill(principal, slug, tombstoneWindowMs) {
4643
+ return await this.sql.begin(async (tx) => {
4644
+ const existingRows = await tx`
4645
+ SELECT tombstoned_at FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${slug} LIMIT 1
4646
+ `;
4647
+ if (!existingRows[0])
4648
+ return null;
4649
+ if (existingRows[0].tombstoned_at != null) {
4650
+ const rows2 = await tx`SELECT * FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${slug} LIMIT 1`;
4651
+ return rowToSkill(rows2[0]);
4652
+ }
4653
+ const rows = await tx`
4654
+ UPDATE skills_registry
4655
+ SET tombstoned_at = now(), tombstone_purge_after = now() + (${tombstoneWindowMs}::int * interval '1 millisecond'), updated_at = now()
4656
+ WHERE org_id = ${principal.orgId} AND slug = ${slug}
4657
+ RETURNING *
4658
+ `;
4659
+ return rows[0] ? rowToSkill(rows[0]) : null;
4660
+ });
4661
+ }
4662
+ async purgeExpiredTombstones(principal) {
4663
+ return await this.sql.begin(async (tx) => {
4664
+ const expiredRows = await tx`
4665
+ SELECT * FROM skills_registry
4666
+ WHERE org_id = ${principal.orgId} AND tombstoned_at IS NOT NULL AND tombstone_purge_after <= now()
4667
+ `;
4668
+ if (!expiredRows.length)
4669
+ return [];
4670
+ const purged = [];
4671
+ for (const row of expiredRows) {
4672
+ const record = rowToSkill(row);
4673
+ await tx`
4674
+ DELETE FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${record.slug} AND tombstone_purge_after <= now()
4675
+ `;
4676
+ await tx`DELETE FROM skills_tags WHERE org_id = ${principal.orgId} AND slug = ${record.slug}`;
4677
+ await tx`
4678
+ DELETE FROM skills_registry WHERE org_id = ${principal.orgId} AND slug = ${record.slug} AND tombstone_purge_after <= now()
4679
+ `;
4680
+ if (record.bundleSha256) {
4681
+ await tx`
4682
+ DELETE FROM skills_bundles
4683
+ WHERE org_id = ${principal.orgId} AND sha256 = ${record.bundleSha256}
4684
+ AND NOT EXISTS (SELECT 1 FROM skills_registry WHERE org_id = ${principal.orgId} AND bundle_sha256 = ${record.bundleSha256})
4685
+ AND NOT EXISTS (SELECT 1 FROM skills_versions WHERE org_id = ${principal.orgId} AND bundle_sha256 = ${record.bundleSha256})
4686
+ `;
4687
+ }
4688
+ purged.push(record);
4689
+ }
4690
+ return purged;
4691
+ });
4692
+ }
4693
+ async getSkillBundle(principal, sha256) {
4694
+ const rows = await this.sql`SELECT * FROM skills_bundles WHERE org_id = ${principal.orgId} AND sha256 = ${sha256} LIMIT 1`;
4695
+ return rows[0] ? rowToSkillBundle(rows[0]) : null;
4696
+ }
4697
+ async listSkillVersions(principal, slug) {
4698
+ const rows = await this.sql`
4699
+ SELECT * FROM skills_versions WHERE org_id = ${principal.orgId} AND slug = ${slug}
4700
+ ORDER BY created_at DESC, version DESC
4701
+ `;
4702
+ return rows.map((row) => rowToSkillVersion(row));
4703
+ }
4704
+ async getSkillVersion(principal, slug, version) {
4705
+ const rows = await this.sql`
4706
+ SELECT * FROM skills_versions WHERE org_id = ${principal.orgId} AND slug = ${slug} AND version = ${version} LIMIT 1
4707
+ `;
4708
+ return rows[0] ? rowToSkillVersion(rows[0]) : null;
4709
+ }
4710
+ async pinSkill(principal, slug, metadata = {}) {
4711
+ const rows = await this.sql`
4712
+ INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
4713
+ VALUES (${principal.orgId}, ${principal.apiKeyId}, ${slug}, now(), ${JSON.stringify(metadata)}::jsonb)
4714
+ ON CONFLICT (org_id, principal, slug) DO UPDATE SET
4715
+ pinned_at = now(),
4716
+ metadata_json = EXCLUDED.metadata_json
4717
+ RETURNING *
4718
+ `;
4719
+ return rowToPin(rows[0]);
4720
+ }
4721
+ async unpinSkill(principal, slug) {
4722
+ const rows = await this.sql`
4723
+ DELETE FROM skills_pins WHERE org_id = ${principal.orgId} AND principal = ${principal.apiKeyId} AND slug = ${slug}
4724
+ RETURNING 1 AS present
4725
+ `;
4726
+ return rows.length > 0;
4727
+ }
4728
+ async listPins(principal) {
4729
+ const rows = await this.sql`
4730
+ SELECT * FROM skills_pins WHERE org_id = ${principal.orgId} AND principal = ${principal.apiKeyId} ORDER BY slug ASC
4731
+ `;
4732
+ return rows.map(rowToPin);
4733
+ }
4734
+ async listTags(principal) {
4735
+ await this.purgeExpiredTombstones(principal);
4736
+ const rows = await this.sql`
4737
+ SELECT DISTINCT t.tag FROM skills_tags t JOIN skills_registry s ON s.org_id = t.org_id AND s.slug = t.slug WHERE t.org_id = ${principal.orgId} AND s.lifecycle = 'active' ORDER BY t.tag ASC
4738
+ `;
4739
+ return rows.map((row) => String(row.tag));
4740
+ }
4741
+ async listSkillsByTag(principal, tag) {
4742
+ await this.purgeExpiredTombstones(principal);
4743
+ const rows = await this.sql`
4744
+ SELECT s.* FROM skills_registry s
4745
+ JOIN skills_tags t ON t.org_id = s.org_id AND t.slug = s.slug
4746
+ WHERE t.org_id = ${principal.orgId} AND t.tag = ${tag} AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
4747
+ ORDER BY s.slug ASC
4748
+ `;
4749
+ return rows.map(rowToSkill);
4750
+ }
4751
+ async listPinsByTag(principal, tag) {
4752
+ await this.purgeExpiredTombstones(principal);
4753
+ const rows = await this.sql`
4754
+ SELECT p.* FROM skills_pins p
4755
+ JOIN skills_tags t ON t.org_id = p.org_id AND t.slug = p.slug
4756
+ JOIN skills_registry s ON s.org_id = p.org_id AND s.slug = p.slug
4757
+ WHERE p.org_id = ${principal.orgId} AND p.principal = ${principal.apiKeyId}
4758
+ AND t.tag = ${tag} AND s.tombstoned_at IS NULL AND s.lifecycle = 'active'
4759
+ ORDER BY p.slug ASC
4760
+ `;
4761
+ return rows.map(rowToPin);
4762
+ }
4763
+ async listPublishedSlugs(principal) {
4764
+ const rows = await this.sql`
4765
+ SELECT slug FROM skills_registry WHERE org_id = ${principal.orgId} AND tombstoned_at IS NULL ORDER BY slug ASC
4766
+ `;
4767
+ return rows.map((row) => String(row.slug));
4768
+ }
4769
+ async collectOrphanBundle(orgId, sha256) {
4770
+ await this.sql`
4771
+ DELETE FROM skills_bundles
4772
+ WHERE org_id = ${orgId} AND sha256 = ${sha256}
4773
+ AND NOT EXISTS (SELECT 1 FROM skills_registry WHERE org_id = ${orgId} AND bundle_sha256 = ${sha256})
4774
+ AND NOT EXISTS (SELECT 1 FROM skills_versions WHERE org_id = ${orgId} AND bundle_sha256 = ${sha256})
4775
+ `;
4776
+ }
4777
+ }
4778
+ var NO_REVISION_SENTINEL2 = "0000000000000000000000000000000000000000000000000000000000000000";
4779
+ function isUniqueViolation(error) {
4780
+ const code = error?.code;
4781
+ if (code === "23505" || code === 23505)
4782
+ return true;
4783
+ const message = error instanceof Error ? error.message : String(error);
4784
+ return /duplicate key value violates unique constraint/i.test(message);
4785
+ }
4786
+ function connectionFailureSummary(error) {
4787
+ const message = error instanceof Error ? error.message : String(error);
4788
+ return message.replace(/[a-z][a-z0-9+.-]*:\/\/\S*/gi, "<redacted-url>").split(`
4789
+ `)[0].slice(0, 200);
4790
+ }
4791
+
4792
+ // src/server/maintenance-entry.ts
4793
+ var MAX_MANIFEST_BYTES = 32 * 1024;
4794
+ function sha256(value) {
4795
+ return createHash3("sha256").update(value).digest("hex");
4796
+ }
4797
+ function canonical(value) {
4798
+ if (Array.isArray(value))
4799
+ return `[${value.map(canonical).join(",")}]`;
4800
+ if (value && typeof value === "object")
4801
+ return `{${Object.entries(value).filter(([key]) => key !== "manifestDigest").sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => `${JSON.stringify(key)}:${canonical(child)}`).join(",")}}`;
4802
+ return JSON.stringify(value);
4803
+ }
4804
+ function readBoundedJson(path) {
4805
+ const stat = lstatSync(path);
4806
+ if (!stat.isFile() || stat.size > MAX_MANIFEST_BYTES)
4807
+ throw new Error("maintenance input must be a bounded regular file");
4808
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
4809
+ try {
4810
+ const current = fstatSync(fd);
4811
+ if (!current.isFile() || current.size !== stat.size || current.size > MAX_MANIFEST_BYTES)
4812
+ throw new Error("maintenance input changed or is not bounded");
4813
+ const bytes = Buffer.alloc(current.size);
4814
+ let offset = 0;
4815
+ while (offset < bytes.length) {
4816
+ const count = readSync(fd, bytes, offset, bytes.length - offset, null);
4817
+ if (count === 0)
4818
+ throw new Error("maintenance input ended early");
4819
+ offset += count;
4820
+ }
4821
+ return { value: JSON.parse(bytes.toString("utf8")), bytesSha256: sha256(bytes.toString("utf8")) };
4822
+ } finally {
4823
+ closeSync(fd);
4824
+ }
4825
+ }
4826
+ function readManifest(path, receiptPath) {
4827
+ const manifestInput = readBoundedJson(path);
4828
+ const receiptInput = readBoundedJson(receiptPath);
4829
+ const parsed = manifestInput.value;
4830
+ const receipt = receiptInput.value;
4831
+ if (parsed.operation !== "enroll-publish")
4832
+ throw new Error("maintenance manifest operation must be enroll-publish");
4833
+ if (parsed.manifestVersion !== 1)
4834
+ throw new Error("unsupported maintenance manifest version");
4835
+ for (const field of ["keyId", "stationId", "orgId", "operationId", "manifestDigest", "expiresAt"]) {
4836
+ if (typeof parsed[field] !== "string" || !parsed[field].trim())
4837
+ throw new Error(`maintenance manifest is missing ${field}`);
4838
+ }
4839
+ if (!Array.isArray(parsed.expectedScopes) || parsed.expectedScopes.length === 0 || parsed.expectedScopes.length > 32 || parsed.expectedScopes.some((scope) => typeof scope !== "string" || !scope.trim() || scope.length > 128 || !/^[a-z][a-z0-9_-]*:[a-z][a-z0-9_-]*$/.test(scope))) {
4840
+ throw new Error("maintenance manifest expectedScopes must be a non-empty string array");
4841
+ }
4842
+ if (parsed.expectedScopes.includes("skills:publish"))
4843
+ throw new Error("maintenance manifest must describe current scopes without skills:publish");
4844
+ const expiry = Date.parse(parsed.expiresAt);
4845
+ if (!Number.isFinite(expiry) || expiry <= Date.now())
4846
+ throw new Error("maintenance manifest has an invalid or expired expiry");
4847
+ if (!/^[a-f0-9]{64}$/.test(parsed.manifestDigest))
4848
+ throw new Error("maintenance manifest digest must be SHA-256");
4849
+ if (sha256(canonical(parsed)) !== parsed.manifestDigest)
4850
+ throw new Error("maintenance manifest digest does not match its bytes");
4851
+ if (typeof receipt.operatorJobId !== "string" || !receipt.operatorJobId.trim() || typeof receipt.operatorTaskArn !== "string" || !receipt.operatorTaskArn.trim())
4852
+ throw new Error("verified operator receipt is incomplete");
4853
+ if (typeof receipt.manifestBytesSha256 !== "string" || !/^[a-f0-9]{64}$/.test(receipt.manifestBytesSha256))
4854
+ throw new Error("verified operator receipt digest is invalid");
4855
+ if (manifestInput.bytesSha256 !== receipt.manifestBytesSha256)
4856
+ throw new Error("manifest bytes are not the bytes verified by the operator wrapper");
4857
+ if (typeof receipt.taskDefinitionArn !== "string" || !receipt.taskDefinitionArn.trim() || typeof receipt.verifiedAt !== "string" || !Number.isFinite(Date.parse(receipt.verifiedAt)))
4858
+ throw new Error("verified operator receipt provenance is invalid");
4859
+ return { ...parsed, operatorJobId: receipt.operatorJobId, operatorTaskArn: receipt.operatorTaskArn };
4860
+ }
4861
+ function registerMaintenance(parent) {
4862
+ const maintenance = parent.command("maintenance").description("Run protected, metadata-only Skills maintenance operations");
4863
+ maintenance.command("enroll-publish").requiredOption("--manifest <path>", "Bounded target manifest supplied by the protected task wrapper").requiredOption("--operator-receipt <path>", "Verified actor receipt written by the protected task wrapper").option("--apply", "Commit the bounded scope update; defaults to dry-run", false).option("--json", "Output a safe JSON receipt", false).action(async (options) => {
4864
+ let manifest;
4865
+ let store;
4866
+ try {
4867
+ manifest = readManifest(options.manifest, options.operatorReceipt);
4868
+ if (!validOperatorScopeEnrollmentInput(manifest))
4869
+ throw new Error("maintenance manifest fields are invalid or unbounded");
4870
+ const databaseUrl = process.env.HASNA_SKILLS_DATABASE_URL;
4871
+ if (!databaseUrl)
4872
+ throw new Error("maintenance requires HASNA_SKILLS_DATABASE_URL");
4873
+ store = await createStore({ databaseUrl });
4874
+ const snapshot = await store.inspectOperatorScopeTarget?.(manifest.keyId, manifest.orgId);
4875
+ if (!snapshot)
4876
+ throw new Error("configured store does not support operator maintenance");
4877
+ if (snapshot.kind !== "found")
4878
+ throw new Error(`operator target ${snapshot.kind}`);
4879
+ if (!validOperatorScopeList(snapshot.scopes))
4880
+ throw new Error("store returned invalid scope metadata");
4881
+ const base = { status: "dry-run", operationId: manifest.operationId, keyId: manifest.keyId, stationId: manifest.stationId, orgId: manifest.orgId, expectedScopes: manifest.expectedScopes, currentScopes: snapshot.scopes, addScopes: ["skills:publish"] };
4882
+ if (!options.apply) {
4883
+ console.log(JSON.stringify(base));
4884
+ return;
4885
+ }
4886
+ const result = await store.enrollPublishScopeByOperator?.(manifest);
4887
+ if (!result)
4888
+ throw new Error("configured store does not support operator maintenance");
4889
+ if ("scopes" in result && !validOperatorScopeList(result.scopes))
4890
+ throw new Error("store returned invalid scope metadata");
4891
+ console.log(JSON.stringify({ ...base, status: result.kind, scopes: "scopes" in result ? result.scopes : undefined }));
4892
+ if (result.kind === "updated" || result.kind === "already_applied")
4893
+ return;
4894
+ process.exitCode = 1;
4895
+ } catch (error) {
4896
+ const message = error instanceof Error ? error.message : "";
4897
+ const code = message.includes("expired") || message.includes("expiry") ? "INVALID_EXPIRY" : message.includes("receipt") || message.includes("operator") ? "INVALID_OPERATOR_RECEIPT" : message.includes("manifest") || message.includes("input") ? "INVALID_MANIFEST" : message.includes("database") || message.includes("store") ? "STORE_UNAVAILABLE" : "MAINTENANCE_FAILED";
4898
+ if (options.json || !process.stdout.isTTY)
4899
+ console.log(JSON.stringify({ status: "failed", code }));
4900
+ else
4901
+ console.error(code);
4902
+ process.exitCode = 1;
4903
+ } finally {
4904
+ await store?.close?.();
4905
+ }
4906
+ });
4907
+ }
4908
+
4909
+ // src/server/maintenance.ts
4910
+ var program2 = new Command;
4911
+ program2.name("skills-maintenance");
4912
+ registerMaintenance(program2);
4913
+ await program2.parseAsync(process.argv);