@kaynooo/guard-tools 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -2
  3. package/dist/cli.js +2292 -0
  4. package/package.json +45 -4
package/dist/cli.js ADDED
@@ -0,0 +1,2292 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/cli.ts
5
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { dirname } from "path";
8
+ import process6 from "process";
9
+
10
+ // ../kit/src/args.ts
11
+ import process3 from "process";
12
+ import { parseArgs } from "util";
13
+
14
+ // ../kit/src/style.ts
15
+ import process2 from "process";
16
+
17
+ // ../../node_modules/.bun/@kaynooo+utils@2.31.0+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
18
+ function x(s) {
19
+ let r = s.startsWith("#") ? s.slice(1).toLowerCase() : s.toLowerCase();
20
+ if (!/^[a-f0-9]+$/.test(r))
21
+ return [0, 0, 0];
22
+ let t = r.length, n = (c) => Number.parseInt(c, 16);
23
+ if (t === 3 || t === 4) {
24
+ let c = n(r[0] + r[0]), u = n(r[1] + r[1]), h = n(r[2] + r[2]), g = t === 4 ? n(r[3] + r[3]) : undefined;
25
+ return g !== undefined ? [c, u, h, g] : [c, u, h];
26
+ }
27
+ if (t !== 6 && t !== 8)
28
+ throw Error(`Invalid hex color length: ${t}`);
29
+ let a = n(r.slice(0, 2)), b = n(r.slice(2, 4)), o = n(r.slice(4, 6)), y = t === 8 ? n(r.slice(6, 8)) : undefined;
30
+ if ([a, b, o].some(Number.isNaN) || y !== undefined && Number.isNaN(y))
31
+ throw Error(`Invalid hex color value: ${s}`);
32
+ return y !== undefined ? [a, b, o, y] : [a, b, o];
33
+ }
34
+ function A(s) {
35
+ let [r, t, n, a] = s;
36
+ if (a)
37
+ return `#${(r << 24 | t << 16 | n << 8 | Math.round((a ?? 1) * 255)).toString(16).padStart(8, "0").toUpperCase()}`;
38
+ return `#${(r << 16 | t << 8 | n).toString(16).padStart(6, "0").toUpperCase()}`;
39
+ }
40
+ function R(s, r, t) {
41
+ s = (s % 360 + 360) % 360, t /= 100;
42
+ let n = r * Math.min(t, 1 - t) / 100, a = (b) => {
43
+ let o = (b + s / 30) % 12, y = t - n * Math.max(Math.min(o - 3, 9 - o, 1), -1);
44
+ return Math.round(255 * y);
45
+ };
46
+ return [a(0), a(8), a(4)];
47
+ }
48
+ function k(s) {
49
+ let [r, t, n] = s;
50
+ r /= 255, t /= 255, n /= 255;
51
+ let a = Math.max(r, t, n), b = Math.min(r, t, n), o = 0, y = 0, c = (a + b) / 2;
52
+ if (a !== b) {
53
+ let u = a - b;
54
+ if (y = c > 0 && c < 1 ? u / (1 - Math.abs(2 * c - 1)) : 0, a === r)
55
+ o = (t - n) / u + (t < n ? 6 : 0);
56
+ else if (a === t)
57
+ o = (n - r) / u + 2;
58
+ else
59
+ o = (r - t) / u + 4;
60
+ o /= 6;
61
+ }
62
+ return [Math.round(o * 360), Math.round(y * 100), Math.round(c * 100)];
63
+ }
64
+ function H(s, r, t) {
65
+ s = (s % 360 + 360) % 360, r /= 100, t /= 100;
66
+ let n = (a) => {
67
+ let b = (a + s / 60) % 6, o = t - t * r * Math.max(Math.min(b, 4 - b, 1), 0);
68
+ return Math.round(255 * o);
69
+ };
70
+ return [n(5), n(3), n(1)];
71
+ }
72
+ function f(s) {
73
+ let [r, t, n] = s;
74
+ r /= 255, t /= 255, n /= 255;
75
+ let a = Math.max(r, t, n), b = Math.min(r, t, n), o = a - b, y = 0, c = a === 0 ? 0 : o / a, u = a;
76
+ if (o !== 0) {
77
+ if (a === r)
78
+ y = (t - n) / o + (t < n ? 6 : 0);
79
+ else if (a === t)
80
+ y = (n - r) / o + 2;
81
+ else
82
+ y = (r - t) / o + 4;
83
+ y /= 6;
84
+ }
85
+ return [Math.round(y * 360), Math.round(c * 100), Math.round(u * 100)];
86
+ }
87
+ function O(s) {
88
+ let r = s[0] / 255, t = s[1] / 255, n = s[2] / 255, a = 1 - Math.max(r, t, n);
89
+ if (a === 1)
90
+ return [0, 0, 0, 100];
91
+ let b = (1 - r - a) / (1 - a), o = (1 - t - a) / (1 - a), y = (1 - n - a) / (1 - a);
92
+ return [Math.round(b * 100), Math.round(o * 100), Math.round(y * 100), Math.round(a * 100)];
93
+ }
94
+ function v(s, r, t, n) {
95
+ return s /= 100, r /= 100, t /= 100, n /= 100, [Math.round(255 * (1 - s) * (1 - n)), Math.round(255 * (1 - r) * (1 - n)), Math.round(255 * (1 - t) * (1 - n))];
96
+ }
97
+ var m = (s) => s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
98
+ var p = (s) => s <= 0.0031308 ? 12.92 * s : 1.055 * s ** 0.4166666666666667 - 0.055;
99
+ var i = (s, r) => Math.round(s * 10 ** r) / 10 ** r;
100
+ function T(s) {
101
+ let r = m(s[0] / 255), t = m(s[1] / 255), n = m(s[2] / 255), a = Math.cbrt(0.4122214708 * r + 0.5363325363 * t + 0.0514459929 * n), b = Math.cbrt(0.2119034982 * r + 0.6806995451 * t + 0.1073969566 * n), o = Math.cbrt(0.0883024619 * r + 0.2817188376 * t + 0.6299787005 * n);
102
+ return [i(0.2104542553 * a + 0.793617785 * b - 0.0040720468 * o, 4), i(1.9779984951 * a - 2.428592205 * b + 0.4505937099 * o, 4), i(0.0259040371 * a + 0.7827717662 * b - 0.808675766 * o, 4)];
103
+ }
104
+ function M(s, r, t) {
105
+ let n = (s + 0.3963377774 * r + 0.2158037573 * t) ** 3, a = (s - 0.1055613458 * r - 0.0638541728 * t) ** 3, b = (s - 0.0894841775 * r - 1.291485548 * t) ** 3, o = p(4.0767416621 * n - 3.3077115913 * a + 0.2309699292 * b), y = p(-1.2684380046 * n + 2.6097574011 * a - 0.3413193965 * b), c = p(-0.0041960863 * n - 0.7034186147 * a + 1.707614701 * b), u = (h) => Math.min(255, Math.max(0, Math.round(h * 255)));
106
+ return [u(o), u(y), u(c)];
107
+ }
108
+ function d(s) {
109
+ let [r, t, n] = T(s), a = Math.sqrt(t * t + n * n), b = (Math.atan2(n, t) * 180 / Math.PI + 360) % 360;
110
+ return [i(r, 4), i(a, 4), i(b, 2)];
111
+ }
112
+ function C(s, r, t) {
113
+ let n = t * Math.PI / 180;
114
+ return M(s, r * Math.cos(n), r * Math.sin(n));
115
+ }
116
+ function l(s) {
117
+ let r = s / 255;
118
+ return r <= 0.03928 ? r / 12.92 : ((r + 0.055) / 1.055) ** 2.4;
119
+ }
120
+
121
+ class e {
122
+ r;
123
+ g;
124
+ b;
125
+ a;
126
+ constructor(s, r, t, n = 1) {
127
+ this.r = s, this.g = r, this.b = t, this.a = n;
128
+ }
129
+ static from(s, r) {
130
+ if (s === "hex") {
131
+ let [t, n, a, b] = x(r);
132
+ return new e(t, n, a, b);
133
+ }
134
+ if (s === "rgb") {
135
+ if (Array.isArray(r)) {
136
+ let [o, y, c, u] = r;
137
+ return new e(o, y, c, u);
138
+ }
139
+ let { r: t, g: n, b: a, a: b } = r;
140
+ return new e(t, n, a, b);
141
+ }
142
+ if (s === "hsl") {
143
+ let t, n, a, b;
144
+ if (Array.isArray(r))
145
+ [t, n, a, b] = r;
146
+ else
147
+ ({ h: t, s: n, l: a, a: b } = r);
148
+ let [o, y, c] = R(t, n, a);
149
+ return new e(o, y, c, b);
150
+ }
151
+ if (s === "hsv") {
152
+ let t, n, a, b;
153
+ if (Array.isArray(r))
154
+ [t, n, a, b] = r;
155
+ else
156
+ ({ h: t, s: n, v: a, a: b } = r);
157
+ let [o, y, c] = H(t, n, a);
158
+ return new e(o, y, c, b);
159
+ }
160
+ if (s === "cmyk") {
161
+ let t, n, a, b, o;
162
+ if (Array.isArray(r))
163
+ [t, n, a, b, o] = r;
164
+ else
165
+ ({ c: t, m: n, y: a, k: b, a: o } = r);
166
+ let [y, c, u] = v(t, n, a, b);
167
+ return new e(y, c, u, o);
168
+ }
169
+ if (s === "oklch") {
170
+ let t, n, a, b;
171
+ if (Array.isArray(r))
172
+ [t, n, a, b] = r;
173
+ else
174
+ ({ l: t, c: n, h: a, a: b } = r);
175
+ let [o, y, c] = C(t, n, a);
176
+ return new e(o, y, c, b);
177
+ }
178
+ if (s === "oklab") {
179
+ let t, n, a, b;
180
+ if (Array.isArray(r))
181
+ [t, n, a, b] = r;
182
+ else
183
+ ({ l: t, a: n, b: a, alpha: b } = r);
184
+ let [o, y, c] = M(t, n, a);
185
+ return new e(o, y, c, b);
186
+ }
187
+ throw Error("Invalid input");
188
+ }
189
+ to(s) {
190
+ switch (s) {
191
+ case "rgb":
192
+ return [this.r, this.g, this.b];
193
+ case "rgba":
194
+ return [this.r, this.g, this.b, this.a];
195
+ case "hex":
196
+ return A([this.r, this.g, this.b]);
197
+ case "hexa":
198
+ return A([this.r, this.g, this.b, this.a]);
199
+ case "hsl":
200
+ return k([this.r, this.g, this.b]);
201
+ case "hsla":
202
+ return [...k([this.r, this.g, this.b]), this.a];
203
+ case "hsv":
204
+ return f([this.r, this.g, this.b]);
205
+ case "hsva":
206
+ return [...f([this.r, this.g, this.b]), this.a];
207
+ case "cmyk":
208
+ return O([this.r, this.g, this.b]);
209
+ case "cmyka":
210
+ return [...O([this.r, this.g, this.b]), this.a];
211
+ case "oklch":
212
+ return d([this.r, this.g, this.b]);
213
+ case "oklcha":
214
+ return [...d([this.r, this.g, this.b]), this.a];
215
+ case "oklab":
216
+ return T([this.r, this.g, this.b]);
217
+ case "oklaba":
218
+ return [...T([this.r, this.g, this.b]), this.a];
219
+ default:
220
+ return [this.r, this.g, this.b];
221
+ }
222
+ }
223
+ getLuminance() {
224
+ let s = l(this.r), r = l(this.g), t = l(this.b);
225
+ return 0.2126 * s + 0.7152 * r + 0.0722 * t;
226
+ }
227
+ contrastRatio(s) {
228
+ let r = this.getLuminance(), t = s.getLuminance(), [n, a] = r >= t ? [r, t] : [t, r];
229
+ return (n + 0.05) / (a + 0.05);
230
+ }
231
+ bestContrastForeground() {
232
+ let s = new e(255, 255, 255, 1), r = new e(0, 0, 0, 1), t = this.contrastRatio(s), n = this.contrastRatio(r);
233
+ return t >= n ? s : r;
234
+ }
235
+ }
236
+
237
+ // ../../node_modules/.bun/@kaynooo+utils@2.31.0+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-y1msbssb.js
238
+ var r = 39;
239
+ var o = 49;
240
+ var f2 = "\x1B[";
241
+ var C2 = { reset: [0, 0], bold: [1, 22], dim: [2, 22], italic: [3, 23], underline: [4, 24], inverse: [7, 27], hidden: [8, 28], strikethrough: [9, 29], black: [30, r], red: [31, r], green: [32, r], yellow: [33, r], blue: [34, r], magenta: [35, r], cyan: [36, r], white: [37, r], gray: [90, r], grey: [90, r], blackBright: [90, r], redBright: [91, r], greenBright: [92, r], yellowBright: [93, r], blueBright: [94, r], magentaBright: [95, r], cyanBright: [96, r], whiteBright: [97, r], bgBlack: [40, o], bgRed: [41, o], bgGreen: [42, o], bgYellow: [43, o], bgBlue: [44, o], bgMagenta: [45, o], bgCyan: [46, o], bgWhite: [47, o], bgBlackBright: [100, o], bgRedBright: [101, o], bgGreenBright: [102, o], bgYellowBright: [103, o], bgBlueBright: [104, o], bgMagentaBright: [105, o], bgCyanBright: [106, o], bgWhiteBright: [107, o], rgb: (i, n, a, l = "fg") => [`${l === "fg" ? 38 : 48};2;${i};${n};${a}`, l === "fg" ? r : o], hex: (i, n = "fg") => {
242
+ let [a, l, s] = e.from("hex", i).to("rgb");
243
+ return [`${n === "fg" ? 38 : 48};2;${a};${l};${s}`, n === "fg" ? r : o];
244
+ }, ansi256: (i, n = "fg") => [`${n === "fg" ? 38 : 48};5;${i}`, n === "fg" ? r : o] };
245
+ var b = new Set(["", "1", "2", "3", "true"]);
246
+ function h(i, n = globalThis.process?.env ?? {}) {
247
+ if (n.FORCE_COLOR !== undefined)
248
+ return b.has(n.FORCE_COLOR);
249
+ if (n.NO_COLOR !== undefined || n.NODE_DISABLE_COLORS !== undefined || n.TERM === "dumb")
250
+ return false;
251
+ return i?.isTTY === true;
252
+ }
253
+ function d2(i, n, a) {
254
+ if (!h(a.stream ?? globalThis.process?.stdout, a.env))
255
+ return n;
256
+ let l = "", s = "";
257
+ for (let g = 0, y = i.length;g < y; g++) {
258
+ let [c, p] = i[g];
259
+ l += `${f2}${c}m`, s = `${f2}${p}m${s}`;
260
+ }
261
+ return l + n + s;
262
+ }
263
+ function u(i, n = []) {
264
+ return new Proxy((l) => d2(n, l, i), { get(l, s) {
265
+ let g = C2[s];
266
+ if (Array.isArray(g))
267
+ return u(i, [...n, g]);
268
+ if (typeof g === "function")
269
+ return (...y) => u(i, [...n, g(...y)]);
270
+ throw Error(`Unknown style: ${String(s)}`);
271
+ } });
272
+ }
273
+ function le(i = {}) {
274
+ return u(i);
275
+ }
276
+ var t = le();
277
+
278
+ // ../kit/src/style.ts
279
+ function takesControlCodes(stream, env = process2.env) {
280
+ return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
281
+ }
282
+ function paint(stream, env = process2.env) {
283
+ return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
284
+ }
285
+ var STATUSES = {
286
+ passed: { symbol: "\u2713", words: ["passed", "success", "succeeded", "ok"], paint: (colors) => colors.green },
287
+ failed: { symbol: "\u2717", words: ["failed", "failure", "error", "errored"], paint: (colors) => colors.red },
288
+ running: { symbol: "\u25CF", words: ["running", "in_progress", "started"], paint: (colors) => colors.cyan },
289
+ queued: { symbol: "\u25CB", words: ["queued", "waiting", "pending", "created", "blocked", "scheduled"], paint: (colors) => colors.yellow },
290
+ skipped: { symbol: "-", words: ["skipped", "cancelled", "canceled", "manual"], paint: (colors) => colors.dim }
291
+ };
292
+ var STATUS_OF = new Map(Object.entries(STATUSES).flatMap(([status, { words }]) => words.map((word) => [word, status])));
293
+
294
+ // ../kit/src/format.ts
295
+ function upTo(text, separator) {
296
+ const end = text.indexOf(separator);
297
+ return end === -1 ? text : text.slice(0, end);
298
+ }
299
+ var ESCAPE = String.fromCharCode(27);
300
+
301
+ // ../kit/src/args.ts
302
+ function jsonOption(holds) {
303
+ return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
304
+ }
305
+
306
+ class Exit extends Error {
307
+ code;
308
+ hint;
309
+ constructor(message, code, hint) {
310
+ super(message);
311
+ this.code = code;
312
+ this.hint = hint;
313
+ }
314
+ }
315
+ function fail(message, code = 2, hint) {
316
+ throw new Exit(message, code, hint);
317
+ }
318
+
319
+ class UsageError extends Exit {
320
+ constructor(message) {
321
+ super(message, 2);
322
+ }
323
+ }
324
+ function badUsage(message) {
325
+ throw new UsageError(message);
326
+ }
327
+
328
+ class HelpShown extends Exit {
329
+ constructor() {
330
+ super("help shown", 0);
331
+ }
332
+ }
333
+
334
+ class Completing {
335
+ options;
336
+ constructor(options) {
337
+ this.options = options;
338
+ }
339
+ }
340
+ var completion = { active: false };
341
+ var OPTIONAL = /\[[^\]]+\]/g;
342
+ var USAGE_PREFIX = /^usage:\s*/i;
343
+ function usageLine(usage, colors = paint(process3.stdout)) {
344
+ return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
345
+ }
346
+ function helpText(usage, options, colors = paint(process3.stdout), examples = []) {
347
+ return [usageLine(usage, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
348
+ `);
349
+ }
350
+ function exampleRows(examples, colors = paint(process3.stdout)) {
351
+ if (examples.length === 0)
352
+ return [];
353
+ return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
354
+ }
355
+ function optionRows(options, colors = paint(process3.stdout)) {
356
+ const rows = Object.entries(options).map(([name, option]) => {
357
+ const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
358
+ const value = option.type === "string" ? ` ${colors.dim("<value>")}` : "";
359
+ return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description };
360
+ });
361
+ const width = Math.max(0, ...rows.map((row) => row.width));
362
+ return rows.map((row) => ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
363
+ }
364
+ var OUTPUT_OPTIONS = {
365
+ color: { type: "string", description: "auto (on a terminal alone), always or never" },
366
+ plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
367
+ };
368
+ function applyOutput(color, plain, env = process3.env) {
369
+ const when = plain === true ? "never" : color ?? "auto";
370
+ if (when === "auto")
371
+ return;
372
+ if (when !== "always" && when !== "never")
373
+ badUsage(`--color takes auto, always or never, not ${when}`);
374
+ if (when === "always") {
375
+ env.FORCE_COLOR = "1";
376
+ delete env.NO_COLOR;
377
+ } else {
378
+ env.NO_COLOR = "1";
379
+ delete env.FORCE_COLOR;
380
+ }
381
+ }
382
+ function parse(args, usage, options, settings = {}) {
383
+ const output = Object.fromEntries(Object.entries(OUTPUT_OPTIONS).filter(([name]) => !(name in options)));
384
+ const all = { ...options, ...output, help: { type: "boolean", short: "h", description: "show this help" } };
385
+ if (completion.active)
386
+ throw new Completing(all);
387
+ const separator = settings.rest === true ? args.indexOf("--") : -1;
388
+ const own = separator === -1 ? args : args.slice(0, separator);
389
+ const rest = separator === -1 ? undefined : args.slice(separator + 1);
390
+ let parsed;
391
+ try {
392
+ parsed = parseArgs({ args: own, options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
393
+ } catch (error) {
394
+ badUsage(parseError(error));
395
+ }
396
+ const values = parsed.values;
397
+ applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
398
+ if (values.help === true) {
399
+ console.log(helpText(usage, all, undefined, settings.examples));
400
+ throw new HelpShown;
401
+ }
402
+ if (settings.positionals !== undefined)
403
+ checkPositionals(parsed.positionals, settings.positionals);
404
+ return { ...parsed, positionals: parsed.positionals, rest };
405
+ }
406
+ function checkPositionals(given, names) {
407
+ const variadic = names.at(-1)?.replace("]", "").endsWith("...") === true;
408
+ const required = names.filter((name) => !name.startsWith("[")).length;
409
+ if (given.length < required)
410
+ badUsage(`missing ${names[given.length]}`);
411
+ if (!variadic && given.length > names.length)
412
+ badUsage(`unexpected argument ${given[names.length]}`);
413
+ }
414
+ function parseError(error) {
415
+ const message = upTo(error instanceof Error ? error.message : String(error), ". ");
416
+ return `${message.charAt(0).toLowerCase()}${message.slice(1)}`;
417
+ }
418
+ async function readInput(path, { noStdin = "nothing is piped on stdin" } = {}) {
419
+ if (path === "-") {
420
+ if (process3.stdin.isTTY)
421
+ fail(noStdin);
422
+ return Bun.stdin.text();
423
+ }
424
+ const file = Bun.file(path);
425
+ if (!await file.exists())
426
+ fail(`no file ${path}`);
427
+ return file.text();
428
+ }
429
+ // ../kit/src/cli.ts
430
+ import process4 from "process";
431
+
432
+ // ../kit/src/text.ts
433
+ var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
434
+ function distance(a, b) {
435
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
436
+ for (const [i, left] of [...a].entries()) {
437
+ const current = [i + 1];
438
+ for (const [j, right] of [...b].entries())
439
+ current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
440
+ previous = current;
441
+ }
442
+ return previous[b.length] ?? 0;
443
+ }
444
+
445
+ // ../kit/src/cli.ts
446
+ var LEVEL_OPTIONS = {
447
+ ...OUTPUT_OPTIONS,
448
+ help: { type: "boolean", short: "h", description: "show this help" }
449
+ };
450
+ var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
451
+ function commandRows(commands, colors) {
452
+ const width = Math.max(...Object.keys(commands).map((name) => name.length));
453
+ const groups = new Map([["Commands:", []]]);
454
+ for (const [name, { summary, group }] of Object.entries(commands)) {
455
+ const heading = group === undefined ? "Commands:" : `${group}:`;
456
+ const rows = groups.get(heading) ?? [];
457
+ rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(summary)}`);
458
+ groups.set(heading, rows);
459
+ }
460
+ return [...groups].filter(([, rows]) => rows.length > 0).flatMap(([heading, rows], index) => [...index === 0 ? [] : [""], colors.bold(heading), ...rows]);
461
+ }
462
+ function takeOutputFlags(argv) {
463
+ let index = 0;
464
+ for (;index < argv.length; index++) {
465
+ const word = argv[index];
466
+ if (word === "--plain")
467
+ applyOutput(undefined, true);
468
+ else if (word.startsWith("--color="))
469
+ applyOutput(word.slice("--color=".length), undefined);
470
+ else if (word === "--color" && argv[index + 1] !== undefined)
471
+ applyOutput(argv[++index], undefined);
472
+ else
473
+ break;
474
+ }
475
+ return argv.slice(index);
476
+ }
477
+ function closest(name, names) {
478
+ const started = names.find((known) => known.startsWith(name));
479
+ if (started !== undefined)
480
+ return started;
481
+ const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
482
+ return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
483
+ }
484
+ async function help({ path, header, commands, defaultCommand, examples }, stream) {
485
+ const colors = paint(stream);
486
+ const prefix = path.join(" ");
487
+ const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
488
+ const options = fallback !== undefined && "run" in fallback ? await optionsOf(fallback.run) : undefined;
489
+ const own = Object.entries(options ?? {}).filter(([name]) => !(name in TOP_OPTIONS));
490
+ return [
491
+ header(colors),
492
+ "",
493
+ usageLine(`${prefix} ${defaultCommand === undefined ? colors.cyan("<command>") : `[${colors.cyan("<command>")}]`} [options]`, colors),
494
+ "",
495
+ ...commandRows(commands, colors),
496
+ ...own.length === 0 ? [] : ["", `${colors.bold(`Options of ${defaultCommand}:`)} ${colors.dim("the command run when none is named")}`, ...optionRows(Object.fromEntries(own), colors)],
497
+ "",
498
+ colors.bold("Options:"),
499
+ ...optionRows(path.length === 1 ? TOP_OPTIONS : LEVEL_OPTIONS, colors),
500
+ ...exampleRows(examples ?? [], colors),
501
+ "",
502
+ `${colors.dim("Run")} ${prefix} ${colors.cyan("<command>")} --help ${colors.dim("for its options.")}`
503
+ ].join(`
504
+ `);
505
+ }
506
+ function printError(prefix, message, hint, cause) {
507
+ const colors = paint(process4.stderr);
508
+ const styled = takesControlCodes(process4.stderr);
509
+ const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
510
+ const under = (label, text, tint) => text === undefined ? "" : `
511
+ ${styled ? tint(label) : `${label}:`} ${text}`;
512
+ console.error(`${head} ${message}${under("cause", cause, colors.dim)}${under("hint", hint, colors.yellow)}`);
513
+ }
514
+ function reportThrown(prefix, error, redact = (text) => text) {
515
+ if (error instanceof HelpShown)
516
+ return 0;
517
+ const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
518
+ const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
519
+ const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
520
+ printError(prefix, redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
521
+ return error instanceof Exit ? error.code : 1;
522
+ }
523
+ async function dispatch(level, typed, { version, redact }) {
524
+ const prefix = level.path.join(" ");
525
+ let argv;
526
+ try {
527
+ argv = takeOutputFlags(typed);
528
+ } catch (error) {
529
+ return reportThrown(prefix, error);
530
+ }
531
+ const [name, ...args] = argv;
532
+ if (name === undefined && level.path.length > 1) {
533
+ console.error(await help(level, process4.stderr));
534
+ return 2;
535
+ }
536
+ if (name === undefined || name === "help" || name === "--help" || name === "-h") {
537
+ console.log(await help(level, process4.stdout));
538
+ return 0;
539
+ }
540
+ if (level.path.length === 1 && (name === "--version" || name === "-v")) {
541
+ console.log(version);
542
+ return 0;
543
+ }
544
+ let command = level.commands[level.aliases?.[name] ?? name];
545
+ let rest = args;
546
+ if (command === undefined && level.defaultCommand !== undefined) {
547
+ command = level.commands[level.defaultCommand];
548
+ rest = argv;
549
+ }
550
+ if (command === undefined) {
551
+ const meant = closest(name, Object.keys(level.commands));
552
+ printError(prefix, `no command ${name}`, meant === undefined ? `run ${prefix} --help for its commands` : `did you mean ${meant}?`);
553
+ return 2;
554
+ }
555
+ if ("commands" in command) {
556
+ const { summary } = command;
557
+ return dispatch({ path: [...level.path, name], header: (colors) => `${colors.bold(`${prefix} ${name}`)} ${summary}`, commands: command.commands, aliases: command.aliases, defaultCommand: undefined, examples: command.examples }, rest, { version, redact });
558
+ }
559
+ try {
560
+ return await command.run(rest);
561
+ } catch (error) {
562
+ return reportThrown(`${prefix} ${name}`, error, redact);
563
+ }
564
+ }
565
+ var COMPLETE = "__complete";
566
+ var FILES = "__files__";
567
+ async function optionsOf(run) {
568
+ completion.active = true;
569
+ try {
570
+ await run([]);
571
+ } catch (error) {
572
+ if (error instanceof Completing)
573
+ return error.options;
574
+ } finally {
575
+ completion.active = false;
576
+ }
577
+ return {};
578
+ }
579
+ async function leafCandidates(run, words) {
580
+ if (!(words.at(-1) ?? "").startsWith("-"))
581
+ return [FILES];
582
+ return Object.entries(await optionsOf(run)).map(([name, option]) => `--${name} ${option.description}`);
583
+ }
584
+ async function candidates(commands, aliases, defaultCommand, words) {
585
+ const [word = "", ...rest] = words;
586
+ const command = commands[aliases?.[word] ?? word];
587
+ if (rest.length === 0 && !word.startsWith("-"))
588
+ return Object.entries(commands).map(([name, { summary }]) => `${name} ${summary}`);
589
+ if (command === undefined) {
590
+ const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
591
+ return fallback !== undefined && "run" in fallback ? leafCandidates(fallback.run, words) : [];
592
+ }
593
+ return "commands" in command ? candidates(command.commands, command.aliases, undefined, rest) : leafCandidates(command.run, rest);
594
+ }
595
+ async function printCandidates(found) {
596
+ for (const line of await found)
597
+ console.log(line);
598
+ return 0;
599
+ }
600
+ async function runCli(cli, argv = process4.argv.slice(2)) {
601
+ if (argv[0] === COMPLETE)
602
+ return printCandidates(candidates(cli.commands, cli.aliases, cli.defaultCommand, argv.slice(1)));
603
+ return dispatch({ path: [cli.name], header: (colors) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${cli.about}`, commands: cli.commands, aliases: cli.aliases, defaultCommand: cli.defaultCommand, examples: cli.examples }, argv, { version: cli.version, redact: cli.redact });
604
+ }
605
+ // ../kit/src/process.ts
606
+ import { constants } from "os";
607
+ async function collect(stream, name, chunks) {
608
+ for await (const data of stream)
609
+ chunks.push({ stream: name, data });
610
+ }
611
+ function exitCodeOf(code, signal) {
612
+ if (code !== null)
613
+ return code;
614
+ if (typeof signal === "number")
615
+ return 128 + signal;
616
+ return signal === null ? 129 : 128 + constants.signals[signal];
617
+ }
618
+ async function runCommand(argv, { cwd, env, stdin = "ignore" } = {}) {
619
+ const chunks = [];
620
+ const proc = Bun.spawn(argv, { ...cwd === undefined ? {} : { cwd }, ...env === undefined ? {} : { env }, stdin, stdout: "pipe", stderr: "pipe" });
621
+ await Promise.all([collect(proc.stdout, "stdout", chunks), collect(proc.stderr, "stderr", chunks)]);
622
+ await proc.exited;
623
+ const output = Buffer.concat(chunks.map(({ data }) => data));
624
+ return { code: exitCodeOf(proc.exitCode, proc.signalCode), chunks, output, text: output.toString() };
625
+ }
626
+ // package.json
627
+ var version = "0.3.0";
628
+
629
+ // ../kit/src/shell.ts
630
+ var SEPARATORS = new Set([`
631
+ `, ";", "|"]);
632
+ var PLAIN_BREAKERS = new Set(["<", ">", "(", ")", "&", "|"]);
633
+ var WHITESPACE = /\s/;
634
+ var ASSIGNMENT = /^\w+=/;
635
+ var TRAILING_SLASHES = /\/+$/;
636
+ var LEADING_TILDE = /^~(?=\/|$)/;
637
+ var LEADING_TABS = /^\t+/;
638
+ var OPENERS = /^[({]+/;
639
+ var CLOSERS = /[)}]+$/;
640
+ var DOUBLE_QUOTED_ESCAPES = new Set(["$", "`", '"', "\\"]);
641
+ var HEREDOC = /^<<(-?)[ \t]*(?:'([^']*)'|"([^"]*)"|([^\s;&|<>()-][^\s;&|<>()]*))/;
642
+ var WRAPPERS = new Set(["command", "env", "sudo", "exec", "nohup", "time"]);
643
+ function separatorWidth(command, i) {
644
+ const pair = command.slice(i, i + 2);
645
+ if (pair === "&&" || pair === "||")
646
+ return 2;
647
+ const char = command[i] ?? "";
648
+ if (char === "&")
649
+ return command[i - 1] === ">" || command[i - 1] === "<" || command[i + 1] === ">" ? 0 : 1;
650
+ return SEPARATORS.has(char) ? 1 : 0;
651
+ }
652
+ function heredocEnd(command, newline, words) {
653
+ let lineStart = newline + 1;
654
+ for (const { word, stripsTabs } of words) {
655
+ while (lineStart <= command.length) {
656
+ const next = command.indexOf(`
657
+ `, lineStart);
658
+ const lineEnd = next === -1 ? command.length : next;
659
+ const line = command.slice(lineStart, lineEnd);
660
+ lineStart = lineEnd + 1;
661
+ if ((stripsTabs ? line.replace(LEADING_TABS, "") : line) === word)
662
+ break;
663
+ }
664
+ }
665
+ return Math.min(lineStart - 1, command.length);
666
+ }
667
+ function withoutRanges(command, start, end, ranges) {
668
+ let text = "";
669
+ let at = start;
670
+ for (const [from, to] of ranges) {
671
+ if (to <= start || from >= end)
672
+ continue;
673
+ text += command.slice(at, Math.max(at, from));
674
+ at = Math.max(at, to);
675
+ }
676
+ return text + command.slice(at, end);
677
+ }
678
+ function splitCommand(command) {
679
+ const bounds = [];
680
+ const bodies = [];
681
+ let heredocs = [];
682
+ let start = 0;
683
+ let quote = null;
684
+ for (let i = 0;i < command.length; i++) {
685
+ const char = command[i] ?? "";
686
+ if (char === "\\" && quote !== "'") {
687
+ i++;
688
+ continue;
689
+ }
690
+ if (quote !== null) {
691
+ if (char === quote)
692
+ quote = null;
693
+ continue;
694
+ }
695
+ if (char === "'" || char === '"') {
696
+ quote = char;
697
+ continue;
698
+ }
699
+ const heredoc = char === "<" && command[i + 1] === "<" && command[i + 2] !== "<" ? HEREDOC.exec(command.slice(i)) : null;
700
+ if (heredoc) {
701
+ heredocs.push({ word: heredoc[2] ?? heredoc[3] ?? heredoc[4] ?? "", stripsTabs: heredoc[1] === "-", isQuoted: heredoc[4] === undefined });
702
+ i += heredoc[0].length - 1;
703
+ continue;
704
+ }
705
+ if (char === `
706
+ ` && heredocs.length > 0) {
707
+ const end = heredocEnd(command, i, heredocs);
708
+ bodies.push([i, end, heredocs.some((each) => !each.isQuoted)]);
709
+ heredocs = [];
710
+ i = end - 1;
711
+ continue;
712
+ }
713
+ const width = separatorWidth(command, i);
714
+ if (width === 0)
715
+ continue;
716
+ bounds.push([start, i, command.slice(i, i + width)]);
717
+ i += width - 1;
718
+ start = i + 1;
719
+ }
720
+ bounds.push([start, command.length, ""]);
721
+ const ranges = bodies.map(([from, to]) => [from, to]);
722
+ return bounds.map(([from, to, separator]) => ({
723
+ text: command.slice(from, to),
724
+ start: from,
725
+ end: to,
726
+ command: withoutRanges(command, from, to, ranges),
727
+ separator,
728
+ expanded: bodies.filter(([body, , expands]) => expands && body >= from && body < to).map(([body, bodyEnd]) => command.slice(body, bodyEnd)).join("")
729
+ }));
730
+ }
731
+ function isPlain(text) {
732
+ let quote = null;
733
+ for (let i = 0;i < text.length; i++) {
734
+ const char = text[i] ?? "";
735
+ const isSubstitution = char === "`" || char === "$" && text[i + 1] === "(";
736
+ if (char === "\\" && quote !== "'")
737
+ i++;
738
+ else if (quote !== null && char === quote)
739
+ quote = null;
740
+ else if (quote === null && (char === '"' || char === "'"))
741
+ quote = char;
742
+ else if (quote !== "'" && (isSubstitution || quote === null && PLAIN_BREAKERS.has(char)))
743
+ return false;
744
+ }
745
+ return quote === null;
746
+ }
747
+ function closingParen(text, open) {
748
+ let depth = 0;
749
+ let quote = null;
750
+ for (let i = open;i < text.length; i++) {
751
+ const char = text[i] ?? "";
752
+ if (char === "\\" && quote !== "'") {
753
+ i++;
754
+ } else if (quote !== null) {
755
+ if (char === quote)
756
+ quote = null;
757
+ } else if (char === "'" || char === '"') {
758
+ quote = char;
759
+ } else if (char === "(") {
760
+ depth++;
761
+ } else if (char === ")" && --depth === 0) {
762
+ return i;
763
+ }
764
+ }
765
+ return text.length;
766
+ }
767
+ function closingBackquote(text, open) {
768
+ for (let i = open + 1;i < text.length; i++) {
769
+ if (text[i] === "\\")
770
+ i++;
771
+ else if (text[i] === "`")
772
+ return i;
773
+ }
774
+ return text.length;
775
+ }
776
+ function bodies(text, isQuoting) {
777
+ const found = [];
778
+ let quote = null;
779
+ for (let i = 0;i < text.length; i++) {
780
+ const char = text[i] ?? "";
781
+ if (quote === "'") {
782
+ if (char === "'")
783
+ quote = null;
784
+ } else if (char === "\\") {
785
+ i++;
786
+ } else if (char === "$" && text[i + 1] === "(") {
787
+ const end = closingParen(text, i + 1);
788
+ found.push(text.slice(i + 2, end));
789
+ i = end;
790
+ } else if (char === "`") {
791
+ const end = closingBackquote(text, i);
792
+ found.push(text.slice(i + 1, end));
793
+ i = end;
794
+ } else if (isQuoting && char === '"') {
795
+ quote = quote === null ? char : null;
796
+ } else if (isQuoting && char === "'" && quote === null) {
797
+ quote = char;
798
+ }
799
+ }
800
+ return found;
801
+ }
802
+ function substitutions(command) {
803
+ return splitCommand(command).flatMap((piece) => [...bodies(piece.command, true), ...bodies(piece.expanded, false)]);
804
+ }
805
+ function words(text) {
806
+ const found = [];
807
+ let current = "";
808
+ let isStarted = false;
809
+ let isProse = false;
810
+ let quote = null;
811
+ for (let i = 0;i < text.length; i++) {
812
+ const char = text[i] ?? "";
813
+ const isContinuation = char === "\\" && quote !== "'" && text[i + 1] === `
814
+ `;
815
+ if (isContinuation)
816
+ i++;
817
+ if (quote === null && (isContinuation || WHITESPACE.test(char))) {
818
+ if (isStarted)
819
+ found.push({ text: current, isProse });
820
+ current = "";
821
+ isStarted = false;
822
+ isProse = false;
823
+ continue;
824
+ }
825
+ if (isContinuation)
826
+ continue;
827
+ isStarted = true;
828
+ if (char === "\\" && quote === '"' && !DOUBLE_QUOTED_ESCAPES.has(text[i + 1] ?? ""))
829
+ current += char;
830
+ else if (char === "\\" && quote !== "'")
831
+ current += text[++i] ?? "";
832
+ else if (quote !== null && char === quote)
833
+ quote = null;
834
+ else if (quote === null && (char === '"' || char === "'"))
835
+ quote = char;
836
+ else
837
+ current += char;
838
+ isProse ||= quote !== null && WHITESPACE.test(char);
839
+ }
840
+ if (isStarted)
841
+ found.push({ text: current, isProse });
842
+ return found;
843
+ }
844
+ function tokenize(text) {
845
+ return words(text).map((word) => word.text);
846
+ }
847
+ function parseCommand(text) {
848
+ const tokens = tokenize(text);
849
+ const assignments = [];
850
+ let isWrapped = false;
851
+ for (;; ) {
852
+ const first = tokens[0] ?? "";
853
+ if (OPENERS.test(first)) {
854
+ isWrapped = true;
855
+ const rest = first.replace(OPENERS, "");
856
+ if (rest === "")
857
+ tokens.shift();
858
+ else
859
+ tokens[0] = rest;
860
+ } else if (ASSIGNMENT.test(first)) {
861
+ assignments.push(tokens.shift() ?? "");
862
+ } else if (WRAPPERS.has(first)) {
863
+ isWrapped = true;
864
+ tokens.shift();
865
+ while (tokens[0]?.startsWith("-"))
866
+ tokens.shift();
867
+ } else {
868
+ break;
869
+ }
870
+ }
871
+ const closers = CLOSERS.exec(text.trimEnd())?.[0].length ?? 0;
872
+ const word = tokens.at(-1) ?? "";
873
+ const trimmed = word.length - (CLOSERS.exec(word)?.[0].length ?? 0);
874
+ const last = word.slice(0, Math.max(trimmed, word.length - closers));
875
+ if (tokens.length > 0)
876
+ tokens.splice(-1, 1, ...last === "" ? [] : [last]);
877
+ const [program = "", ...args] = tokens;
878
+ return { program: program.split("/").pop() ?? "", args, assignments, isWrapped };
879
+ }
880
+ function replacePieces(command, replacements) {
881
+ let result = command;
882
+ for (const [piece, text] of [...replacements].sort(([a], [b]) => b.start - a.start)) {
883
+ result = result.slice(0, piece.start) + text + result.slice(piece.end);
884
+ }
885
+ return result;
886
+ }
887
+ function parentDir(path) {
888
+ const cut = path.replace(TRAILING_SLASHES, "").lastIndexOf("/");
889
+ return cut <= 0 ? "/" : path.slice(0, cut);
890
+ }
891
+ function resolvePath(base, arg, home) {
892
+ const expanded = arg.replace(LEADING_TILDE, home);
893
+ const parts = [];
894
+ for (const part of (expanded.startsWith("/") ? expanded : `${base}/${expanded}`).split("/")) {
895
+ if (part === "..")
896
+ parts.pop();
897
+ else if (part !== "" && part !== ".")
898
+ parts.push(part);
899
+ }
900
+ return `/${parts.join("/")}`;
901
+ }
902
+
903
+ // ../db/src/guard.ts
904
+ var WORD_START = /[a-z_]/i;
905
+ var WORD_PART = /[\w$]/;
906
+ var SPACE = /\s/;
907
+ var ESCAPE_PREFIX = /e/i;
908
+ var DOLLAR_TAG = /^\$(?:[a-z_]\w*)?\$/i;
909
+ var LOCK_KIND = /^(?:update|share|no|key)$/;
910
+ var DIGITS = /\d/;
911
+ var COMMAND_ENDS = ";";
912
+ var UNCLOSED = "unclosed";
913
+ var PRIVILEGED_SET = new Set(["role", "session", "transaction"]);
914
+ var READ_STARTS = new Set(["select", "with", "values", "table", "show", "explain"]);
915
+ var WRITE_WORDS = new Set(["insert", "update", "delete", "merge", "truncate", "drop", "alter", "create", "grant", "revoke", "into", "call", "vacuum", "lock", "refresh", "reindex", "cluster", "do"]);
916
+ var SIDE_EFFECT_FUNCTIONS = /^(?:nextval|setval|set_config|pg_terminate_backend|pg_cancel_backend|pg_reload_conf|pg_rotate_logfile|pg_switch_wal|pg_create_restore_point|pg_advisory_lock\w*|pg_advisory_xact_lock\w*|pg_try_advisory\w*|pg_notify|lo_\w+|dblink\w*|pg_read_file|pg_read_binary_file|pg_ls_dir|query_to_xml\w*|pg_logical_\w+|pg_replication_\w+|pg_drop_replication_slot|pg_stat_reset\w*|pg_promote|pg_file_\w+)$/;
917
+ function tokenize2(sql) {
918
+ const tokens = [];
919
+ let at = 0;
920
+ while (at < sql.length) {
921
+ const char = sql.charAt(at);
922
+ const next = sql.charAt(at + 1);
923
+ if (SPACE.test(char)) {
924
+ at++;
925
+ } else if (char === "-" && next === "-") {
926
+ const end = sql.indexOf(`
927
+ `, at);
928
+ at = end === -1 ? sql.length : end + 1;
929
+ } else if (char === "/" && next === "*" && sql.charAt(at + 2) === "!") {
930
+ at += 3;
931
+ while (DIGITS.test(sql.charAt(at)))
932
+ at++;
933
+ } else if (char === "/" && next === "*") {
934
+ at = skipComment(sql, at);
935
+ } else if (char === "'" || char === '"' || char === "`") {
936
+ const backslashes = char === "'" && ESCAPE_PREFIX.test(sql.charAt(at - 1)) && !WORD_PART.test(sql.charAt(at - 2));
937
+ const end = skipQuoted(sql, at, char, backslashes);
938
+ at = end ?? sql.length;
939
+ tokens.push({ kind: "punct", text: end === undefined ? UNCLOSED : char });
940
+ } else if (char === "$" && DOLLAR_TAG.test(sql.slice(at))) {
941
+ const tag = DOLLAR_TAG.exec(sql.slice(at))?.[0] ?? "$$";
942
+ const end = sql.indexOf(tag, at + tag.length);
943
+ at = end === -1 ? sql.length : end + tag.length;
944
+ tokens.push({ kind: "punct", text: end === -1 ? UNCLOSED : "$" });
945
+ } else if (WORD_START.test(char)) {
946
+ let end = at + 1;
947
+ while (end < sql.length && WORD_PART.test(sql.charAt(end)))
948
+ end++;
949
+ tokens.push({ kind: "word", text: sql.slice(at, end).toLowerCase() });
950
+ at = end;
951
+ } else {
952
+ tokens.push({ kind: "punct", text: char });
953
+ at++;
954
+ }
955
+ }
956
+ return tokens;
957
+ }
958
+ function skipComment(sql, start) {
959
+ let depth = 0;
960
+ let at = start;
961
+ while (at < sql.length) {
962
+ if (sql.startsWith("/*", at)) {
963
+ depth++;
964
+ at += 2;
965
+ } else if (sql.startsWith("*/", at)) {
966
+ depth--;
967
+ at += 2;
968
+ if (depth === 0)
969
+ return at;
970
+ } else {
971
+ at++;
972
+ }
973
+ }
974
+ return sql.length;
975
+ }
976
+ function skipQuoted(sql, start, quote, backslashes) {
977
+ let at = start + 1;
978
+ while (at < sql.length) {
979
+ const char = sql.charAt(at);
980
+ if (backslashes && char === "\\") {
981
+ at += 2;
982
+ } else if (char === quote) {
983
+ if (sql.charAt(at + 1) !== quote)
984
+ return at + 1;
985
+ at += 2;
986
+ } else {
987
+ at++;
988
+ }
989
+ }
990
+ return;
991
+ }
992
+ function splitStatements(tokens) {
993
+ const statements = [[]];
994
+ for (const token of tokens) {
995
+ if (token.kind === "punct" && token.text === COMMAND_ENDS)
996
+ statements.push([]);
997
+ else
998
+ statements.at(-1)?.push(token);
999
+ }
1000
+ return statements.filter((statement) => statement.length > 0);
1001
+ }
1002
+ var isPunct = (token, text) => token?.kind === "punct" && token.text === text;
1003
+ function copyReads(statement, index) {
1004
+ let depth = 0;
1005
+ for (const [at, token] of statement.entries()) {
1006
+ if (at <= index)
1007
+ continue;
1008
+ if (isPunct(token, "("))
1009
+ depth++;
1010
+ else if (isPunct(token, ")"))
1011
+ depth--;
1012
+ else if (depth === 0 && token.kind === "word" && (token.text === "from" || token.text === "to"))
1013
+ return token.text === "to" && (statement[at + 1]?.text === "stdout" || isPunct(statement[index - 1], "\\"));
1014
+ }
1015
+ return false;
1016
+ }
1017
+ function statementWrites(statement) {
1018
+ const first = statement[0];
1019
+ const start = isPunct(first, "\\") ? 1 : 0;
1020
+ const reasons = [];
1021
+ for (const [index, token] of statement.entries()) {
1022
+ if (isPunct(token, UNCLOSED))
1023
+ reasons.push("has a quote that never closes");
1024
+ if (token.kind !== "word")
1025
+ continue;
1026
+ if (SIDE_EFFECT_FUNCTIONS.test(token.text)) {
1027
+ reasons.push(`calls ${token.text}`);
1028
+ } else if (isPunct(statement[index - 1], ".")) {
1029
+ continue;
1030
+ } else if (token.text === "set") {
1031
+ if (index !== start || PRIVILEGED_SET.has(statement[index + 1]?.text ?? ""))
1032
+ reasons.push("uses SET");
1033
+ } else if (token.text === "copy") {
1034
+ if (!copyReads(statement, index))
1035
+ reasons.push("uses COPY");
1036
+ } else if (WRITE_WORDS.has(token.text)) {
1037
+ reasons.push(`uses ${token.text.toUpperCase()}`);
1038
+ } else if (token.text === "analyze" && first?.text !== "explain") {
1039
+ reasons.push("uses ANALYZE");
1040
+ } else if (token.text === "for" && LOCK_KIND.test(statement[index + 1]?.text ?? "")) {
1041
+ reasons.push("takes row locks with FOR UPDATE or SHARE");
1042
+ } else if (token.text === "comment" && statement[index + 1]?.text === "on") {
1043
+ reasons.push("uses COMMENT ON");
1044
+ }
1045
+ }
1046
+ return reasons;
1047
+ }
1048
+ function writes(sql) {
1049
+ return [...new Set(splitStatements(tokenize2(sql)).flatMap(statementWrites))];
1050
+ }
1051
+
1052
+ // ../db/src/clients.ts
1053
+ var SQL_CLIENTS = new Set(["psql", "mysql", "mariadb", "pgcli", "mycli", "sqlite3"]);
1054
+ var WRITING_TOOLS = new Set(["dropdb", "createdb", "pg_restore"]);
1055
+ var CONSOLE_PROGRAMS = new Set(["php", "docker", "symfony", "console", "artisan"]);
1056
+ var STATEMENT_OPTIONS = new Set(["-c", "--command", "-e", "--execute"]);
1057
+ var FILE_OPTIONS = new Set(["-f", "--file", "<"]);
1058
+ var PRINTERS = new Set(["echo", "printf"]);
1059
+ var LIST_OPTIONS = new Set(["-l", "--list"]);
1060
+ var WRITING_CONSOLE = /^(?:doctrine:(?:migrations:(?:migrate|execute|rollup)|schema:(?:update|create|drop)|database:(?:create|drop)|fixtures:load)|migrate(?::\w+)?|db:(?:seed|wipe))$/;
1061
+ var TEST_SCOPED = /(?:^|[^a-z])test/i;
1062
+ var SHOT_STACK = /\.worktrees\/[^/\s]+\/shot(?:\/|$)|--shot(?:-|$)/;
1063
+ var SQL_WRITE = "Statements that write (INSERT, UPDATE, DELETE, DDL, migrations, fixtures) are handed over: list the exact command for the user to run. Only a clearly test-only database runs them directly.";
1064
+ function hasWrite(statement) {
1065
+ return writes(statement).length > 0;
1066
+ }
1067
+ function programOf(word) {
1068
+ return word.split("/").pop() ?? "";
1069
+ }
1070
+ function isTestScoped(words) {
1071
+ const client = words.findIndex((word) => SQL_CLIENTS.has(programOf(word)));
1072
+ return words.some((word, i) => (TEST_SCOPED.test(word) || SHOT_STACK.test(word)) && (i <= client || client === -1 || !STATEMENT_OPTIONS.has(words[i - 1] ?? "") && !FILE_OPTIONS.has(words[i - 1] ?? "")));
1073
+ }
1074
+ async function sqlViolation(command, read) {
1075
+ const pieces = splitCommand(command);
1076
+ let inShotStack = false;
1077
+ for (const [index, piece] of pieces.entries()) {
1078
+ const words = tokenize(piece.command);
1079
+ const { program, args } = parseCommand(piece.command);
1080
+ if (program === "cd")
1081
+ inShotStack = SHOT_STACK.test(args[0] ?? "");
1082
+ if (inShotStack || isTestScoped(words))
1083
+ continue;
1084
+ if (WRITING_TOOLS.has(program) && !words.some((word) => LIST_OPTIONS.has(word)))
1085
+ return SQL_WRITE;
1086
+ if (CONSOLE_PROGRAMS.has(program) && words.some((word) => WRITING_CONSOLE.test(word)))
1087
+ return SQL_WRITE;
1088
+ const client = words.findIndex((word) => SQL_CLIENTS.has(programOf(word)));
1089
+ if (client === -1)
1090
+ continue;
1091
+ const files = words.filter((_, i) => i > client && FILE_OPTIONS.has(words[i - 1] ?? ""));
1092
+ const before = index === 0 ? null : parseCommand(pieces[index - 1]?.command ?? "");
1093
+ const statements = [
1094
+ ...words.filter((_, i) => i > client && STATEMENT_OPTIONS.has(words[i - 1] ?? "")),
1095
+ piece.text === piece.command ? "" : piece.text.slice(piece.text.indexOf(`
1096
+ `)),
1097
+ before !== null && PRINTERS.has(before.program) ? before.args.join(" ") : "",
1098
+ ...(await Promise.all(files.map(read))).map((text) => text ?? "")
1099
+ ];
1100
+ if (statements.some(hasWrite))
1101
+ return SQL_WRITE;
1102
+ }
1103
+ return null;
1104
+ }
1105
+
1106
+ // ../env/src/files.ts
1107
+ var ENV_FILE = /^\.env(?:\..+)?$/;
1108
+ var TEMPLATE = /\.(?:example|sample|template|dist|defaults)$/;
1109
+
1110
+ // ../env/src/guard.ts
1111
+ var REDIRECT = /^\d*[<>]+&?/;
1112
+ var ENV_DUMP = /(?:console\.\w+|JSON\.stringify|Object\.(?:entries|keys|values)|print)\(\s*process\.env\s*[,)]/;
1113
+ var HARMLESS = new Set(["ev", "redact", "ls", "stat", "test", "[", "[[", "touch"]);
1114
+ var GIT_HARMLESS = new Set(["check-ignore", "ls-files", "status"]);
1115
+ var ENV_PRINTERS = new Set(["env", "printenv"]);
1116
+ var DOCKER = new Set(["docker", "docker-compose"]);
1117
+ var SHELLS = new Set(["sh", "bash", "zsh", "ash"]);
1118
+ var DOCKER_VALUE_OPTIONS = new Set(["-f", "--file", "-p", "--project-name", "--profile", "--env-file", "--project-directory", "--ansi", "--progress", "--parallel", "-H", "--host", "-c", "--context", "--config", "-l", "--log-level"]);
1119
+ var EXEC_VALUE_OPTIONS = new Set(["-e", "--env", "--env-file", "-u", "--user", "-w", "--workdir", "--index", "--detach-keys"]);
1120
+ var CONFIG_LISTS = new Set(["--services", "--volumes", "--profiles", "--images", "--networks", "--hash", "-q", "--quiet"]);
1121
+ var FORMAT_OPTIONS = new Set(["-f", "--format"]);
1122
+ var WHOLE_FORMAT = /Env|\{\{\s*json\s+\.\s*\}\}|^json$/;
1123
+ var USE_EV = '.env files hold secrets that must never reach the transcript, so they are not read, edited or printed directly. Use `ev` instead: `ev list`, `ev get KEY` (masked), `ev grep <regex>` for the keys matching it, `ev show <rev>:<file>` for one at a git revision, `printf %s "$value" | ev set KEY [file]` (`--empty` for an empty value), `ev unset KEY`, `ev mv KEY --from .env --to .env.local`, `ev diff a b`, `ev missing`; and pipe output that may carry a secret through `redact`, as `docker compose exec <svc> printenv | redact` or `docker compose config | redact`. If `ev` is missing: `bun install -g @kaynooo/env-tools`.';
1124
+ function isEnvFile(path) {
1125
+ const name = path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf(":")) + 1);
1126
+ return (ENV_FILE.test(name) || name === ".env*") && !TEMPLATE.test(name);
1127
+ }
1128
+ function namesEnvFile(word, previous) {
1129
+ if (previous === "--env-file" || word.startsWith("--env-file="))
1130
+ return false;
1131
+ const bare = word.replace(REDIRECT, "");
1132
+ return isEnvFile(bare.includes("=") ? bare.slice(bare.indexOf("=") + 1) : bare);
1133
+ }
1134
+ function firstOperand(words, valueOptions) {
1135
+ for (let i = 0;i < words.length; i++) {
1136
+ const word = words[i] ?? "";
1137
+ if (valueOptions.has(word))
1138
+ i++;
1139
+ else if (!word.startsWith("-"))
1140
+ return i;
1141
+ }
1142
+ return words.length;
1143
+ }
1144
+ function printsEnv([program = "", ...args]) {
1145
+ if (SHELLS.has(program) && args[0] === "-c")
1146
+ return splitCommand(args[1] ?? "").some((piece) => ENV_PRINTERS.has(tokenize(piece.command)[0] ?? ""));
1147
+ return ENV_PRINTERS.has(program);
1148
+ }
1149
+ function dumpsContainerEnv(program, args) {
1150
+ if (!DOCKER.has(program))
1151
+ return false;
1152
+ const afterOptions = (words) => words.slice(firstOperand(words, DOCKER_VALUE_OPTIONS));
1153
+ let rest = afterOptions(args);
1154
+ const isCompose = program === "docker-compose" || rest[0] === "compose";
1155
+ if (program === "docker" && isCompose)
1156
+ rest = afterOptions(rest.slice(1));
1157
+ const [subcommand = "", ...after] = rest;
1158
+ if (subcommand === "exec")
1159
+ return printsEnv(after.slice(firstOperand(after, EXEC_VALUE_OPTIONS) + 1));
1160
+ if (isCompose)
1161
+ return subcommand === "config" && !after.some((word) => CONFIG_LISTS.has(word));
1162
+ if (subcommand !== "inspect" && (subcommand !== "container" || after[0] !== "inspect"))
1163
+ return false;
1164
+ const format = after.find((_, i) => FORMAT_OPTIONS.has(after[i - 1] ?? "")) ?? after.find((word) => word.startsWith("--format="))?.slice("--format=".length);
1165
+ return format === undefined || WHOLE_FORMAT.test(format);
1166
+ }
1167
+ function readsEnv(piece) {
1168
+ const { program, args } = parseCommand(piece.command);
1169
+ const found = words(piece.command);
1170
+ if (program === "" || HARMLESS.has(program) || program === "git" && GIT_HARMLESS.has(args[0] ?? ""))
1171
+ return false;
1172
+ return found.some((word, index) => !word.isProse && namesEnvFile(word.text, found[index - 1]?.text));
1173
+ }
1174
+ function touchesEnv(command) {
1175
+ if (ENV_DUMP.test(command))
1176
+ return true;
1177
+ if (substitutions(command).some(touchesEnv))
1178
+ return true;
1179
+ const pieces = splitCommand(command);
1180
+ return pieces.some((piece, index) => {
1181
+ if (readsEnv(piece))
1182
+ return true;
1183
+ const { program, args } = parseCommand(piece.command);
1184
+ if (!dumpsContainerEnv(program, args))
1185
+ return false;
1186
+ for (let at = index;pieces[at]?.separator === "|"; at++) {
1187
+ if (parseCommand(pieces[at + 1]?.command ?? "").program === "redact")
1188
+ return false;
1189
+ }
1190
+ return true;
1191
+ });
1192
+ }
1193
+
1194
+ // ../git/src/message.ts
1195
+ var CONVENTIONAL = /^[a-z]+(?:\([^()\n]+\))?!?: \S/;
1196
+ var GIT_MADE = /^(?:Merge |Revert "|fixup! |squash! |amend! )/;
1197
+ var TICKET = /^(?:[\w.-]+\/[\w.-]+)?#\d+ - /;
1198
+ var BREAKING = /^BREAKING[ -]CHANGE: /;
1199
+ var BREAKING_TITLE = /^[a-z]+(?:\([^()\n]+\))?!: /;
1200
+ var TRAILER = /^(?:co-authored-by|signed-off-by|reviewed-by|acked-by|tested-by|reported-by|suggested-by|helped-by|change-id):|^\W*generated with\b/i;
1201
+ var BULLET = /^\s*(?:[-*+]|\d+[.)])\s/;
1202
+ var SENTENCE_END = /[.!?:;)\]`'"]$/;
1203
+ var LOWER_START = /^\p{Ll}/u;
1204
+ var RULE = 'The rule: a `type(scope): title` line, `!` after the scope for a breaking change, type and scope taken from the repository\'s history; then optionally a blank line and one or two lines on what changed, never a bullet list or a hard-wrapped sentence; then a blank line and one line per ticket, its title copied verbatim, `#<n> - <title>` (`owner/repo#<n> - <title>` for another repository). The message ends there: no Co-Authored-By, Signed-off-by or "Generated with" trailer. Only a breaking change gives full details.';
1205
+ var TITLE = `The title is no Conventional Commit. ${RULE}`;
1206
+ var TITLE_LINE = `The title takes one line and a blank line follows it. ${RULE}`;
1207
+ var TRAILERS = `No trailers, ever: no Co-Authored-By, Signed-off-by or "Generated with" line, no --signoff or --trailer. ${RULE}`;
1208
+ var BULLETS = `The body names what changed in a line or two, never a bullet list narrating the diff. ${RULE}`;
1209
+ var TOO_LONG = `The body holds at most two lines besides the ticket lines; what goes beyond belongs in the reply. ${RULE}`;
1210
+ var WRAPPED = `A body sentence stays on one line however long it runs: never hard-wrap it. ${RULE}`;
1211
+ var USE_GC = "Commit with `gc`, which stages exactly the paths given once the message keeps the rule: `gc 'fix(api): retry a timed-out fetch' -m 'A 502 is retried once before failing.' --ticket '#12 - [UC3] Retry failed fetches' -- src/api.ts`.";
1212
+ function isDescription(line) {
1213
+ return line !== undefined && line !== "" && !TICKET.test(line);
1214
+ }
1215
+ function commitMessageViolation(message) {
1216
+ const lines = message.split(`
1217
+ `).map((line) => line.trimEnd());
1218
+ while (lines[0] === "")
1219
+ lines.shift();
1220
+ while (lines.at(-1) === "")
1221
+ lines.pop();
1222
+ const [title = "", second, ...body] = lines;
1223
+ if (title === "")
1224
+ return null;
1225
+ if (lines.some((line) => TRAILER.test(line)))
1226
+ return TRAILERS;
1227
+ if (!CONVENTIONAL.test(title) && !GIT_MADE.test(title))
1228
+ return TITLE;
1229
+ if (second !== undefined && second !== "")
1230
+ return TITLE_LINE;
1231
+ if (BREAKING_TITLE.test(title) || body.some((line) => BREAKING.test(line)))
1232
+ return null;
1233
+ const descriptions = body.filter(isDescription);
1234
+ if (descriptions.some((line) => BULLET.test(line)))
1235
+ return BULLETS;
1236
+ if (descriptions.length > 2)
1237
+ return TOO_LONG;
1238
+ const isWrapped = body.some((line, i) => isDescription(line) && isDescription(body[i + 1]) && !SENTENCE_END.test(line) && LOWER_START.test(body[i + 1] ?? ""));
1239
+ return isWrapped ? WRAPPED : null;
1240
+ }
1241
+
1242
+ // ../out/src/wrap.ts
1243
+ var ASSIGNMENT2 = /^\w+=/;
1244
+ var EXCLUDED = /(?:^|\s)(?:--json|--help|--version|-h)(?:\s|$)/;
1245
+ var BUN_SCRIPT = /^(?:build|check|lint|typecheck|types|test|ci|verify|compile)(?::|$)/;
1246
+ var BUN_TOOLS = new Set(["tsc", "tsgo", "eslint", "keslint", "vitest", "jest", "prettier", "playwright", "biome", "vue-tsc"]);
1247
+ var BUN_SUBCOMMANDS = new Set(["install", "i", "add", "remove", "rm", "update", "test", "check", "lint", "build"]);
1248
+ var STDERR_MERGE = /\s+2>&1$/;
1249
+ var FILTERS = new Set(["tail", "head", "grep"]);
1250
+ var GREP_SUMMARY = /^(?:-[a-zA-Z]*[cqlL]|--(?:count|quiet|silent|files-with(?:out)?-match(?:es)?)$)/;
1251
+ var PHP_VALUE_OPTIONS = new Set(["-d", "-c", "-z"]);
1252
+ var BUNX_VALUE_OPTIONS = new Set(["-p", "--package"]);
1253
+ var COMPOSE_VALUE_OPTIONS = new Set(["-f", "--file", "-p", "--project-name", "--profile", "--env-file", "--project-directory", "--ansi", "--progress", "--parallel"]);
1254
+ var EXEC_VALUE_OPTIONS2 = new Set(["-e", "--env", "--env-file", "-u", "--user", "-w", "--workdir", "--index", "--detach-keys", "--name", "--entrypoint", "-v", "--volume", "-p", "--publish", "-l", "--label", "--cap-add", "--cap-drop", "--pull"]);
1255
+ var always = () => true;
1256
+ var typecheck = ([sub]) => sub === "typecheck";
1257
+ var RULES = {
1258
+ bun: ([sub, script]) => sub !== undefined && (BUN_SUBCOMMANDS.has(sub) || sub === "run" && script !== undefined && BUN_SCRIPT.test(script)),
1259
+ bunx: (args) => {
1260
+ const [tool, ...rest] = afterOptions(args, BUNX_VALUE_OPTIONS);
1261
+ return tool !== undefined && (BUN_TOOLS.has(tool) || (tool === "nuxi" || tool === "nuxt") && typecheck(rest));
1262
+ },
1263
+ tsc: always,
1264
+ tsgo: always,
1265
+ "vue-tsc": always,
1266
+ nuxi: typecheck,
1267
+ nuxt: typecheck,
1268
+ eslint: always,
1269
+ keslint: always,
1270
+ vitest: always,
1271
+ jest: always,
1272
+ pytest: always,
1273
+ phpunit: always,
1274
+ paratest: always,
1275
+ phpstan: always,
1276
+ pest: always,
1277
+ "php-cs-fixer": always,
1278
+ rector: always,
1279
+ php: (args) => listed(afterOptions(args, PHP_VALUE_OPTIONS)),
1280
+ make: always,
1281
+ check: always,
1282
+ just: ([first]) => first !== undefined && !first.startsWith("-"),
1283
+ composer: ([sub]) => sub === "install" || sub === "update" || sub === "require",
1284
+ pip: ([sub]) => sub === "install",
1285
+ cargo: ([sub]) => ["build", "test", "check", "clippy"].includes(sub ?? ""),
1286
+ go: ([sub]) => ["build", "test", "vet"].includes(sub ?? ""),
1287
+ docker: (args) => {
1288
+ if (args[0] === "build")
1289
+ return true;
1290
+ if (args[0] === "exec")
1291
+ return listed(afterOptions(args.slice(1), EXEC_VALUE_OPTIONS2).slice(1));
1292
+ if (args[0] !== "compose")
1293
+ return false;
1294
+ const [sub, ...rest] = afterOptions(args.slice(1), COMPOSE_VALUE_OPTIONS);
1295
+ if (sub === "exec" || sub === "run")
1296
+ return listed(afterOptions(rest, EXEC_VALUE_OPTIONS2).slice(1));
1297
+ return sub === "build" || sub === "pull";
1298
+ }
1299
+ };
1300
+ function afterOptions(args, valued) {
1301
+ let index = 0;
1302
+ while (args[index]?.startsWith("-") === true) {
1303
+ index += valued.has(args[index] ?? "") ? 2 : 1;
1304
+ }
1305
+ return args.slice(index);
1306
+ }
1307
+ function scan(command) {
1308
+ const segments = [];
1309
+ const pipes = [];
1310
+ let current = "";
1311
+ let plain = true;
1312
+ for (const piece of splitCommand(command)) {
1313
+ current += piece.text;
1314
+ plain &&= isPlain(piece.text) && piece.separator !== "&";
1315
+ if (piece.separator === "|") {
1316
+ pipes.push(piece.end);
1317
+ current += "|";
1318
+ plain = false;
1319
+ continue;
1320
+ }
1321
+ segments.push(current);
1322
+ current = "";
1323
+ }
1324
+ return { segments: segments.map((segment) => segment.trim()).filter((segment) => segment !== ""), plain, pipes };
1325
+ }
1326
+ function isFilter(stage) {
1327
+ const { segments, plain } = scan(stage);
1328
+ const [bin, ...args] = tokenize(stage);
1329
+ if (!plain || segments.length !== 1 || bin === undefined || !FILTERS.has(bin))
1330
+ return false;
1331
+ return bin !== "grep" || !args.some((arg) => GREP_SUMMARY.test(arg));
1332
+ }
1333
+ function withoutFilters(command) {
1334
+ let rest = command;
1335
+ for (let at = scan(rest).pipes.at(-1);at !== undefined && isFilter(rest.slice(at + 1).trim()); at = scan(rest).pipes.at(-1))
1336
+ rest = rest.slice(0, at).trimEnd().replace(STDERR_MERGE, "");
1337
+ return rest;
1338
+ }
1339
+ function listed(words) {
1340
+ const start = words.findIndex((word) => !ASSIGNMENT2.test(word));
1341
+ const path = words[start];
1342
+ if (path === undefined)
1343
+ return false;
1344
+ return RULES[path.slice(path.lastIndexOf("/") + 1)]?.(words.slice(start + 1)) ?? false;
1345
+ }
1346
+ function listedSegment(segment) {
1347
+ return !EXCLUDED.test(segment) && listed(tokenize(segment));
1348
+ }
1349
+ function isCd(segment) {
1350
+ return segment === "cd" || segment.startsWith("cd ");
1351
+ }
1352
+ function shouldWrap(command) {
1353
+ const { segments, plain } = scan(withoutFilters(command.trim()));
1354
+ if (!plain || !segments.some((segment) => listedSegment(segment)))
1355
+ return false;
1356
+ return segments.every((segment) => isCd(segment) || listedSegment(segment));
1357
+ }
1358
+ function quoted(value) {
1359
+ return `'${value.replaceAll("'", "'\\''")}'`;
1360
+ }
1361
+ function wrap(command) {
1362
+ const trimmed = withoutFilters(command.trim());
1363
+ if (scan(trimmed).segments.length <= 1 && !ASSIGNMENT2.test(trimmed))
1364
+ return `out -- ${trimmed}`;
1365
+ return `out -- sh -c ${quoted(trimmed)}`;
1366
+ }
1367
+
1368
+ // ../pkg/src/added.ts
1369
+ var REDIRECT2 = /^\d*[<>]/;
1370
+ var BARE_REDIRECT = /^\d*(?:[<>]|>>)$/;
1371
+ var ADDS = {
1372
+ bun: new Set(["add", "a", "install", "i"]),
1373
+ npm: new Set(["install", "i", "add", "in", "isntall"]),
1374
+ pnpm: new Set(["add"]),
1375
+ yarn: new Set(["add"])
1376
+ };
1377
+ var VALUE_FLAGS = new Set(["--cwd", "--filter", "-F", "--workspace", "--registry", "--prefix", "-C", "--dir", "--config", "--cache-dir"]);
1378
+ var NOT_REGISTRY = /^(?:[./~]|file:|link:|portal:|workspace:|git[+:]|github:|gitlab:|bitbucket:|https?:)|\.tgz$|^[\w.-]+\/[\w.-]+(?:#.*)?$/;
1379
+ var NPM_ALIAS = /^(?:@[^/]+\/)?[^@]+@npm:(.+)$/;
1380
+ var PUBLISH = /\b(?:ft|cli\.ts)\s+release\b|\b(?:bun|npm|pnpm|yarn)\s+publish\b|\bjust\s+(?:patch|minor|major)\b/;
1381
+ function addedPackages(command) {
1382
+ let published = false;
1383
+ return splitCommand(command).flatMap((piece) => {
1384
+ published ||= PUBLISH.test(piece.command);
1385
+ const { program, args } = parseCommand(piece.command);
1386
+ const [sub = "", ...rest] = args;
1387
+ if (published || !ADDS[program]?.has(sub))
1388
+ return [];
1389
+ return rest.flatMap((word, index) => {
1390
+ const previous = rest[index - 1] ?? "";
1391
+ if (word.startsWith("-") || REDIRECT2.test(word) || VALUE_FLAGS.has(previous) || BARE_REDIRECT.test(previous) || NOT_REGISTRY.test(word))
1392
+ return [];
1393
+ return [NPM_ALIAS.exec(word)?.[1] ?? word];
1394
+ });
1395
+ });
1396
+ }
1397
+
1398
+ // ../pkg/src/pin.ts
1399
+ var LOCKFILES = [
1400
+ ["bun.lock", "bun"],
1401
+ ["bun.lockb", "bun"],
1402
+ ["pnpm-lock.yaml", "pnpm"],
1403
+ ["yarn.lock", "yarn"],
1404
+ ["package-lock.json", "npm"]
1405
+ ];
1406
+ var ACTIONS = {
1407
+ bun: actions({ install: ["install", "i"], add: ["add", "a"], remove: ["remove", "rm"], update: ["update"] }),
1408
+ pnpm: actions({ install: ["install", "i"], add: ["add"], remove: ["remove", "rm"], update: ["update", "up"] }),
1409
+ yarn: new Map([...actions({ install: ["", "install"], add: ["add"], remove: ["remove"], update: ["upgrade"] }), ["upgrade-interactive", null], ["import", null]]),
1410
+ npm: actions({
1411
+ install: ["install", "i", "in", "ins", "inst", "insta", "instal", "isnt", "isnta", "isntal", "isntall"],
1412
+ add: ["add"],
1413
+ frozen: ["ci", "clean-install", "ic", "install-clean", "isntall-clean"],
1414
+ remove: ["uninstall", "unlink", "remove", "rm", "r", "un"],
1415
+ update: ["update", "u", "up", "upgrade", "udpate"]
1416
+ })
1417
+ };
1418
+ var COMMANDS = {
1419
+ bun: { install: "install", frozen: "install --frozen-lockfile", add: "add", remove: "remove", update: "update" },
1420
+ pnpm: { install: "install", frozen: "install --frozen-lockfile", add: "add", remove: "remove", update: "update" },
1421
+ yarn: { install: "install", add: "add", remove: "remove" },
1422
+ npm: { install: "install", frozen: "ci", add: "install", remove: "uninstall", update: "update" }
1423
+ };
1424
+ var OPTIONS = {
1425
+ bun: { dev: "--dev", exact: "--exact" },
1426
+ pnpm: { dev: "--save-dev", exact: "--save-exact" },
1427
+ yarn: { dev: "--dev", exact: "--exact" },
1428
+ npm: { dev: "--save-dev", exact: "--save-exact" }
1429
+ };
1430
+ var OPTION_FLAGS = new Map([
1431
+ ["-D", "dev"],
1432
+ ["--save-dev", "dev"],
1433
+ ["--dev", "dev"],
1434
+ ["-E", "exact"],
1435
+ ["--save-exact", "exact"],
1436
+ ["--exact", "exact"]
1437
+ ]);
1438
+ var DIR_FLAGS = new Set(["--cwd", "-C", "--dir", "--prefix"]);
1439
+ var VALUE_FLAGS2 = new Set([...DIR_FLAGS, "--filter", "-F"]);
1440
+ var PLAIN_WORD = /^[\w=@./:+-]+$/;
1441
+ function actions(byAction) {
1442
+ return new Map(Object.entries(byAction).flatMap(([action, names]) => names.map((name) => [name, action])));
1443
+ }
1444
+ function isManager(program) {
1445
+ return Object.hasOwn(ACTIONS, program);
1446
+ }
1447
+ function translate(from, to, subcommand, rest) {
1448
+ const packages = rest.filter((arg) => !arg.startsWith("-"));
1449
+ const flags = rest.filter((arg) => arg.startsWith("-"));
1450
+ const listed = ACTIONS[from].get(subcommand);
1451
+ const action = listed === "install" && packages.length > 0 ? "add" : listed;
1452
+ const command = action ? COMMANDS[to][action] : undefined;
1453
+ if (!action || command === undefined || !packages.every((name) => PLAIN_WORD.test(name)))
1454
+ return null;
1455
+ const options = [];
1456
+ for (const flag of flags) {
1457
+ const option = OPTION_FLAGS.get(flag) ?? (from === "bun" && flag === "-d" ? "dev" : undefined);
1458
+ if (option === undefined || action !== "add")
1459
+ return null;
1460
+ options.push(OPTIONS[to][option]);
1461
+ }
1462
+ return [to, command, ...options, ...packages].join(" ");
1463
+ }
1464
+ function installRequests(command, cwd, home) {
1465
+ const requests = [];
1466
+ for (const piece of splitCommand(command)) {
1467
+ const { program, args, assignments, isWrapped } = parseCommand(piece.command);
1468
+ if (program === "cd" && args[0] !== undefined) {
1469
+ cwd = resolvePath(cwd, args[0], home);
1470
+ continue;
1471
+ }
1472
+ if (!isManager(program))
1473
+ continue;
1474
+ let dir = cwd;
1475
+ let subcommand = "";
1476
+ let rest = [];
1477
+ let hasLeadingFlag = false;
1478
+ for (let i = 0;i < args.length; i++) {
1479
+ const arg = args[i] ?? "";
1480
+ const [flag = "", value] = arg.split("=", 2);
1481
+ if (DIR_FLAGS.has(flag) && value !== undefined && value !== "") {
1482
+ dir = resolvePath(cwd, value, home);
1483
+ } else if (VALUE_FLAGS2.has(arg)) {
1484
+ if (DIR_FLAGS.has(arg) && args[i + 1] !== undefined)
1485
+ dir = resolvePath(cwd, args[i + 1] ?? "", home);
1486
+ i++;
1487
+ } else if (!arg.startsWith("-")) {
1488
+ subcommand = arg;
1489
+ rest = args.slice(i + 1);
1490
+ break;
1491
+ }
1492
+ hasLeadingFlag = true;
1493
+ }
1494
+ if (ACTIONS[program].has(subcommand))
1495
+ requests.push({ piece, program, subcommand, rest, dir, assignments, isRewritable: !hasLeadingFlag && !isWrapped && piece.command === piece.text });
1496
+ }
1497
+ return requests;
1498
+ }
1499
+ function mismatchFor(request, pinned) {
1500
+ const { piece, program, subcommand, rest, isRewritable, assignments } = request;
1501
+ if (!pinned || pinned.managers.has(program))
1502
+ return null;
1503
+ const expected = [...pinned.managers];
1504
+ const only = expected.length === 1 ? expected[0] : undefined;
1505
+ const canRewrite = only !== undefined && isRewritable && assignments.every((word) => PLAIN_WORD.test(word));
1506
+ const translated = canRewrite ? translate(program, only, subcommand, rest) : null;
1507
+ const leading = piece.text.slice(0, piece.text.length - piece.text.trimStart().length);
1508
+ const trailing = piece.text.slice(piece.text.trimEnd().length);
1509
+ return {
1510
+ piece,
1511
+ rewrite: translated === null ? null : `${leading}${[...assignments, translated].join(" ")}${trailing}`,
1512
+ reason: `${pinned.dir} is pinned to ${expected.join(" or ")} by its lockfile, so \`${`${program} ${subcommand}`.trim()}\` would install with the wrong package manager.`
1513
+ };
1514
+ }
1515
+
1516
+ // src/git.ts
1517
+ var GLOBAL_VALUE_OPTIONS = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env"]);
1518
+ var READ_ONLY_STASH = new Set(["list", "show"]);
1519
+ var WHOLE_TREE = new Set(["-A", "--all", "-u", "--update", "--no-ignore-removal", ".", "./", ":/", "*"]);
1520
+ var COMMIT_VALUE_SHORTS = new Set(["m", "F", "c", "C", "t"]);
1521
+ var COMMIT_VALUE_LONGS = new Set(["--message", "--file", "--reuse-message", "--reedit-message", "--template", "--author", "--date", "--fixup", "--squash", "--cleanup", "--trailer", "--pathspec-from-file"]);
1522
+ var SHORT_CLUSTER = /^-[A-Z]+$/i;
1523
+ var STAGES_ALL = /[Au]/;
1524
+ var ALL_TAGS = new Set(["--tags", "--follow-tags"]);
1525
+ var STAGE_BY_PATH = `Stage by path: the tree carries unrelated work and peer sessions edit it live. ${USE_GC}`;
1526
+ var STASH = "Never `git stash`: a pop with nothing saved applies someone else's old stash. To see how a test behaves without your change, run it at HEAD with `base -- <cmd>` (a temporary worktree, the working tree untouched), `--keep <path>` to carry the new test over, `--ref <rev>` for another commit.";
1527
+ var ADD_ALL = `\`git add\` over the whole tree is refused. ${STAGE_BY_PATH}`;
1528
+ var COMMIT_ALL = `\`git commit -a\` stages the whole tree and is refused. ${STAGE_BY_PATH}`;
1529
+ var PUSH = "`git push` runs only when the user asked for a push in their latest prompt: say the commit is ready and let them ask.";
1530
+ var TAGS_APART = "A tag goes out with its branch in one `git push --atomic origin <branch> <tag>`, never a separate `git push --tags`.";
1531
+ function subcommandOf(args) {
1532
+ for (let i = 0;i < args.length; i++) {
1533
+ const arg = args[i] ?? "";
1534
+ if (GLOBAL_VALUE_OPTIONS.has(arg))
1535
+ i++;
1536
+ else if (!arg.startsWith("-"))
1537
+ return { name: arg, rest: args.slice(i + 1) };
1538
+ }
1539
+ return { name: "", rest: [] };
1540
+ }
1541
+ function commitsAll(args) {
1542
+ for (let i = 0;i < args.length; i++) {
1543
+ const arg = args[i] ?? "";
1544
+ if (arg === "--")
1545
+ return false;
1546
+ if (arg === "--all")
1547
+ return true;
1548
+ if (COMMIT_VALUE_LONGS.has(arg)) {
1549
+ i++;
1550
+ continue;
1551
+ }
1552
+ if (!SHORT_CLUSTER.test(arg))
1553
+ continue;
1554
+ for (const [index, flag] of [...arg.slice(1)].entries()) {
1555
+ if (flag === "a")
1556
+ return true;
1557
+ if (!COMMIT_VALUE_SHORTS.has(flag))
1558
+ continue;
1559
+ if (index === arg.length - 2)
1560
+ i++;
1561
+ break;
1562
+ }
1563
+ }
1564
+ return false;
1565
+ }
1566
+ function gitViolation(command, isPushAsked = false) {
1567
+ for (const piece of splitCommand(command)) {
1568
+ const { program, args } = parseCommand(piece.command);
1569
+ if (program !== "git")
1570
+ continue;
1571
+ const { name, rest } = subcommandOf(args);
1572
+ if (name === "stash" && !READ_ONLY_STASH.has(rest[0] ?? ""))
1573
+ return STASH;
1574
+ if (name === "add" && rest.some((arg) => WHOLE_TREE.has(arg) || SHORT_CLUSTER.test(arg) && STAGES_ALL.test(arg)))
1575
+ return ADD_ALL;
1576
+ if (name === "commit" && commitsAll(rest))
1577
+ return COMMIT_ALL;
1578
+ if (name === "push" && !isPushAsked)
1579
+ return PUSH;
1580
+ if (name === "push" && rest.some((arg) => ALL_TAGS.has(arg)) && !rest.includes("--atomic"))
1581
+ return TAGS_APART;
1582
+ }
1583
+ return null;
1584
+ }
1585
+
1586
+ // src/commit.ts
1587
+ var REUSES = new Set(["c", "C", "--reuse-message", "--reedit-message", "--fixup", "--squash"]);
1588
+ var TRAILER_FLAGS = new Set(["--signoff", "--trailer"]);
1589
+ var LONG_VALUE = /=(.*)/s;
1590
+ var CAT_OPENER = /^\$\(cat <<-?[ \t]*(['"]?)([\w-]+)\1/;
1591
+ var HEREDOC_OPENER = /<<-?[ \t]*(['"]?)([\w-]+)\1/;
1592
+ var CAT_CLOSER = /^\s*(?:\)\s*)?$/;
1593
+ var EXPANDS = /\$[({\w]|`/;
1594
+ function heredocBody(text, opener) {
1595
+ const found = opener.exec(text);
1596
+ const newline = found === null ? -1 : text.indexOf(`
1597
+ `, found.index + found[0].length);
1598
+ if (found === null || newline === -1)
1599
+ return;
1600
+ const lines = text.slice(newline + 1).split(`
1601
+ `);
1602
+ const end = lines.findIndex((line) => line.trim() === found[2] || line.trim() === `${found[2]})`);
1603
+ return end === -1 ? undefined : { body: lines.slice(0, end).join(`
1604
+ `), rest: lines.slice(end + 1).join(`
1605
+ `) };
1606
+ }
1607
+ function sourceOf(args, text) {
1608
+ const source = { parts: [], file: null, stdin: null, isReused: false, hasTrailerFlag: false };
1609
+ const take = (option, value) => {
1610
+ if (option === "m" || option === "--message")
1611
+ source.parts.push(value);
1612
+ if (option === "F" || option === "--file")
1613
+ source.file = value;
1614
+ if (REUSES.has(option))
1615
+ source.isReused = true;
1616
+ if (option === "--trailer")
1617
+ source.hasTrailerFlag = true;
1618
+ };
1619
+ for (let i = 0;i < args.length; i++) {
1620
+ const arg = args[i] ?? "";
1621
+ if (arg === "--")
1622
+ break;
1623
+ if (arg.startsWith("--")) {
1624
+ const [option = "", value] = arg.split(LONG_VALUE);
1625
+ if (TRAILER_FLAGS.has(option))
1626
+ source.hasTrailerFlag = true;
1627
+ if (REUSES.has(option))
1628
+ source.isReused = true;
1629
+ if (value !== undefined)
1630
+ take(option, value);
1631
+ else if (COMMIT_VALUE_LONGS.has(option))
1632
+ take(option, args[++i] ?? "");
1633
+ continue;
1634
+ }
1635
+ if (!SHORT_CLUSTER.test(arg))
1636
+ continue;
1637
+ for (const [index, flag] of [...arg.slice(1)].entries()) {
1638
+ if (flag === "s")
1639
+ source.hasTrailerFlag = true;
1640
+ if (!COMMIT_VALUE_SHORTS.has(flag))
1641
+ continue;
1642
+ take(flag, index === arg.length - 2 ? args[++i] ?? "" : arg.slice(index + 2));
1643
+ break;
1644
+ }
1645
+ }
1646
+ if (source.file === "-")
1647
+ source.stdin = heredocBody(text, HEREDOC_OPENER)?.body ?? null;
1648
+ return source;
1649
+ }
1650
+ function commitSources(command) {
1651
+ const sources = [];
1652
+ for (const piece of splitCommand(command)) {
1653
+ const { program, args } = parseCommand(piece.command);
1654
+ const { name, rest } = program === "git" ? subcommandOf(args) : { name: "", rest: [] };
1655
+ if (name === "commit")
1656
+ sources.push(sourceOf(rest, piece.text));
1657
+ }
1658
+ return sources;
1659
+ }
1660
+ function inlineMessage(parts) {
1661
+ const texts = [];
1662
+ for (const part of parts) {
1663
+ const found = heredocBody(part, CAT_OPENER);
1664
+ const body = found !== undefined && CAT_CLOSER.test(found.rest) ? found.body : undefined;
1665
+ if (body === undefined && EXPANDS.test(part))
1666
+ return null;
1667
+ texts.push(body ?? part);
1668
+ }
1669
+ return texts.join(`
1670
+
1671
+ `);
1672
+ }
1673
+
1674
+ // src/remote.ts
1675
+ var SHELL_PROGRAMS = new Set(["ssh", "scp", "sftp", "mosh", "autossh"]);
1676
+ var RSYNC_REMOTE = /^(?:[\w.-]+@)?[\w.-]{2,}:/;
1677
+ var SSH_URL = /^ssh:\/\//;
1678
+ var REMOTE = "Remote servers are off limits (no SSH, no deploys, no remote commands). Hand the user the exact command to run themselves.";
1679
+ function isRemote(text) {
1680
+ const { program, args, assignments } = parseCommand(text);
1681
+ if (SHELL_PROGRAMS.has(program))
1682
+ return true;
1683
+ if (program === "rsync")
1684
+ return args.some((arg) => !arg.startsWith("-") && RSYNC_REMOTE.test(arg));
1685
+ if (assignments.some((assignment) => assignment.startsWith("DOCKER_HOST=") && SSH_URL.test(assignment.slice("DOCKER_HOST=".length))))
1686
+ return true;
1687
+ return program === "docker" && tokenize(text).some((word) => SSH_URL.test(word));
1688
+ }
1689
+ function remoteViolation(command) {
1690
+ return splitCommand(command).some((piece) => isRemote(piece.command)) ? REMOTE : null;
1691
+ }
1692
+
1693
+ // src/replace.ts
1694
+ var PYTHON = /^python[\d.]*$/;
1695
+ var SCRIPT_RUNNERS = new Set(["bun", "node"]);
1696
+ var EVAL_FLAGS = new Set(["-e", "--eval", "-p", "--print"]);
1697
+ var WRITES = /\bwrite_text\(|\bBun\.write\(|\bwriteFileSync\(|\bwriteFile\(|\bopen\([^)\n]*,\s*['"](?:w|a|r\+)/;
1698
+ var REPLACE_CALL = /\.(replace|replaceAll)\(/g;
1699
+ var FILE_CALL = /\b(?:Path|open|Bun\.file|Bun\.write|readFileSync|writeFileSync|readFile|writeFile)\(\s*/g;
1700
+ var IDENTIFIER = /^[A-Z_$][\w$]*/i;
1701
+ var COUNT_CHECK = /\.count\(/g;
1702
+ var COUNT_EQUALS = /^\s*\)\s*==\s*(\d+)/;
1703
+ var PYTHON_COUNT = /^\s*,\s*(\d+)\s*\)/;
1704
+ var SPACE2 = /^\s*/;
1705
+ var COMMA = /^\s*,\s*/;
1706
+ var REGEX_FLAGS = /^[gm]*$/;
1707
+ var PLAIN_WORD2 = /^[\w@%+=:,./*?-]+$/;
1708
+ var SED_SEPARATORS = /^[\s;]*/;
1709
+ var SED_SEPARATOR = /[\s;]/;
1710
+ var BRE_ESCAPED = new Map([["(", "("], [")", ")"], ["{", "{"], ["}", "}"], ["+", "+"], ["?", "?"], ["|", "|"]]);
1711
+ var POSIX_CLASSES = new Map([["[:space:]", String.raw`\s`], ["[:digit:]", String.raw`\d`], ["[:alpha:]", "a-zA-Z"], ["[:alnum:]", "a-zA-Z0-9"], ["[:upper:]", "A-Z"], ["[:lower:]", "a-z"], ["[:blank:]", String.raw` \t`]]);
1712
+ var ESCAPES = new Map([["n", `
1713
+ `], ["t", "\t"]]);
1714
+ var BRE_METAS = new Set([".", "[", "]", "*", "^", "$"]);
1715
+ var ERE_METAS = new Set([...BRE_METAS, "+", "?", "(", ")", "{", "}", "|"]);
1716
+ var JS_SPECIALS = /[.*+?^${}()|[\]\\]/g;
1717
+ var WHY = "writes nothing unless each file matches as often as expected, where it rewrites files whatever it matched";
1718
+ var GENERIC = "Run `sub --from <text> --to <text> <file>...` instead (`--regex`, `--all`, `-n N`, `--pairs -` for several in order, `--dry-run --diff` to look first).";
1719
+ function literalAt(text, at, regex = false) {
1720
+ let start = at;
1721
+ const isRaw = text[start] === "r" || text[start] === "R";
1722
+ if (isRaw || text[start] === "b")
1723
+ start++;
1724
+ const quote = text[start] ?? "";
1725
+ if (regex && quote === "/")
1726
+ return regexAt(text, start);
1727
+ if (quote !== "'" && quote !== '"' && quote !== "`")
1728
+ return;
1729
+ const fence = text.startsWith(quote.repeat(3), start) ? quote.repeat(3) : quote;
1730
+ let value = "";
1731
+ for (let i = start + fence.length;i < text.length; i++) {
1732
+ const char = text[i] ?? "";
1733
+ if (text.startsWith(fence, i))
1734
+ return { value, end: i + fence.length };
1735
+ if (quote === "`" && text.startsWith("${", i))
1736
+ return;
1737
+ if (char === "\\" && !isRaw) {
1738
+ const next = text[++i] ?? "";
1739
+ value += ESCAPES.get(next) ?? (next === `
1740
+ ` ? "" : next);
1741
+ } else {
1742
+ value += char;
1743
+ }
1744
+ }
1745
+ return;
1746
+ }
1747
+ function regexAt(text, start) {
1748
+ let source = "";
1749
+ let inClass = false;
1750
+ for (let i = start + 1;i < text.length; i++) {
1751
+ const char = text[i] ?? "";
1752
+ if (char === `
1753
+ `)
1754
+ return;
1755
+ if (char === "\\") {
1756
+ source += char + (text[++i] ?? "");
1757
+ continue;
1758
+ }
1759
+ if (char === "/" && !inClass) {
1760
+ const flags = IDENTIFIER.exec(text.slice(i + 1))?.[0] ?? "";
1761
+ return { value: source, end: i + 1 + flags.length, flags };
1762
+ }
1763
+ if (char === "[" || char === "]")
1764
+ inClass = char === "[";
1765
+ source += char;
1766
+ }
1767
+ return;
1768
+ }
1769
+ function skip(text, at, pattern) {
1770
+ const found = pattern.exec(text.slice(at));
1771
+ return found === null ? undefined : at + found[0].length;
1772
+ }
1773
+ function valueAt(script, at, regex = false) {
1774
+ const literal = literalAt(script, at, regex);
1775
+ const name = literal === undefined ? IDENTIFIER.exec(script.slice(at))?.[0] : undefined;
1776
+ if (literal !== undefined || name === undefined)
1777
+ return literal;
1778
+ const assignments = [...script.slice(0, at).matchAll(new RegExp(String.raw`(?<![\w.$])${name.replaceAll("$", String.raw`\$`)}\s*=(?!=)\s*`, "g"))];
1779
+ const last = assignments.at(-1);
1780
+ const assigned = last === undefined ? undefined : literalAt(script, last.index + last[0].length, regex);
1781
+ return assigned === undefined ? undefined : { ...assigned, end: at + name.length };
1782
+ }
1783
+ function scriptPaths(script) {
1784
+ const paths = new Set;
1785
+ for (const call of script.matchAll(FILE_CALL)) {
1786
+ const path = valueAt(script, call.index + call[0].length)?.value;
1787
+ if (path !== undefined && path !== "")
1788
+ paths.add(path);
1789
+ }
1790
+ return [...paths];
1791
+ }
1792
+ function assertedCounts(script) {
1793
+ const counts = new Map;
1794
+ for (const call of script.matchAll(COUNT_CHECK)) {
1795
+ const text = valueAt(script, call.index + call[0].length);
1796
+ const equals = text === undefined ? null : COUNT_EQUALS.exec(script.slice(text.end));
1797
+ if (text !== undefined && equals !== null)
1798
+ counts.set(text.value, Number(equals[1]));
1799
+ }
1800
+ return counts;
1801
+ }
1802
+ function scriptPairs(script, isPython) {
1803
+ const pairs = [];
1804
+ const counts = isPython ? assertedCounts(script) : new Map;
1805
+ for (const call of script.matchAll(REPLACE_CALL)) {
1806
+ const from = valueAt(script, skip(script, call.index + call[0].length, SPACE2) ?? 0, !isPython);
1807
+ const comma = from === undefined ? undefined : skip(script, from.end, COMMA);
1808
+ const to = comma === undefined ? undefined : valueAt(script, comma);
1809
+ if (from === undefined || to === undefined)
1810
+ return;
1811
+ if (from.flags !== undefined) {
1812
+ if (!REGEX_FLAGS.test(from.flags))
1813
+ return;
1814
+ pairs.push({ from: from.value, to: to.value, regex: true, ...from.flags.includes("g") ? { all: true } : {} });
1815
+ continue;
1816
+ }
1817
+ const limit = isPython ? PYTHON_COUNT.exec(script.slice(to.end)) : null;
1818
+ const count = limit === null ? counts.get(from.value) : Number(limit[1]);
1819
+ const all = call[1] === "replaceAll" || isPython && count === undefined;
1820
+ const pair = { from: from.value, to: to.value };
1821
+ if (all)
1822
+ pair.all = true;
1823
+ else if (count !== undefined && count !== 1)
1824
+ pair.count = count;
1825
+ pairs.push(pair);
1826
+ }
1827
+ return pairs.length === 0 ? undefined : pairs;
1828
+ }
1829
+ function sedArgs(args) {
1830
+ const sed = { scripts: [], files: [], isExtended: false, isInPlace: false, isPlain: true };
1831
+ const operands = [];
1832
+ for (let i = 0;i < args.length; i++) {
1833
+ const arg = args[i] ?? "";
1834
+ if (arg === "--in-place" || arg.startsWith("--in-place="))
1835
+ sed.isInPlace = true;
1836
+ else if (arg === "--regexp-extended")
1837
+ sed.isExtended = true;
1838
+ else if (arg === "--expression")
1839
+ sed.scripts.push(args[++i] ?? "");
1840
+ else if (arg.startsWith("--expression="))
1841
+ sed.scripts.push(arg.slice("--expression=".length));
1842
+ else if (arg.startsWith("--"))
1843
+ sed.isPlain = false;
1844
+ else if (arg.startsWith("-") && arg.length > 1)
1845
+ i += sedCluster(arg, args[i + 1], sed);
1846
+ else
1847
+ operands.push(arg);
1848
+ }
1849
+ if (sed.scripts.length === 0)
1850
+ sed.scripts.push(operands.shift() ?? "");
1851
+ sed.files = operands;
1852
+ return sed;
1853
+ }
1854
+ function sedCluster(arg, next, sed) {
1855
+ for (const [index, flag] of [...arg.slice(1)].entries()) {
1856
+ if (flag === "i") {
1857
+ sed.isInPlace = true;
1858
+ return 0;
1859
+ }
1860
+ if (flag === "E" || flag === "r") {
1861
+ sed.isExtended = true;
1862
+ } else if (flag === "e") {
1863
+ const value = index === arg.length - 2 ? next : arg.slice(index + 2);
1864
+ sed.scripts.push(value ?? "");
1865
+ return index === arg.length - 2 ? 1 : 0;
1866
+ } else if (flag !== "s" && flag !== "u") {
1867
+ sed.isPlain = false;
1868
+ }
1869
+ }
1870
+ return 0;
1871
+ }
1872
+ function sedPart(script, at, delimiter) {
1873
+ for (let i = at;i < script.length; i++) {
1874
+ if (script[i] === "\\")
1875
+ i++;
1876
+ else if (script[i] === delimiter)
1877
+ return { part: script.slice(at, i), end: i + 1 };
1878
+ }
1879
+ return;
1880
+ }
1881
+ function sedLiteral(pattern, delimiter, metas) {
1882
+ let text = "";
1883
+ for (let i = 0;i < pattern.length; i++) {
1884
+ const char = pattern[i] ?? "";
1885
+ if (char !== "\\") {
1886
+ if (metas.has(char))
1887
+ return;
1888
+ text += char;
1889
+ continue;
1890
+ }
1891
+ const next = pattern[++i] ?? "";
1892
+ if (ESCAPES.has(next))
1893
+ text += ESCAPES.get(next);
1894
+ else if (next === delimiter || next === "\\" || BRE_METAS.has(next) || metas === ERE_METAS && ERE_METAS.has(next))
1895
+ text += next;
1896
+ else
1897
+ return;
1898
+ }
1899
+ return text;
1900
+ }
1901
+ function sedRegex(pattern, delimiter, isExtended) {
1902
+ let source = "";
1903
+ for (let i = 0;i < pattern.length; i++) {
1904
+ const char = pattern[i] ?? "";
1905
+ const posix = [...POSIX_CLASSES].find(([name]) => pattern.startsWith(name, i));
1906
+ if (posix !== undefined) {
1907
+ source += posix[1];
1908
+ i += posix[0].length - 1;
1909
+ } else if (char === "\\") {
1910
+ const next = pattern[++i] ?? "";
1911
+ if (next === delimiter)
1912
+ source += next.replaceAll(JS_SPECIALS, String.raw`\$&`);
1913
+ else
1914
+ source += !isExtended && BRE_ESCAPED.has(next) ? next : `\\${next}`;
1915
+ } else {
1916
+ source += !isExtended && BRE_ESCAPED.has(char) ? `\\${char}` : char;
1917
+ }
1918
+ }
1919
+ return source;
1920
+ }
1921
+ function sedReplacement(replacement, literal) {
1922
+ let text = "";
1923
+ for (let i = 0;i < replacement.length; i++) {
1924
+ const char = replacement[i] ?? "";
1925
+ if (char === "&") {
1926
+ text += literal ?? "$&";
1927
+ } else if (char === "$" && literal === undefined) {
1928
+ text += "$$";
1929
+ } else if (char === "\\") {
1930
+ const next = replacement[++i] ?? "";
1931
+ if (next >= "1" && next <= "9" && literal === undefined)
1932
+ text += `$${next}`;
1933
+ else if (next >= "0" && next <= "9")
1934
+ return;
1935
+ else
1936
+ text += ESCAPES.get(next) ?? (next === "$" && literal === undefined ? "$$" : next);
1937
+ } else {
1938
+ text += char;
1939
+ }
1940
+ }
1941
+ return text;
1942
+ }
1943
+ function sedPairs(script, isExtended) {
1944
+ const pairs = [];
1945
+ for (let at = 0;at < script.length; ) {
1946
+ at = skip(script, at, SED_SEPARATORS) ?? at;
1947
+ if (at >= script.length)
1948
+ break;
1949
+ const delimiter = script[at + 1] ?? "";
1950
+ if (script[at] !== "s" || delimiter === "" || delimiter === "\\" || delimiter === `
1951
+ `)
1952
+ return;
1953
+ const pattern = sedPart(script, at + 2, delimiter);
1954
+ const replacement = pattern === undefined ? undefined : sedPart(script, pattern.end, delimiter);
1955
+ if (pattern === undefined || replacement === undefined)
1956
+ return;
1957
+ const isGlobal = script[replacement.end] === "g";
1958
+ at = replacement.end + (isGlobal ? 1 : 0);
1959
+ if (at < script.length && !SED_SEPARATOR.test(script[at] ?? ""))
1960
+ return;
1961
+ const literal = sedLiteral(pattern.part, delimiter, isExtended ? ERE_METAS : BRE_METAS);
1962
+ const to = sedReplacement(replacement.part, literal);
1963
+ if (to === undefined)
1964
+ return;
1965
+ pairs.push({ from: literal ?? sedRegex(pattern.part, delimiter, isExtended), to, ...literal === undefined ? { regex: true } : {}, ...isGlobal ? { all: true } : {} });
1966
+ }
1967
+ return pairs.length === 0 ? undefined : pairs;
1968
+ }
1969
+ function quote(text) {
1970
+ return `'${text.replaceAll("'", String.raw`'\''`)}'`;
1971
+ }
1972
+ function fileWord(path) {
1973
+ return PLAIN_WORD2.test(path) ? path : quote(path);
1974
+ }
1975
+ function subCommand(pairs, files) {
1976
+ const targets = files.length === 0 ? "<file>" : files.map(fileWord).join(" ");
1977
+ const [pair] = pairs;
1978
+ if (pairs.length > 1 || pair === undefined)
1979
+ return `sub --pairs - ${targets} <<'PAIRS'
1980
+ ${JSON.stringify(pairs)}
1981
+ PAIRS`;
1982
+ const flags = [pair.regex === true ? " --regex" : "", pair.all === true ? " --all" : "", pair.count === undefined ? "" : ` -n ${pair.count}`].join("");
1983
+ return `sub --from ${quote(pair.from)} --to ${quote(pair.to)}${flags} ${targets}`;
1984
+ }
1985
+ function route(what, pairs, files) {
1986
+ return pairs === undefined ? `${what} ${GENERIC}` : `${what} Run this instead, which ${WHY}:
1987
+ ${subCommand(pairs, files)}`;
1988
+ }
1989
+ function inlineScript(program, args, text) {
1990
+ const flags = PYTHON.test(program) ? new Set(["-c"]) : EVAL_FLAGS;
1991
+ const flag = args.findIndex((arg) => flags.has(arg));
1992
+ if (flag !== -1)
1993
+ return args[flag + 1] ?? null;
1994
+ const reads = args.every((arg) => arg === "-" || arg.startsWith("<<") || program === "bun" && arg === "run");
1995
+ return reads ? heredocBody(text, HEREDOC_OPENER)?.body ?? null : null;
1996
+ }
1997
+ function replaceViolation(command) {
1998
+ for (const piece of splitCommand(command)) {
1999
+ const { program, args } = parseCommand(piece.command);
2000
+ if (program === "sed") {
2001
+ const sed = sedArgs(args);
2002
+ if (sed.isInPlace)
2003
+ return route("`sed -i` rewrites files without checking what it matched.", sed.isPlain && sed.scripts.length > 0 ? sedPairs(sed.scripts.join(`
2004
+ `), sed.isExtended) : undefined, sed.files);
2005
+ continue;
2006
+ }
2007
+ const isPython = PYTHON.test(program);
2008
+ const script = isPython || SCRIPT_RUNNERS.has(program) ? inlineScript(program, args, piece.text) : null;
2009
+ if (script === null || !WRITES.test(script) || script.search(REPLACE_CALL) === -1)
2010
+ continue;
2011
+ const pairs = scriptPairs(script, isPython);
2012
+ return route(`A ${isPython ? "python" : program} script replacing text in a file is what \`sub\` does with a match-count check.`, pairs, scriptPaths(script));
2013
+ }
2014
+ return null;
2015
+ }
2016
+
2017
+ // src/check.ts
2018
+ function absolute(path, cwd) {
2019
+ return path.startsWith("/") ? path : `${cwd}/${path}`;
2020
+ }
2021
+ function commitViolation(command, { cwd, read }) {
2022
+ for (const source of commitSources(command)) {
2023
+ if (source.hasTrailerFlag)
2024
+ return TRAILERS;
2025
+ if (source.isReused || source.file?.startsWith("/dev/") === true)
2026
+ continue;
2027
+ const { file } = source;
2028
+ let message = source.stdin;
2029
+ if (file === null)
2030
+ message = inlineMessage(source.parts);
2031
+ else if (file !== "-")
2032
+ message = read(absolute(file, cwd));
2033
+ const violation = message === null ? null : commitMessageViolation(message);
2034
+ if (violation !== null)
2035
+ return violation;
2036
+ }
2037
+ return null;
2038
+ }
2039
+ function pinnedManagers(start, exists) {
2040
+ for (let dir = start;; dir = parentDir(dir)) {
2041
+ const managers = new Set(LOCKFILES.filter(([file]) => exists(`${dir}/${file}`)).map(([, manager]) => manager));
2042
+ if (managers.size > 0)
2043
+ return { dir, managers };
2044
+ if (dir === "/")
2045
+ return null;
2046
+ }
2047
+ }
2048
+ function commandsRun(command) {
2049
+ return [command, ...substitutions(command).flatMap(commandsRun)];
2050
+ }
2051
+ function mismatches(command, { cwd, home, exists }) {
2052
+ return installRequests(command, cwd, home).flatMap((request) => {
2053
+ const mismatch = mismatchFor(request, pinnedManagers(request.dir, exists));
2054
+ return mismatch === null ? [] : [mismatch];
2055
+ });
2056
+ }
2057
+ async function checkCommand(command, context) {
2058
+ const violations = [];
2059
+ const notes = [];
2060
+ const add = (rule, message) => {
2061
+ if (message !== null)
2062
+ violations.push({ rule, message });
2063
+ };
2064
+ const run = commandsRun(command);
2065
+ const first = (messages) => messages.find((message) => message !== null) ?? null;
2066
+ add("remote", first(run.map(remoteViolation)));
2067
+ add("git", first(run.map((each) => gitViolation(each, context.isPushAsked))));
2068
+ const commit = commitViolation(command, context);
2069
+ add("commit", commit === null ? null : `${commit} ${USE_GC}`);
2070
+ add("sql", first(await Promise.all(run.map(async (each) => sqlViolation(each, async (path) => Promise.resolve(context.read(absolute(path, context.cwd))))))));
2071
+ add("env", touchesEnv(command) ? USE_EV : null);
2072
+ add("edit", context.canSub ? first(run.map(replaceViolation)) : null);
2073
+ const found = mismatches(command, context);
2074
+ const refused = found.find((mismatch) => mismatch.rewrite === null || !context.canRewrite);
2075
+ const rewrite = refused?.rewrite ?? null;
2076
+ const route = rewrite === null ? "Run it with the pinned manager instead." : `Run \`${rewrite.trim()}\` instead.`;
2077
+ add("package-manager", refused === undefined ? null : `${refused.reason} ${route}`);
2078
+ let rewritten = command;
2079
+ if (refused === undefined && found.length > 0) {
2080
+ rewritten = replacePieces(command, new Map(found.map((mismatch) => [mismatch.piece, mismatch.rewrite ?? ""])));
2081
+ notes.push(...found.map((mismatch) => `${mismatch.reason} It ran as \`${mismatch.rewrite?.trim()}\` instead.`));
2082
+ }
2083
+ if (context.canRewrite && context.canWrap && shouldWrap(rewritten))
2084
+ rewritten = wrap(rewritten);
2085
+ return { violations, command: violations.length > 0 ? command : rewritten, notes: violations.length > 0 ? [] : notes, packages: addedPackages(command) };
2086
+ }
2087
+ function checkPath(path) {
2088
+ return isEnvFile(path) ? { rule: "env", message: USE_EV } : null;
2089
+ }
2090
+
2091
+ // src/bins.ts
2092
+ var BINS = {
2093
+ check: "`check`: runs the repo's check, typecheck, lint and test steps and prints only what failed, each full log saved to a file; validate a change with it rather than chaining the scripts (`--only lint,test`, `--list`).",
2094
+ fix: "`fix`: runs the repo's fixers and formatters (its fix, lint:fix and format scripts, just fmt, go fmt, cargo fmt, ruff) and lists the files they changed.",
2095
+ db: '`db "<sql>"`: one read-only statement against the Postgres in DATABASE_URL (`-e KEY` for another) or, without one, the compose project\'s running Postgres service (`-s <service>`, `-d <database>`), credentials never shown, a write printed for the user to run; `db tables`, `db describe <table>`, `db dbs`.',
2096
+ ev: "`ev`: .env files read and edited with values masked (`ev list`, `ev get KEY`, `ev grep <regex>`, `ev show <rev>:<file>`, `ev set`, `ev diff`, `ev missing`); `ev audit [dir]` finds env files others can read or git tracks, and compose files writing secrets out.",
2097
+ redact: "`redact`: masks secrets in piped output, `<cmd> | redact`.",
2098
+ pkg: "`pkg <name>[@version]`: checks an npm package before it is added: whether the name exists or is a near miss of a popular one, the newest version at least two weeks old, licence, install scripts; `bun add`, `npm install`, `pnpm add` and `yarn add` run it first and are refused on what it flags. `pkg bump <@scope/lib> [version]` waits until the registry serves a release, then `bun add --exact`s it in every consumer under ~/Workspace (`--dry-run`), `pkg consumers <name>` lists them; use it instead of a `bun info` and sleep loop and a `bun add` per repo.",
2099
+ stack: "`stack port <name>`: the port a service name always hashes to, for every host port a compose file publishes; `stack audit [dir]` cross-checks release.config.ts against its compose files.",
2100
+ ft: "`ft`: Forgejo CI runs, logs and waits, releases, issue checklists and claims, in a repo whose origin is on Forgejo.",
2101
+ sub: "`sub --from <text> --to <text> <file>...`: replaces text, writing nothing unless each match count is as expected (1, `-n N`, `--all`), `--regex`, `--pairs <json|->` for several in order, `--dry-run --diff`; use it instead of sed -i, a bun -e or python editor.",
2102
+ base: "`base [--ref <rev>] [--keep <path>...] -- <cmd>`: runs a command at HEAD (or a ref) in a temporary worktree, removed afterwards, ignored env files and node_modules/vendor linked; use it instead of git stash to see whether a test fails without your change (`--keep` the new test).",
2103
+ gst: "`gst [--log N]`: branch, upstream, changed counts and HEAD in one line, then the changed paths by staged, unstaged and untracked; use it instead of `git status --short` and `git log --oneline`.",
2104
+ gc: "`gc <title> [-m <line>]... [--ticket <line>]... -- <path>...`: commits exactly those paths, refusing with nothing staged when the message breaks a commit rule, a path has no change or other files are already staged; use it instead of chaining git add, status and commit.",
2105
+ waitfor: "`waitfor --file <log> --until <regex> [--fail <regex>]`, `--port <n>`, `--url <url>`, `--pid <n>` (`-t 5m`): waits until the line shows, the port listens, the URL answers 2xx or the process exits, exits 1 on a --fail line with the lines around it, 2 on a timeout with the last lines; `waitfor task <id>` waits for a background Bash task and prints its exit code, last lines and failure lines; use it instead of sleep and tail, `until ! pgrep -f` or polling loops.",
2106
+ gl: "`gl ci [ref]` waits for the GitLab pipeline covering a commit and prints each failed job's tests (exit 0/1/2), `gl runs`, `gl logs <pipeline> [-j job] [--full]`; use it instead of `glab ci` or `glab api .../pipelines/<id>/jobs` polled in a sleep loop.",
2107
+ out: "`out -- <cmd>`: folds long output, keeping the errors and saving the full log; run noisy builds, installs, tests and linters through it."
2108
+ };
2109
+ function binsSection(installed) {
2110
+ const lines = Object.entries(BINS).filter(([bin]) => installed.has(bin)).map(([, line]) => `- ${line}`);
2111
+ return lines.length === 0 ? undefined : `# Agent tools
2112
+
2113
+ CLIs installed on this machine for you to use over hand-rolled equivalents, \`<bin> --help\` for the options:
2114
+ ${lines.join(`
2115
+ `)}`;
2116
+ }
2117
+
2118
+ // src/hook.ts
2119
+ var EVENTS = ["pre-tool-use", "user-prompt-submit", "session-start"];
2120
+ var PUSH_WORDS = /\b(?:push|pousse)/i;
2121
+ var SESSION_ID = /^[\w-]+$/;
2122
+ var FILE_TOOLS = new Set(["Read", "Edit", "Write"]);
2123
+ function pushFile(sessionId, { stateDir }) {
2124
+ return sessionId !== undefined && SESSION_ID.test(sessionId) ? `${stateDir}/push-asked/${sessionId}` : null;
2125
+ }
2126
+ function deny(violations) {
2127
+ if (violations.length === 0)
2128
+ return null;
2129
+ return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: violations.map((each) => each.message).join(`
2130
+
2131
+ `) } };
2132
+ }
2133
+ async function bashCall(command, input, machine) {
2134
+ const file = pushFile(input.session_id, machine);
2135
+ const cwd = input.cwd ?? machine.home;
2136
+ const verdict = await checkCommand(command, {
2137
+ cwd,
2138
+ home: machine.home,
2139
+ isPushAsked: file !== null && machine.exists(file),
2140
+ canRewrite: false,
2141
+ canWrap: false,
2142
+ canSub: machine.has("sub"),
2143
+ read: machine.read,
2144
+ exists: machine.exists
2145
+ });
2146
+ if (verdict.violations.length > 0 || verdict.packages.length === 0 || !machine.has("pkg"))
2147
+ return deny(verdict.violations);
2148
+ const checked = await machine.run(["pkg", "check", ...verdict.packages], cwd);
2149
+ return checked.code === 1 ? deny([{ rule: "pkg", message: `pkg refused what this adds, so it was not run:
2150
+ ${checked.text.trim()}` }]) : null;
2151
+ }
2152
+ function preToolUse(input, machine) {
2153
+ const { tool_name: tool, tool_input: given = {} } = input;
2154
+ if (tool === "Bash" && typeof given.command === "string")
2155
+ return bashCall(given.command, input, machine);
2156
+ if (tool !== undefined && FILE_TOOLS.has(tool) && typeof given.file_path === "string")
2157
+ return deny([checkPath(given.file_path)].filter((each) => each !== null));
2158
+ return null;
2159
+ }
2160
+ function userPromptSubmit(input, machine) {
2161
+ const file = pushFile(input.session_id, machine);
2162
+ if (file !== null && PUSH_WORDS.test(input.prompt ?? ""))
2163
+ machine.write(file, "");
2164
+ else if (file !== null)
2165
+ machine.remove(file);
2166
+ return null;
2167
+ }
2168
+ function sessionStart(machine) {
2169
+ const text = binsSection(new Set(Object.keys(BINS).filter(machine.has)));
2170
+ return text === undefined ? null : { hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text } };
2171
+ }
2172
+ async function hook(event, input, machine) {
2173
+ if (event === "pre-tool-use")
2174
+ return preToolUse(input, machine);
2175
+ return event === "user-prompt-submit" ? userPromptSubmit(input, machine) : sessionStart(machine);
2176
+ }
2177
+
2178
+ // src/cli.ts
2179
+ var checkUsage = `guard check [options] [<command> | -]
2180
+
2181
+ Checks a shell command against the rules: remote machines, git (stash, whole-tree add and commit, push, tags), commit messages, SQL writes, env files, package managers and in-place edits (sed -i and replace scripts, pointed at sub).
2182
+ Prints each broken rule with the route to take instead and exits 1, prints nothing and exits 0 when the command is clean.
2183
+ The command is read from stdin with - or when none is given; a rule may rewrite it instead (the pinned package manager, a noisy run through out), printed as \`runs as: <command>\`.`;
2184
+ var checkOptions = {
2185
+ json: jsonOption("{violations, command, notes, packages} for a hook"),
2186
+ cwd: { type: "string", description: "the directory the command runs in (default: here)" },
2187
+ "push-asked": { type: "boolean", description: "the user asked for a push, so git push may run" },
2188
+ "no-wrap": { type: "boolean", description: "never route a noisy command through out" }
2189
+ };
2190
+ var pathUsage = `guard path [--json] <path>...
2191
+
2192
+ Checks the paths a file tool would read, edit or write: env files are refused.`;
2193
+ var pathOptions = {
2194
+ json: jsonOption("{violations} for a hook")
2195
+ };
2196
+ var hookUsage = `guard hook <${EVENTS.join("|")}>
2197
+
2198
+ Answers a Claude Code hook: reads the event JSON on stdin and prints the reply JSON, nothing when the call goes ahead untouched.
2199
+ pre-tool-use refuses a Bash call breaking a rule, an install pkg refuses, and a Read, Edit or Write of an env file; user-prompt-submit remembers whether the prompt asked for a push; session-start lists the installed agent-tools bins.
2200
+ A failure exits 1, which Claude Code reports without blocking the call.`;
2201
+ function readText(path) {
2202
+ try {
2203
+ return readFileSync(path, "utf8");
2204
+ } catch {
2205
+ return null;
2206
+ }
2207
+ }
2208
+ function report(violations) {
2209
+ for (const { rule, message } of violations)
2210
+ console.log(`${rule}: ${message}`);
2211
+ }
2212
+ async function check(args) {
2213
+ const parsed = parse(args, checkUsage, checkOptions);
2214
+ const { values, positionals } = parsed;
2215
+ if (positionals.length > 1)
2216
+ fail(`give the command as one argument, quoted
2217
+
2218
+ ${checkUsage}`);
2219
+ const given = positionals[0] ?? "-";
2220
+ const command = given === "-" ? await readInput("-", { noStdin: "give the command, or pipe it on stdin" }) : given;
2221
+ const context = {
2222
+ cwd: values.cwd ?? process6.cwd(),
2223
+ home: homedir(),
2224
+ isPushAsked: values["push-asked"] === true,
2225
+ canRewrite: true,
2226
+ canWrap: values["no-wrap"] !== true && Bun.which("out") !== null,
2227
+ canSub: Bun.which("sub") !== null,
2228
+ read: readText,
2229
+ exists: existsSync
2230
+ };
2231
+ const verdict = await checkCommand(command, context);
2232
+ if (values.json === true)
2233
+ console.log(JSON.stringify(verdict));
2234
+ else
2235
+ report(verdict.violations);
2236
+ if (values.json !== true && verdict.command !== command)
2237
+ console.log(`runs as: ${verdict.command}${verdict.notes.map((note) => `
2238
+ ${note}`).join("")}`);
2239
+ return verdict.violations.length > 0 ? 1 : 0;
2240
+ }
2241
+ function path(args) {
2242
+ const parsed = parse(args, pathUsage, pathOptions);
2243
+ if (parsed.positionals.length === 0)
2244
+ fail(`give at least one path
2245
+
2246
+ ${pathUsage}`);
2247
+ const violations = parsed.positionals.flatMap((each) => checkPath(each) ?? []);
2248
+ if (parsed.values.json === true)
2249
+ console.log(JSON.stringify({ violations }));
2250
+ else
2251
+ report(violations);
2252
+ return violations.length > 0 ? 1 : 0;
2253
+ }
2254
+ function machine() {
2255
+ const home = homedir();
2256
+ return {
2257
+ home,
2258
+ stateDir: `${process6.env.XDG_STATE_HOME ?? `${home}/.local/state`}/guard`,
2259
+ has: (bin) => Bun.which(bin) !== null,
2260
+ run: async (argv, cwd) => runCommand(argv, { cwd }),
2261
+ read: readText,
2262
+ exists: existsSync,
2263
+ write: (path, text) => {
2264
+ mkdirSync(dirname(path), { recursive: true });
2265
+ writeFileSync(path, text);
2266
+ },
2267
+ remove: (path) => rmSync(path, { force: true })
2268
+ };
2269
+ }
2270
+ async function hookCommand(args) {
2271
+ const parsed = parse(args, hookUsage, {});
2272
+ const event = parsed.positionals[0] ?? "";
2273
+ if (!EVENTS.includes(event))
2274
+ fail(`unknown event ${event}
2275
+
2276
+ ${hookUsage}`, 1);
2277
+ const reply = await hook(event, JSON.parse(await Bun.stdin.text()), machine());
2278
+ if (reply !== null)
2279
+ console.log(JSON.stringify(reply));
2280
+ return 0;
2281
+ }
2282
+ process6.exitCode = await runCli({
2283
+ name: "guard",
2284
+ version,
2285
+ about: "the safety rules a Claude Code Bash call is held to, each refusal naming the route",
2286
+ commands: {
2287
+ check: { run: check, summary: "check a shell command, printing each broken rule with its route" },
2288
+ path: { run: async (args) => Promise.resolve(path(args)), summary: "check a path a file tool would open" },
2289
+ hook: { run: hookCommand, summary: "answer a Claude Code hook event, JSON in and out" }
2290
+ },
2291
+ defaultCommand: "check"
2292
+ });