@youtyan/code-viewer 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -94,6 +94,157 @@ var init_sqlite_driver = __esm(() => {
94
94
  NPX_CACHE_GUIDE = 'Stale npx cache likely contains a binary compiled for a different Node.js version. Fix: `rm -rf ~/.npm/_npx` (macOS / Linux) or `Remove-Item -Recurse -Force "$(npm config get cache)\\_npx"` (Windows), ' + "then re-run `npx -y @youtyan/code-viewer@latest …`.";
95
95
  });
96
96
 
97
+ // web-src/core/id.ts
98
+ function bytesToHex(bytes) {
99
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
100
+ }
101
+ function bytesToBase36(bytes) {
102
+ return Array.from(bytes, (b) => BASE36_ALPHABET[b % BASE36_ALPHABET.length]).join("");
103
+ }
104
+ function randomBase36(length) {
105
+ const cryptoApi = globalThis.crypto;
106
+ if (typeof cryptoApi?.getRandomValues === "function") {
107
+ const bytes = new Uint8Array(length);
108
+ cryptoApi.getRandomValues(bytes);
109
+ return bytesToBase36(bytes);
110
+ }
111
+ return Math.random().toString(36).slice(2, 2 + length).padEnd(length, "0");
112
+ }
113
+ function makeId(prefix) {
114
+ const cryptoApi = globalThis.crypto;
115
+ if (typeof cryptoApi?.randomUUID === "function") {
116
+ return `${prefix}-${cryptoApi.randomUUID().replace(/-/g, "").slice(0, 16)}`;
117
+ }
118
+ if (typeof cryptoApi?.getRandomValues === "function") {
119
+ const bytes = new Uint8Array(8);
120
+ cryptoApi.getRandomValues(bytes);
121
+ return `${prefix}-${bytesToHex(bytes)}`;
122
+ }
123
+ return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
124
+ }
125
+ function makeTimedId(prefix) {
126
+ const time = Date.now().toString(36);
127
+ const random = randomBase36(6);
128
+ return `${prefix}-${time}${random}`;
129
+ }
130
+ var BASE36_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
131
+
132
+ // web-src/core/routes.ts
133
+ function assertNever(value) {
134
+ throw new Error(`unhandled route: ${JSON.stringify(value)}`);
135
+ }
136
+ function parseLineTarget(value) {
137
+ const raw = value || "";
138
+ const range = /^(\d+)-(\d+)$/.exec(raw);
139
+ if (range) {
140
+ const a = Number(range[1]);
141
+ const b = Number(range[2]);
142
+ const start = Math.min(a, b);
143
+ const end = Math.max(a, b);
144
+ if (start > 0)
145
+ return { start, end };
146
+ return;
147
+ }
148
+ const line = Number(raw);
149
+ return Number.isInteger(line) && line > 0 ? line : undefined;
150
+ }
151
+ function formatLineTarget(line) {
152
+ return typeof line === "number" ? String(line) : `${line.start}-${line.end}`;
153
+ }
154
+ function buildRoute(route) {
155
+ switch (route.screen) {
156
+ case "repo": {
157
+ const params = new URLSearchParams;
158
+ if (route.ref && route.ref !== "worktree")
159
+ params.set("ref", route.ref);
160
+ if (route.path)
161
+ params.set("path", route.path);
162
+ const qs = params.toString();
163
+ return `/${qs ? `?${qs}` : ""}`;
164
+ }
165
+ case "file":
166
+ if (route.view === "blob") {
167
+ return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=blob" + (route.preview ? "&preview=1" : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
168
+ }
169
+ if (route.view === "blame") {
170
+ const ref = route.ref || "worktree";
171
+ return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(ref) + "&view=blame" + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
172
+ }
173
+ if (route.view === "history") {
174
+ return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=history" + (route.commit ? `&commit=${encodeURIComponent(route.commit)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
175
+ }
176
+ return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
177
+ case "diff":
178
+ return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.path ? `&path=${encodeURIComponent(route.path)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
179
+ case "help": {
180
+ const params = new URLSearchParams;
181
+ if (route.lang && route.lang !== "en")
182
+ params.set("lang", route.lang);
183
+ if (route.section && route.section !== "overview")
184
+ params.set("section", route.section);
185
+ const qs = params.toString();
186
+ return `/help${qs ? `?${qs}` : ""}`;
187
+ }
188
+ case "history": {
189
+ const params = new URLSearchParams;
190
+ if (route.ref && route.ref !== "HEAD")
191
+ params.set("ref", route.ref);
192
+ if (route.commit)
193
+ params.set("commit", route.commit);
194
+ const qs = params.toString();
195
+ return `/history${qs ? `?${qs}` : ""}`;
196
+ }
197
+ case "journal": {
198
+ const params = new URLSearchParams;
199
+ if (route.tab && route.tab !== "journal")
200
+ params.set("tab", route.tab);
201
+ if (route.date)
202
+ params.set("date", route.date);
203
+ if (route.label)
204
+ params.set("label", route.label);
205
+ if (route.task)
206
+ params.set("task", route.task);
207
+ const qs = params.toString();
208
+ return `/journal${qs ? `?${qs}` : ""}`;
209
+ }
210
+ case "database": {
211
+ const params = new URLSearchParams;
212
+ if (route.db)
213
+ params.set("db", route.db);
214
+ if (route.schema)
215
+ params.set("schema", route.schema);
216
+ if (route.table)
217
+ params.set("table", route.table);
218
+ if (route.tab)
219
+ params.set("tab", route.tab);
220
+ if (route.diffBefore)
221
+ params.set("diffBefore", route.diffBefore);
222
+ if (route.diffAfter)
223
+ params.set("diffAfter", route.diffAfter);
224
+ const qs = params.toString();
225
+ return `/database${qs ? `?${qs}` : ""}`;
226
+ }
227
+ case "unknown":
228
+ return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
229
+ default:
230
+ return assertNever(route);
231
+ }
232
+ }
233
+ var SPA_PATHS, APP_ENTRY_PATHS;
234
+ var init_routes = __esm(() => {
235
+ SPA_PATHS = [
236
+ "/todif",
237
+ "/todiff",
238
+ "/file",
239
+ "/help",
240
+ "/history",
241
+ "/journal",
242
+ "/database",
243
+ "/doctor"
244
+ ];
245
+ APP_ENTRY_PATHS = ["/", "/index.html"];
246
+ });
247
+
97
248
  // web-src/server/json-store.ts
98
249
  import { randomBytes } from "node:crypto";
99
250
  import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
@@ -205,6 +356,11 @@ function createJsonFileStore(options) {
205
356
  }
206
357
  var init_json_store = () => {};
207
358
 
359
+ // web-src/server/ordered-insert.ts
360
+ function orderedInsertOptionCount(input) {
361
+ return (input.before_id ? 1 : 0) + (input.after_id ? 1 : 0) + (input.position !== undefined ? 1 : 0);
362
+ }
363
+
208
364
  // web-src/server/annotations.ts
209
365
  import { join } from "node:path";
210
366
  function annotationsFilePath(root) {
@@ -214,9 +370,7 @@ function emptyAnnotationsState() {
214
370
  return { version: 1, sessions: [] };
215
371
  }
216
372
  function makeAnnotationId(prefix) {
217
- const random = Math.random().toString(36).slice(2, 8);
218
- const time = Date.now().toString(36);
219
- return `${prefix}-${time}${random}`;
373
+ return makeTimedId(prefix);
220
374
  }
221
375
  function normalizeLineRange(raw) {
222
376
  if (!raw || typeof raw !== "object")
@@ -229,16 +383,10 @@ function normalizeLineRange(raw) {
229
383
  return { start, end: endValue };
230
384
  }
231
385
  function parseAnnotationLine(raw) {
232
- const range = /^(\d+)-(\d+)$/.exec(raw);
233
- if (range) {
234
- const a = Number(range[1]);
235
- const b = Number(range[2]);
236
- const start = Math.min(a, b);
237
- const end = Math.max(a, b);
238
- return start > 0 ? { start, end } : undefined;
239
- }
240
- const line = Number(raw);
241
- return Number.isInteger(line) && line > 0 ? { start: line, end: line } : undefined;
386
+ const target = parseLineTarget(raw);
387
+ if (target === undefined)
388
+ return;
389
+ return typeof target === "number" ? { start: target, end: target } : target;
242
390
  }
243
391
  function normalizeRange(raw) {
244
392
  const from = raw && typeof raw === "object" && typeof raw.from === "string" ? raw.from || "HEAD" : "HEAD";
@@ -441,11 +589,8 @@ function startAnnotationSession(state, title, now, id = makeAnnotationId("s")) {
441
589
  session
442
590
  };
443
591
  }
444
- function insertOptionCount(input) {
445
- return (input.before_id ? 1 : 0) + (input.after_id ? 1 : 0) + (input.position !== undefined ? 1 : 0);
446
- }
447
592
  function entryInsertIndex(entries, input) {
448
- if (insertOptionCount(input) > 1)
593
+ if (orderedInsertOptionCount(input) > 1)
449
594
  return { ok: false, error: "use only one of before, after, or position" };
450
595
  if (input.before_id) {
451
596
  const index = entries.findIndex((entry) => entry.id === input.before_id);
@@ -471,7 +616,7 @@ function entryInsertIndex(entries, input) {
471
616
  function findSessionByEntryId(sessions, entryId) {
472
617
  return sessions.find((session) => session.entries.some((entry) => entry.id === entryId));
473
618
  }
474
- function addAnnotationEntry(state, input, now, makeId = makeAnnotationId) {
619
+ function addAnnotationEntry(state, input, now, makeId2 = makeAnnotationId) {
475
620
  const target = normalizeAnnotationTarget(input.target);
476
621
  if (target?.kind === "database" && !target.db)
477
622
  return { ok: false, error: "database annotation requires db" };
@@ -514,13 +659,13 @@ function addAnnotationEntry(state, input, now, makeId = makeAnnotationId) {
514
659
  session = sessions[sessions.length - 1];
515
660
  }
516
661
  if (!session) {
517
- const started = startAnnotationSession(state, input.session_title || "", now, makeId("s"));
662
+ const started = startAnnotationSession(state, input.session_title || "", now, makeId2("s"));
518
663
  sessions = started.state.sessions;
519
664
  session = started.session;
520
665
  createdSession = true;
521
666
  }
522
667
  const entry = {
523
- id: makeId("a"),
668
+ id: makeId2("a"),
524
669
  created_at: now,
525
670
  path,
526
671
  range: normalizeRange(input.range),
@@ -554,7 +699,7 @@ function addAnnotationEntry(state, input, now, makeId = makeAnnotationId) {
554
699
  };
555
700
  }
556
701
  function moveAnnotationEntry(state, id, input) {
557
- if (insertOptionCount(input) !== 1)
702
+ if (orderedInsertOptionCount(input) !== 1)
558
703
  return { ok: false, error: "move requires before, after, or position" };
559
704
  const sourceSession = findSessionByEntryId(state.sessions, id);
560
705
  const entry = sourceSession?.entries.find((e) => e.id === id);
@@ -654,6 +799,7 @@ function deleteAnnotationById(state, id) {
654
799
  }
655
800
  var CODE_VIEWER_DIR = ".code-viewer", ANNOTATIONS_FILE_NAME = "annotations.json", ANNOTATION_BODY_MAX_BYTES, ANNOTATION_TITLE_MAX_CHARS = 300, MAX_ANNOTATIONS_JSON_BYTES = 5000000, annotationsStore;
656
801
  var init_annotations = __esm(() => {
802
+ init_routes();
657
803
  init_json_store();
658
804
  ANNOTATION_BODY_MAX_BYTES = 64 * 1024;
659
805
  annotationsStore = createJsonFileStore({
@@ -666,47 +812,9 @@ var init_annotations = __esm(() => {
666
812
  });
667
813
  });
668
814
 
669
- // web-src/server/cache.ts
670
- import { lstatSync } from "node:fs";
671
- import { join as join2 } from "node:path";
672
- function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
673
- return !!cached && now - cached.storedAt <= ttlMs;
674
- }
675
- function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
676
- cache.set(key, { ...value, storedAt: now });
677
- while (cache.size > maxEntries) {
678
- const oldest = cache.keys().next().value;
679
- if (oldest === undefined)
680
- break;
681
- cache.delete(oldest);
682
- }
683
- }
684
- function worktreeFileSignature(path, cwd) {
685
- try {
686
- const stats = lstatSync(join2(cwd, path));
687
- const inode = "ino" in stats ? stats.ino : 0;
688
- return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
689
- } catch {
690
- return "state:missing";
691
- }
692
- }
693
- function fileDiffCacheKey(options) {
694
- const worktreeTarget = options.range.from === "worktree" || !options.range.to || options.range.to === "worktree";
695
- if (options.isUntracked && !worktreeTarget) {
696
- throw new Error("untracked file diffs require a worktree range");
697
- }
698
- const signature = worktreeTarget ? `\x00${worktreeFileSignature(options.path, options.cwd)}` : "";
699
- if (options.isUntracked) {
700
- return `u\x00${options.path}${signature}\x00${options.extras.join("\x00")}`;
701
- }
702
- return `t\x00${options.path}\x00${options.oldPath || ""}${signature}\x00${[...options.extras, ...options.args].join("\x00")}`;
703
- }
704
- var CACHE_TTL_MS = 1500, MAX_TIMED_CACHE_ENTRIES = 200;
705
- var init_cache = () => {};
706
-
707
815
  // web-src/server/command-resolver.ts
708
816
  import { accessSync, constants, realpathSync, statSync } from "node:fs";
709
- import { dirname as dirname2, isAbsolute, join as join3, relative } from "node:path";
817
+ import { dirname as dirname2, isAbsolute, join as join2, relative } from "node:path";
710
818
  function isExternalCommandName(value) {
711
819
  return commandNameSet.has(value);
712
820
  }
@@ -841,7 +949,7 @@ function findGitRootByWalking(start) {
841
949
  let current = start;
842
950
  for (;; ) {
843
951
  try {
844
- statSync(join3(current, ".git"));
952
+ statSync(join2(current, ".git"));
845
953
  return realpathSync(current);
846
954
  } catch {}
847
955
  const parent = dirname2(current);
@@ -873,6 +981,44 @@ var init_command_resolver = __esm(() => {
873
981
  activeOverrides = new Map;
874
982
  });
875
983
 
984
+ // web-src/server/cache.ts
985
+ import { lstatSync } from "node:fs";
986
+ import { join as join3 } from "node:path";
987
+ function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
988
+ return !!cached && now - cached.storedAt <= ttlMs;
989
+ }
990
+ function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
991
+ cache.set(key, { ...value, storedAt: now });
992
+ while (cache.size > maxEntries) {
993
+ const oldest = cache.keys().next().value;
994
+ if (oldest === undefined)
995
+ break;
996
+ cache.delete(oldest);
997
+ }
998
+ }
999
+ function worktreeFileSignature(path, cwd) {
1000
+ try {
1001
+ const stats = lstatSync(join3(cwd, path));
1002
+ const inode = "ino" in stats ? stats.ino : 0;
1003
+ return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
1004
+ } catch {
1005
+ return "state:missing";
1006
+ }
1007
+ }
1008
+ function fileDiffCacheKey(options) {
1009
+ const worktreeTarget = options.range.from === "worktree" || !options.range.to || options.range.to === "worktree";
1010
+ if (options.isUntracked && !worktreeTarget) {
1011
+ throw new Error("untracked file diffs require a worktree range");
1012
+ }
1013
+ const signature = worktreeTarget ? `\x00${worktreeFileSignature(options.path, options.cwd)}` : "";
1014
+ if (options.isUntracked) {
1015
+ return `u\x00${options.path}${signature}\x00${options.extras.join("\x00")}`;
1016
+ }
1017
+ return `t\x00${options.path}\x00${options.oldPath || ""}${signature}\x00${[...options.extras, ...options.args].join("\x00")}`;
1018
+ }
1019
+ var CACHE_TTL_MS = 1500, MAX_TIMED_CACHE_ENTRIES = 200;
1020
+ var init_cache = () => {};
1021
+
876
1022
  // web-src/server/name-pattern.ts
877
1023
  function parseGlobSegment(pattern) {
878
1024
  const matchers = [];
@@ -985,6 +1131,21 @@ import {
985
1131
  } from "node:http";
986
1132
  import { Readable } from "node:stream";
987
1133
  function runSync(args, cwd, options = {}) {
1134
+ const proc = runBytesSync(args, cwd, options);
1135
+ return {
1136
+ code: proc.code,
1137
+ stdout: new TextDecoder().decode(proc.stdout),
1138
+ stderr: proc.stderr
1139
+ };
1140
+ }
1141
+ function runAsync(args, cwd, options = {}) {
1142
+ return runBytesAsync(args, cwd, options).then((proc) => ({
1143
+ code: proc.code,
1144
+ stdout: new TextDecoder().decode(proc.stdout),
1145
+ stderr: proc.stderr
1146
+ }));
1147
+ }
1148
+ function runBytesSync(args, cwd, options = {}) {
988
1149
  const proc = spawnSync(args[0], args.slice(1), {
989
1150
  cwd,
990
1151
  encoding: "buffer",
@@ -995,17 +1156,10 @@ function runSync(args, cwd, options = {}) {
995
1156
  });
996
1157
  return {
997
1158
  code: proc.status ?? (proc.error ? 1 : 0),
998
- stdout: new TextDecoder().decode(proc.stdout || new Uint8Array),
1159
+ stdout: new Uint8Array(proc.stdout || new Uint8Array),
999
1160
  stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
1000
1161
  };
1001
1162
  }
1002
- function runAsync(args, cwd, options = {}) {
1003
- return runBytesAsync(args, cwd, options).then((proc) => ({
1004
- code: proc.code,
1005
- stdout: new TextDecoder().decode(proc.stdout),
1006
- stderr: proc.stderr
1007
- }));
1008
- }
1009
1163
  function runBytesAsync(args, cwd, options = {}) {
1010
1164
  const maxBuffer = options.maxBuffer ?? 64 * 1024 * 1024;
1011
1165
  return new Promise((resolve) => {
@@ -3089,6 +3243,33 @@ function takeValue(argv, index, flag) {
3089
3243
  return { error: `${flag} requires a value` };
3090
3244
  return { value, next: index + 1 };
3091
3245
  }
3246
+ function takeGlobalCliOption(argv, index, options) {
3247
+ const flag = argv[index];
3248
+ if (flag === "--cwd" || flag === "--server" && options.allowServer) {
3249
+ const taken = takeValue(argv, index, flag);
3250
+ if ("error" in taken)
3251
+ return { kind: "error", error: taken.error };
3252
+ return {
3253
+ kind: flag === "--cwd" ? "cwd" : "server",
3254
+ value: taken.value,
3255
+ next: taken.next
3256
+ };
3257
+ }
3258
+ if (flag === "--bin" && options.allowedCommands) {
3259
+ const taken = takeValue(argv, index, flag);
3260
+ if ("error" in taken)
3261
+ return { kind: "error", error: taken.error };
3262
+ const parsed = parseExternalCommandOverride(taken.value, "--bin", options.allowedCommands);
3263
+ if (parsed.ok === false)
3264
+ return { kind: "error", error: parsed.error };
3265
+ return {
3266
+ kind: "command-override",
3267
+ override: parsed.override,
3268
+ next: taken.next
3269
+ };
3270
+ }
3271
+ return { kind: "unhandled" };
3272
+ }
3092
3273
  function shellSingleQuote(value) {
3093
3274
  return `'${value.replace(/'/g, "'\\''")}'`;
3094
3275
  }
@@ -3108,7 +3289,7 @@ function isUnsafeText(value) {
3108
3289
  return true;
3109
3290
  return false;
3110
3291
  }
3111
- function validateRefValue(value, flag) {
3292
+ function validateSafeCliValue(value, flag) {
3112
3293
  if (!value)
3113
3294
  return `${flag} requires a non-empty value`;
3114
3295
  if (isUnsafeText(value))
@@ -3117,13 +3298,16 @@ function validateRefValue(value, flag) {
3117
3298
  return `${flag} must not start with '-'`;
3118
3299
  return;
3119
3300
  }
3301
+ function validateRefValue(value, flag) {
3302
+ const error = validateSafeCliValue(value, flag);
3303
+ if (error)
3304
+ return error;
3305
+ return;
3306
+ }
3120
3307
  function validateRepoRelativePathValue(value, flag) {
3121
- if (!value)
3122
- return `${flag} requires a non-empty value`;
3123
- if (isUnsafeText(value))
3124
- return `${flag} must be single-line and must not contain NUL`;
3125
- if (value.startsWith("-"))
3126
- return `${flag} must not start with '-'`;
3308
+ const error = validateSafeCliValue(value, flag);
3309
+ if (error)
3310
+ return error;
3127
3311
  if (value.startsWith("/") || value.startsWith("\\"))
3128
3312
  return `${flag} must be repo-relative`;
3129
3313
  const parts = value.split(/[\\/]+/);
@@ -3237,6 +3421,7 @@ function extractErrorDetail(rawBody, isJson, status) {
3237
3421
  return trimmed;
3238
3422
  }
3239
3423
  var init_cli_helpers = __esm(() => {
3424
+ init_command_resolver();
3240
3425
  init_git();
3241
3426
  init_server_registry();
3242
3427
  });
@@ -3256,6 +3441,28 @@ function parsePosition(value) {
3256
3441
  const n = Number(value);
3257
3442
  return Number.isInteger(n) && n > 0 ? n : Number.NaN;
3258
3443
  }
3444
+ function parseAnnotationAddOptions(options) {
3445
+ const body = options.get("--body");
3446
+ const bodyFile = options.get("--body-file");
3447
+ if (body !== undefined && bodyFile !== undefined)
3448
+ return { ok: false, error: "use either --body or --body-file" };
3449
+ const position = parsePosition(options.get("--position"));
3450
+ if (Number.isNaN(position))
3451
+ return { ok: false, error: "--position must be a positive integer" };
3452
+ return {
3453
+ ok: true,
3454
+ options: {
3455
+ title: options.get("--title"),
3456
+ session: options.get("--session"),
3457
+ sessionTitle: options.get("--session-title"),
3458
+ body,
3459
+ bodyFile,
3460
+ before: options.get("--before"),
3461
+ after: options.get("--after"),
3462
+ position
3463
+ }
3464
+ };
3465
+ }
3259
3466
  function parseFilter(value) {
3260
3467
  const idx = value.indexOf("=");
3261
3468
  if (idx <= 0)
@@ -3310,15 +3517,15 @@ function parseAnnotateArgs(argv) {
3310
3517
  const arg = argv[i];
3311
3518
  if (arg === "--help" || arg === "-h")
3312
3519
  return { ok: true, args: { command: { kind: "help" } } };
3313
- if (arg === "--cwd" || arg === "--server") {
3314
- const taken = takeValue(argv, i, arg);
3315
- if ("error" in taken)
3316
- return { ok: false, error: taken.error };
3317
- if (arg === "--cwd")
3318
- cwd = taken.value;
3319
- else
3320
- server = taken.value;
3321
- i = taken.next;
3520
+ const global = takeGlobalCliOption(argv, i, { allowServer: true });
3521
+ if (global.kind === "error")
3522
+ return { ok: false, error: global.error };
3523
+ if (global.kind === "cwd") {
3524
+ cwd = global.value;
3525
+ i = global.next;
3526
+ } else if (global.kind === "server") {
3527
+ server = global.value;
3528
+ i = global.next;
3322
3529
  } else if (valueFlags.has(arg)) {
3323
3530
  const taken = takeValue(argv, i, arg);
3324
3531
  if ("error" in taken)
@@ -3363,13 +3570,9 @@ function parseAnnotateArgs(argv) {
3363
3570
  if (!line)
3364
3571
  return { ok: false, error: "--line must be <n> or <n>-<m>" };
3365
3572
  }
3366
- const body = options.get("--body");
3367
- const bodyFile = options.get("--body-file");
3368
- if (body !== undefined && bodyFile !== undefined)
3369
- return { ok: false, error: "use either --body or --body-file" };
3370
- const position = parsePosition(options.get("--position"));
3371
- if (Number.isNaN(position))
3372
- return { ok: false, error: "--position must be a positive integer" };
3573
+ const commonOptions = parseAnnotationAddOptions(options);
3574
+ if (commonOptions.ok === false)
3575
+ return { ok: false, error: commonOptions.error };
3373
3576
  return {
3374
3577
  ok: true,
3375
3578
  args: {
@@ -3379,14 +3582,7 @@ function parseAnnotateArgs(argv) {
3379
3582
  line,
3380
3583
  from: options.get("--from"),
3381
3584
  to: options.get("--to"),
3382
- title: options.get("--title"),
3383
- session: options.get("--session"),
3384
- sessionTitle: options.get("--session-title"),
3385
- body,
3386
- bodyFile,
3387
- before: options.get("--before"),
3388
- after: options.get("--after"),
3389
- position
3585
+ ...commonOptions.options
3390
3586
  },
3391
3587
  cwd,
3392
3588
  server
@@ -3396,13 +3592,9 @@ function parseAnnotateArgs(argv) {
3396
3592
  if (subcommand === "add-db") {
3397
3593
  if (!options.get("--db"))
3398
3594
  return { ok: false, error: "add-db requires --db <id>" };
3399
- const body = options.get("--body");
3400
- const bodyFile = options.get("--body-file");
3401
- if (body !== undefined && bodyFile !== undefined)
3402
- return { ok: false, error: "use either --body or --body-file" };
3403
- const position = parsePosition(options.get("--position"));
3404
- if (Number.isNaN(position))
3405
- return { ok: false, error: "--position must be a positive integer" };
3595
+ const commonOptions = parseAnnotationAddOptions(options);
3596
+ if (commonOptions.ok === false)
3597
+ return { ok: false, error: commonOptions.error };
3406
3598
  const rawTab = options.get("--tab");
3407
3599
  const tab = normalizeDatabaseTab(rawTab);
3408
3600
  if (rawTab !== undefined && tab === undefined)
@@ -3477,14 +3669,7 @@ function parseAnnotateArgs(argv) {
3477
3669
  searchTerm: options.get("--search-term"),
3478
3670
  includeNonText: flags.has("--include-non-text") || undefined,
3479
3671
  searchAutoRun: flags.has("--run-search"),
3480
- title: options.get("--title"),
3481
- session: options.get("--session"),
3482
- sessionTitle: options.get("--session-title"),
3483
- body,
3484
- bodyFile,
3485
- before: options.get("--before"),
3486
- after: options.get("--after"),
3487
- position
3672
+ ...commonOptions.options
3488
3673
  },
3489
3674
  cwd,
3490
3675
  server
@@ -3597,6 +3782,14 @@ function printList(state) {
3597
3782
  });
3598
3783
  }
3599
3784
  }
3785
+ function printAddedAnnotation(result, location, serverUrl) {
3786
+ const sessionTitle = result.session_title || "Untitled session";
3787
+ if (result.created_session) {
3788
+ console.error(`created new annotation session ${result.session_id} (${sessionTitle})`);
3789
+ }
3790
+ console.log(`annotated ${location} ` + `[${result.entry.id}] in session ${result.session_id} (${sessionTitle})`);
3791
+ console.error(`view annotations at ${serverUrl}/ with the code annotations panel`);
3792
+ }
3600
3793
  async function annotationBodyFromCommand(command) {
3601
3794
  let body = command.body;
3602
3795
  if (body === undefined && command.bodyFile !== undefined) {
@@ -3657,11 +3850,7 @@ async function runAnnotateCli(argv) {
3657
3850
  after_id: command.after,
3658
3851
  position: command.position
3659
3852
  });
3660
- if (result.created_session) {
3661
- console.error(`created new annotation session ${result.session_id} (${result.session_title || "Untitled session"})`);
3662
- }
3663
- console.log(`annotated ${result.entry.path}${formatLine(result.entry.line)} ` + `[${result.entry.id}] in session ${result.session_id} (${result.session_title || "Untitled session"})`);
3664
- console.error(`view annotations at ${serverUrl}/ with the code annotations panel`);
3853
+ printAddedAnnotation(result, `${result.entry.path}${formatLine(result.entry.line)}`, serverUrl);
3665
3854
  return;
3666
3855
  }
3667
3856
  if (command.kind === "add-db") {
@@ -3711,11 +3900,7 @@ async function runAnnotateCli(argv) {
3711
3900
  after_id: command.after,
3712
3901
  position: command.position
3713
3902
  });
3714
- if (result.created_session) {
3715
- console.error(`created new annotation session ${result.session_id} (${result.session_title || "Untitled session"})`);
3716
- }
3717
- console.log(`annotated ${result.entry.path} ` + `[${result.entry.id}] in session ${result.session_id} (${result.session_title || "Untitled session"})`);
3718
- console.error(`view annotations at ${serverUrl}/ with the code annotations panel`);
3903
+ printAddedAnnotation(result, result.entry.path, serverUrl);
3719
3904
  return;
3720
3905
  }
3721
3906
  if (command.kind === "list") {
@@ -4274,25 +4459,19 @@ function parseFileArgs(argv) {
4274
4459
  for (let i = 0;i < argv.length; i++) {
4275
4460
  const arg = argv[i];
4276
4461
  if (arg === "--help" || arg === "-h") {
4277
- return { ok: true, args: { command: { kind: "help" } } };
4278
- }
4279
- if (arg === "--cwd") {
4280
- const taken = takeValue(argv, i, arg);
4281
- if ("error" in taken)
4282
- return { ok: false, error: taken.error };
4283
- cwd = taken.value;
4284
- i = taken.next;
4285
- } else if (arg === "--bin") {
4286
- const taken = takeValue(argv, i, arg);
4287
- if ("error" in taken)
4288
- return { ok: false, error: taken.error };
4289
- const parsed = parseExternalCommandOverride(taken.value, "--bin", [
4290
- "git"
4291
- ]);
4292
- if (parsed.ok === false)
4293
- return { ok: false, error: parsed.error };
4294
- commandOverrides.push(parsed.override);
4295
- i = taken.next;
4462
+ return { ok: true, args: { command: { kind: "help" } } };
4463
+ }
4464
+ const global = takeGlobalCliOption(argv, i, {
4465
+ allowedCommands: ["git"]
4466
+ });
4467
+ if (global.kind === "error")
4468
+ return { ok: false, error: global.error };
4469
+ if (global.kind === "cwd") {
4470
+ cwd = global.value;
4471
+ i = global.next;
4472
+ } else if (global.kind === "command-override") {
4473
+ commandOverrides.push(global.override);
4474
+ i = global.next;
4296
4475
  } else if (VALUE_FLAGS.has(arg)) {
4297
4476
  const taken = takeValue(argv, i, arg);
4298
4477
  if ("error" in taken)
@@ -5534,26 +5713,21 @@ function parseJournalArgs(argv) {
5534
5713
  const arg = argv[i];
5535
5714
  if (arg === "--help" || arg === "-h")
5536
5715
  return { ok: true, args: { command: { kind: "help" }, dryRun: false } };
5537
- if (arg === "--cwd" || arg === "--server") {
5538
- const taken = takeValue(argv, i, arg);
5539
- if ("error" in taken)
5540
- return { ok: false, error: taken.error };
5541
- if (arg === "--cwd")
5542
- cwd = taken.value;
5543
- else
5544
- server = taken.value;
5545
- i = taken.next;
5546
- } else if (arg === "--bin") {
5547
- const taken = takeValue(argv, i, arg);
5548
- if ("error" in taken)
5549
- return { ok: false, error: taken.error };
5550
- const parsed = parseExternalCommandOverride(taken.value, "--bin", [
5551
- "gh"
5552
- ]);
5553
- if (parsed.ok === false)
5554
- return { ok: false, error: parsed.error };
5555
- commandOverrides.push(parsed.override);
5556
- i = taken.next;
5716
+ const global = takeGlobalCliOption(argv, i, {
5717
+ allowServer: true,
5718
+ allowedCommands: ["gh"]
5719
+ });
5720
+ if (global.kind === "error")
5721
+ return { ok: false, error: global.error };
5722
+ if (global.kind === "cwd") {
5723
+ cwd = global.value;
5724
+ i = global.next;
5725
+ } else if (global.kind === "server") {
5726
+ server = global.value;
5727
+ i = global.next;
5728
+ } else if (global.kind === "command-override") {
5729
+ commandOverrides.push(global.override);
5730
+ i = global.next;
5557
5731
  } else if (valueFlags.has(arg)) {
5558
5732
  const taken = takeValue(argv, i, arg);
5559
5733
  if ("error" in taken)
@@ -6338,107 +6512,6 @@ var init_journal_cli = __esm(() => {
6338
6512
  init_github_issues();
6339
6513
  });
6340
6514
 
6341
- // web-src/core/routes.ts
6342
- function assertNever(value) {
6343
- throw new Error(`unhandled route: ${JSON.stringify(value)}`);
6344
- }
6345
- function formatLineTarget(line) {
6346
- return typeof line === "number" ? String(line) : `${line.start}-${line.end}`;
6347
- }
6348
- function buildRoute(route) {
6349
- switch (route.screen) {
6350
- case "repo": {
6351
- const params = new URLSearchParams;
6352
- if (route.ref && route.ref !== "worktree")
6353
- params.set("ref", route.ref);
6354
- if (route.path)
6355
- params.set("path", route.path);
6356
- const qs = params.toString();
6357
- return `/${qs ? `?${qs}` : ""}`;
6358
- }
6359
- case "file":
6360
- if (route.view === "blob") {
6361
- return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=blob" + (route.preview ? "&preview=1" : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
6362
- }
6363
- if (route.view === "blame") {
6364
- const ref = route.ref || "worktree";
6365
- return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(ref) + "&view=blame" + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
6366
- }
6367
- if (route.view === "history") {
6368
- return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=history" + (route.commit ? `&commit=${encodeURIComponent(route.commit)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
6369
- }
6370
- return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
6371
- case "diff":
6372
- return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.path ? `&path=${encodeURIComponent(route.path)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
6373
- case "help": {
6374
- const params = new URLSearchParams;
6375
- if (route.lang && route.lang !== "en")
6376
- params.set("lang", route.lang);
6377
- if (route.section && route.section !== "overview")
6378
- params.set("section", route.section);
6379
- const qs = params.toString();
6380
- return `/help${qs ? `?${qs}` : ""}`;
6381
- }
6382
- case "history": {
6383
- const params = new URLSearchParams;
6384
- if (route.ref && route.ref !== "HEAD")
6385
- params.set("ref", route.ref);
6386
- if (route.commit)
6387
- params.set("commit", route.commit);
6388
- const qs = params.toString();
6389
- return `/history${qs ? `?${qs}` : ""}`;
6390
- }
6391
- case "journal": {
6392
- const params = new URLSearchParams;
6393
- if (route.tab && route.tab !== "journal")
6394
- params.set("tab", route.tab);
6395
- if (route.date)
6396
- params.set("date", route.date);
6397
- if (route.label)
6398
- params.set("label", route.label);
6399
- if (route.task)
6400
- params.set("task", route.task);
6401
- const qs = params.toString();
6402
- return `/journal${qs ? `?${qs}` : ""}`;
6403
- }
6404
- case "database": {
6405
- const params = new URLSearchParams;
6406
- if (route.db)
6407
- params.set("db", route.db);
6408
- if (route.schema)
6409
- params.set("schema", route.schema);
6410
- if (route.table)
6411
- params.set("table", route.table);
6412
- if (route.tab)
6413
- params.set("tab", route.tab);
6414
- if (route.diffBefore)
6415
- params.set("diffBefore", route.diffBefore);
6416
- if (route.diffAfter)
6417
- params.set("diffAfter", route.diffAfter);
6418
- const qs = params.toString();
6419
- return `/database${qs ? `?${qs}` : ""}`;
6420
- }
6421
- case "unknown":
6422
- return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
6423
- default:
6424
- return assertNever(route);
6425
- }
6426
- }
6427
- var SPA_PATHS, APP_ENTRY_PATHS;
6428
- var init_routes = __esm(() => {
6429
- SPA_PATHS = [
6430
- "/todif",
6431
- "/todiff",
6432
- "/file",
6433
- "/help",
6434
- "/history",
6435
- "/journal",
6436
- "/database",
6437
- "/doctor"
6438
- ];
6439
- APP_ENTRY_PATHS = ["/", "/index.html"];
6440
- });
6441
-
6442
6515
  // web-src/server/query-cli.ts
6443
6516
  var exports_query_cli = {};
6444
6517
  __export(exports_query_cli, {
@@ -6481,26 +6554,21 @@ function parseQueryArgs(argv) {
6481
6554
  const arg = argv[i];
6482
6555
  if (arg === "--help" || arg === "-h")
6483
6556
  return { ok: true, args: { command: { kind: "help" } } };
6484
- if (arg === "--cwd" || arg === "--server") {
6485
- const taken = takeValue(argv, i, arg);
6486
- if ("error" in taken)
6487
- return { ok: false, error: taken.error };
6488
- if (arg === "--cwd")
6489
- cwd = taken.value;
6490
- else
6491
- server = taken.value;
6492
- i = taken.next;
6493
- } else if (arg === "--bin") {
6494
- const taken = takeValue(argv, i, arg);
6495
- if ("error" in taken)
6496
- return { ok: false, error: taken.error };
6497
- const parsed = parseExternalCommandOverride(taken.value, "--bin", [
6498
- "git"
6499
- ]);
6500
- if (parsed.ok === false)
6501
- return { ok: false, error: parsed.error };
6502
- commandOverrides.push(parsed.override);
6503
- i = taken.next;
6557
+ const global = takeGlobalCliOption(argv, i, {
6558
+ allowServer: true,
6559
+ allowedCommands: ["git"]
6560
+ });
6561
+ if (global.kind === "error")
6562
+ return { ok: false, error: global.error };
6563
+ if (global.kind === "cwd") {
6564
+ cwd = global.value;
6565
+ i = global.next;
6566
+ } else if (global.kind === "server") {
6567
+ server = global.value;
6568
+ i = global.next;
6569
+ } else if (global.kind === "command-override") {
6570
+ commandOverrides.push(global.override);
6571
+ i = global.next;
6504
6572
  } else if (VALUE_FLAGS2.has(arg)) {
6505
6573
  const taken = takeValue(argv, i, arg);
6506
6574
  if ("error" in taken)
@@ -9139,6 +9207,18 @@ __export(exports_search_cli, {
9139
9207
  SEARCH_AGENT_HELP: () => SEARCH_AGENT_HELP,
9140
9208
  FILE_NAME_SEARCH_DEFAULT_MAX: () => FILE_NAME_SEARCH_DEFAULT_MAX
9141
9209
  });
9210
+ function parseSearchMax(raw, hardCap) {
9211
+ if (raw === undefined)
9212
+ return { value: undefined };
9213
+ const value = Number(raw);
9214
+ if (!Number.isInteger(value) || value <= 0) {
9215
+ return { error: `--max must be a positive integer (got ${raw})` };
9216
+ }
9217
+ if (value > hardCap) {
9218
+ return { error: `--max must be <= ${hardCap} (got ${value})` };
9219
+ }
9220
+ return { value };
9221
+ }
9142
9222
  function parseSearchArgs(argv) {
9143
9223
  const rest = [];
9144
9224
  let cwd;
@@ -9152,26 +9232,21 @@ function parseSearchArgs(argv) {
9152
9232
  if (arg === "--help" || arg === "-h") {
9153
9233
  return { ok: true, args: { command: { kind: "help" } } };
9154
9234
  }
9155
- if (arg === "--cwd" || arg === "--server") {
9156
- const taken = takeValue(argv, i, arg);
9157
- if ("error" in taken)
9158
- return { ok: false, error: taken.error };
9159
- if (arg === "--cwd")
9160
- cwd = taken.value;
9161
- else
9162
- server = taken.value;
9163
- i = taken.next;
9164
- } else if (arg === "--bin") {
9165
- const taken = takeValue(argv, i, arg);
9166
- if ("error" in taken)
9167
- return { ok: false, error: taken.error };
9168
- const parsed = parseExternalCommandOverride(taken.value, "--bin", [
9169
- "git"
9170
- ]);
9171
- if (parsed.ok === false)
9172
- return { ok: false, error: parsed.error };
9173
- commandOverrides.push(parsed.override);
9174
- i = taken.next;
9235
+ const global = takeGlobalCliOption(argv, i, {
9236
+ allowServer: true,
9237
+ allowedCommands: ["git"]
9238
+ });
9239
+ if (global.kind === "error")
9240
+ return { ok: false, error: global.error };
9241
+ if (global.kind === "cwd") {
9242
+ cwd = global.value;
9243
+ i = global.next;
9244
+ } else if (global.kind === "server") {
9245
+ server = global.value;
9246
+ i = global.next;
9247
+ } else if (global.kind === "command-override") {
9248
+ commandOverrides.push(global.override);
9249
+ i = global.next;
9175
9250
  } else if (REPEATABLE_VALUE_FLAGS.has(arg)) {
9176
9251
  const taken = takeValue(argv, i, arg);
9177
9252
  if ("error" in taken)
@@ -9230,24 +9305,10 @@ function parseSearchArgs(argv) {
9230
9305
  error: "search files does not accept --path"
9231
9306
  };
9232
9307
  }
9233
- const maxRaw2 = options.get("--max");
9234
- let max2 = FILE_NAME_SEARCH_DEFAULT_MAX;
9235
- if (maxRaw2 !== undefined) {
9236
- const n = Number(maxRaw2);
9237
- if (!Number.isInteger(n) || n <= 0) {
9238
- return {
9239
- ok: false,
9240
- error: `--max must be a positive integer (got ${maxRaw2})`
9241
- };
9242
- }
9243
- if (n > FILE_SEARCH_ABSOLUTE_MAX) {
9244
- return {
9245
- ok: false,
9246
- error: `--max must be <= ${FILE_SEARCH_ABSOLUTE_MAX} (got ${n})`
9247
- };
9248
- }
9249
- max2 = n;
9250
- }
9308
+ const parsedMax2 = parseSearchMax(options.get("--max"), FILE_SEARCH_ABSOLUTE_MAX);
9309
+ if ("error" in parsedMax2)
9310
+ return { ok: false, error: parsedMax2.error };
9311
+ const max = parsedMax2.value ?? FILE_NAME_SEARCH_DEFAULT_MAX;
9251
9312
  return {
9252
9313
  ok: true,
9253
9314
  args: {
@@ -9255,7 +9316,7 @@ function parseSearchArgs(argv) {
9255
9316
  kind: "files",
9256
9317
  term,
9257
9318
  ref: options.get("--ref"),
9258
- max: max2,
9319
+ max,
9259
9320
  json: flags.has("--json")
9260
9321
  },
9261
9322
  cwd,
@@ -9264,24 +9325,9 @@ function parseSearchArgs(argv) {
9264
9325
  }
9265
9326
  };
9266
9327
  }
9267
- const maxRaw = options.get("--max");
9268
- let max;
9269
- if (maxRaw !== undefined) {
9270
- const n = Number(maxRaw);
9271
- if (!Number.isInteger(n) || n <= 0) {
9272
- return {
9273
- ok: false,
9274
- error: `--max must be a positive integer (got ${maxRaw})`
9275
- };
9276
- }
9277
- if (n > GREP_ABSOLUTE_MAX) {
9278
- return {
9279
- ok: false,
9280
- error: `--max must be <= ${GREP_ABSOLUTE_MAX} (got ${n})`
9281
- };
9282
- }
9283
- max = n;
9284
- }
9328
+ const parsedMax = parseSearchMax(options.get("--max"), GREP_ABSOLUTE_MAX);
9329
+ if ("error" in parsedMax)
9330
+ return { ok: false, error: parsedMax.error };
9285
9331
  return {
9286
9332
  ok: true,
9287
9333
  args: {
@@ -9291,7 +9337,7 @@ function parseSearchArgs(argv) {
9291
9337
  ref: options.get("--ref"),
9292
9338
  paths,
9293
9339
  regex: flags.has("--regex"),
9294
- max,
9340
+ max: parsedMax.value,
9295
9341
  json: flags.has("--json")
9296
9342
  },
9297
9343
  cwd,
@@ -9843,23 +9889,17 @@ function parseStatusArgs(argv) {
9843
9889
  if (arg === "--help" || arg === "-h") {
9844
9890
  return { ok: true, args: { command: { kind: "help" } } };
9845
9891
  }
9846
- if (arg === "--cwd") {
9847
- const taken = takeValue(argv, i, arg);
9848
- if ("error" in taken)
9849
- return { ok: false, error: taken.error };
9850
- cwd = taken.value;
9851
- i = taken.next;
9852
- } else if (arg === "--bin") {
9853
- const taken = takeValue(argv, i, arg);
9854
- if ("error" in taken)
9855
- return { ok: false, error: taken.error };
9856
- const parsed = parseExternalCommandOverride(taken.value, "--bin", [
9857
- "git"
9858
- ]);
9859
- if (parsed.ok === false)
9860
- return { ok: false, error: parsed.error };
9861
- commandOverrides.push(parsed.override);
9862
- i = taken.next;
9892
+ const global = takeGlobalCliOption(argv, i, {
9893
+ allowedCommands: ["git"]
9894
+ });
9895
+ if (global.kind === "error")
9896
+ return { ok: false, error: global.error };
9897
+ if (global.kind === "cwd") {
9898
+ cwd = global.value;
9899
+ i = global.next;
9900
+ } else if (global.kind === "command-override") {
9901
+ commandOverrides.push(global.override);
9902
+ i = global.next;
9863
9903
  } else if (VALUE_FLAGS4.has(arg)) {
9864
9904
  const taken = takeValue(argv, i, arg);
9865
9905
  if ("error" in taken)
@@ -10421,6 +10461,9 @@ function placeValue(coerced, kind, useParams, params) {
10421
10461
  function coerceCell(cell, columnType) {
10422
10462
  return coerceDbValue(cell.value, columnType);
10423
10463
  }
10464
+ function formatWriteComparisons(cells, columnTypes, kind, useParams, params, separator) {
10465
+ return cells.map((cell) => `${sanitizeIdentifier(cell.column, kind)} = ${placeValue(coerceCell(cell, columnTypes.get(cell.column) ?? "TEXT"), kind, useParams, params)}`).join(separator);
10466
+ }
10424
10467
  function buildInsertSql(table, cells, columnTypes, kind) {
10425
10468
  if (cells.length === 0) {
10426
10469
  throw new Error("insert requires at least one column value");
@@ -10441,8 +10484,8 @@ function buildUpdateSql(table, set, pk, columnTypes, kind) {
10441
10484
  }
10442
10485
  const useParams = useParamsFor(kind);
10443
10486
  const params = [];
10444
- const setSql = set.map((c) => `${sanitizeIdentifier(c.column, kind)} = ${placeValue(coerceCell(c, columnTypes.get(c.column) ?? "TEXT"), kind, useParams, params)}`).join(", ");
10445
- const whereSql = pk.map((c) => `${sanitizeIdentifier(c.column, kind)} = ${placeValue(coerceCell(c, columnTypes.get(c.column) ?? "TEXT"), kind, useParams, params)}`).join(" AND ");
10487
+ const setSql = formatWriteComparisons(set, columnTypes, kind, useParams, params, ", ");
10488
+ const whereSql = formatWriteComparisons(pk, columnTypes, kind, useParams, params, " AND ");
10446
10489
  const sql = `UPDATE ${sanitizeIdentifier(table, kind)} SET ${setSql} WHERE ${whereSql}`;
10447
10490
  return { sql, params };
10448
10491
  }
@@ -10452,7 +10495,7 @@ function buildDeleteSql(table, pk, columnTypes, kind) {
10452
10495
  }
10453
10496
  const useParams = useParamsFor(kind);
10454
10497
  const params = [];
10455
- const whereSql = pk.map((c) => `${sanitizeIdentifier(c.column, kind)} = ${placeValue(coerceCell(c, columnTypes.get(c.column) ?? "TEXT"), kind, useParams, params)}`).join(" AND ");
10498
+ const whereSql = formatWriteComparisons(pk, columnTypes, kind, useParams, params, " AND ");
10456
10499
  const sql = `DELETE FROM ${sanitizeIdentifier(table, kind)} WHERE ${whereSql}`;
10457
10500
  return { sql, params };
10458
10501
  }
@@ -15563,23 +15606,6 @@ var init_discovery = __esm(() => {
15563
15606
  supabaseDiscoveryCache = new Map;
15564
15607
  });
15565
15608
 
15566
- // web-src/core/id.ts
15567
- function bytesToHex(bytes) {
15568
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
15569
- }
15570
- function makeId(prefix) {
15571
- const cryptoApi = globalThis.crypto;
15572
- if (typeof cryptoApi?.randomUUID === "function") {
15573
- return `${prefix}-${cryptoApi.randomUUID().replace(/-/g, "").slice(0, 16)}`;
15574
- }
15575
- if (typeof cryptoApi?.getRandomValues === "function") {
15576
- const bytes = new Uint8Array(8);
15577
- cryptoApi.getRandomValues(bytes);
15578
- return `${prefix}-${bytesToHex(bytes)}`;
15579
- }
15580
- return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
15581
- }
15582
-
15583
15609
  // web-src/server/worktree-watcher.ts
15584
15610
  import {
15585
15611
  lstatSync as lstatSync3,
@@ -15850,15 +15876,17 @@ function optionalString(value, maxLen) {
15850
15876
  function optionalBoolean(value) {
15851
15877
  return typeof value === "boolean" ? value : undefined;
15852
15878
  }
15853
- function optionalNumber(value, min, max) {
15879
+ function optionalFiniteNumber(value, min, max, round) {
15854
15880
  if (typeof value !== "number" || !Number.isFinite(value))
15855
15881
  return;
15856
- return Math.max(min, Math.min(max, Math.round(value)));
15882
+ const normalized = round ? Math.round(value) : value;
15883
+ return Math.max(min, Math.min(max, normalized));
15884
+ }
15885
+ function optionalNumber(value, min, max) {
15886
+ return optionalFiniteNumber(value, min, max, true);
15857
15887
  }
15858
15888
  function optionalFloat(value, min, max) {
15859
- if (typeof value !== "number" || !Number.isFinite(value))
15860
- return;
15861
- return Math.max(min, Math.min(max, value));
15889
+ return optionalFiniteNumber(value, min, max, false);
15862
15890
  }
15863
15891
  function optionalFontSize(value) {
15864
15892
  return value === "compact" || value === "regular" || value === "large" || value === "xlarge" ? value : undefined;
@@ -16690,6 +16718,25 @@ function asStringArray(value) {
16690
16718
  function asNumber(value) {
16691
16719
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
16692
16720
  }
16721
+ function asDynamoDbItemsResult(raw) {
16722
+ return {
16723
+ items: Array.isArray(raw.Items) ? raw.Items : [],
16724
+ count: asNumber(raw.Count),
16725
+ scannedCount: asNumber(raw.ScannedCount),
16726
+ ...raw.LastEvaluatedKey ? { lastEvaluatedKey: asObject(raw.LastEvaluatedKey) } : {}
16727
+ };
16728
+ }
16729
+ function dynamoDbItemsRequestFields(opts) {
16730
+ return {
16731
+ ...opts.limit ? { Limit: Math.min(1000, Math.max(1, opts.limit)) } : {},
16732
+ ...opts.exclusiveStartKey ? { ExclusiveStartKey: opts.exclusiveStartKey } : {},
16733
+ ...opts.indexName ? { IndexName: opts.indexName } : {},
16734
+ ...opts.projectionExpression ? { ProjectionExpression: opts.projectionExpression } : {},
16735
+ ...opts.filterExpression ? { FilterExpression: opts.filterExpression } : {},
16736
+ ...opts.expressionAttributeNames ? { ExpressionAttributeNames: opts.expressionAttributeNames } : {},
16737
+ ...opts.expressionAttributeValues ? { ExpressionAttributeValues: opts.expressionAttributeValues } : {}
16738
+ };
16739
+ }
16693
16740
  function createDynamoDbAdapter(config) {
16694
16741
  async function signedJsonRequest(action, body, signal, deadline = createDynamoDbTransportDeadline(config)) {
16695
16742
  const requestBody = JSON.stringify(body);
@@ -16772,41 +16819,19 @@ function createDynamoDbAdapter(config) {
16772
16819
  assertTableName(opts.tableName);
16773
16820
  const raw = await signedJsonRequest("Scan", {
16774
16821
  TableName: opts.tableName,
16775
- ...opts.limit ? { Limit: Math.min(1000, Math.max(1, opts.limit)) } : {},
16776
- ...opts.exclusiveStartKey ? { ExclusiveStartKey: opts.exclusiveStartKey } : {},
16777
- ...opts.indexName ? { IndexName: opts.indexName } : {},
16778
- ...opts.projectionExpression ? { ProjectionExpression: opts.projectionExpression } : {},
16779
- ...opts.filterExpression ? { FilterExpression: opts.filterExpression } : {},
16780
- ...opts.expressionAttributeNames ? { ExpressionAttributeNames: opts.expressionAttributeNames } : {},
16781
- ...opts.expressionAttributeValues ? { ExpressionAttributeValues: opts.expressionAttributeValues } : {}
16822
+ ...dynamoDbItemsRequestFields(opts)
16782
16823
  }, opts.signal);
16783
- return {
16784
- items: Array.isArray(raw.Items) ? raw.Items : [],
16785
- count: asNumber(raw.Count),
16786
- scannedCount: asNumber(raw.ScannedCount),
16787
- ...raw.LastEvaluatedKey ? { lastEvaluatedKey: asObject(raw.LastEvaluatedKey) } : {}
16788
- };
16824
+ return asDynamoDbItemsResult(raw);
16789
16825
  }
16790
16826
  async function queryAsync(opts) {
16791
16827
  assertTableName(opts.tableName);
16792
16828
  const raw = await signedJsonRequest("Query", {
16793
16829
  TableName: opts.tableName,
16794
16830
  KeyConditionExpression: opts.keyConditionExpression,
16795
- ...opts.limit ? { Limit: Math.min(1000, Math.max(1, opts.limit)) } : {},
16796
- ...opts.exclusiveStartKey ? { ExclusiveStartKey: opts.exclusiveStartKey } : {},
16797
- ...opts.indexName ? { IndexName: opts.indexName } : {},
16798
- ...opts.projectionExpression ? { ProjectionExpression: opts.projectionExpression } : {},
16799
- ...opts.filterExpression ? { FilterExpression: opts.filterExpression } : {},
16800
- ...opts.expressionAttributeNames ? { ExpressionAttributeNames: opts.expressionAttributeNames } : {},
16801
- ...opts.expressionAttributeValues ? { ExpressionAttributeValues: opts.expressionAttributeValues } : {},
16831
+ ...dynamoDbItemsRequestFields(opts),
16802
16832
  ...opts.scanIndexForward !== undefined ? { ScanIndexForward: opts.scanIndexForward } : {}
16803
16833
  }, opts.signal);
16804
- return {
16805
- items: Array.isArray(raw.Items) ? raw.Items : [],
16806
- count: asNumber(raw.Count),
16807
- scannedCount: asNumber(raw.ScannedCount),
16808
- ...raw.LastEvaluatedKey ? { lastEvaluatedKey: asObject(raw.LastEvaluatedKey) } : {}
16809
- };
16834
+ return asDynamoDbItemsResult(raw);
16810
16835
  }
16811
16836
  async function getItemAsync(opts) {
16812
16837
  assertTableName(opts.tableName);
@@ -22497,29 +22522,20 @@ function parseDoctorCliArgs(argv) {
22497
22522
  json2 = true;
22498
22523
  continue;
22499
22524
  }
22500
- if (arg === "--cwd") {
22501
- const next = argv[++i];
22502
- if (!next)
22503
- return { kind: "error", message: "--cwd requires a value" };
22504
- cwd = next;
22525
+ const global = takeGlobalCliOption(argv, i, {
22526
+ allowedCommands: ["git", "docker", "gh"]
22527
+ });
22528
+ if (global.kind === "error") {
22529
+ return { kind: "error", message: global.error };
22530
+ }
22531
+ if (global.kind === "cwd") {
22532
+ cwd = global.value;
22533
+ i = global.next;
22505
22534
  continue;
22506
22535
  }
22507
- if (arg === "--bin") {
22508
- const next = argv[++i];
22509
- if (!next) {
22510
- return {
22511
- kind: "error",
22512
- message: "--bin requires <name>=<absolute-path>"
22513
- };
22514
- }
22515
- const parsed = parseExternalCommandOverride(next, "--bin", [
22516
- "git",
22517
- "docker",
22518
- "gh"
22519
- ]);
22520
- if (parsed.ok === false)
22521
- return { kind: "error", message: parsed.error };
22522
- commandOverrides.push(parsed.override);
22536
+ if (global.kind === "command-override") {
22537
+ commandOverrides.push(global.override);
22538
+ i = global.next;
22523
22539
  continue;
22524
22540
  }
22525
22541
  if (arg === "--port") {
@@ -22646,6 +22662,7 @@ Exit codes:
22646
22662
  `, STATUS_SYMBOL;
22647
22663
  var init_doctor_cli = __esm(() => {
22648
22664
  init_command_resolver();
22665
+ init_cli_helpers();
22649
22666
  init_doctor();
22650
22667
  init_git();
22651
22668
  STATUS_SYMBOL = {
@@ -22711,9 +22728,7 @@ function emptyJournalTaskState() {
22711
22728
  return { version: 1, tasks: [] };
22712
22729
  }
22713
22730
  function makeJournalId(prefix) {
22714
- const random = Math.random().toString(36).slice(2, 8);
22715
- const time = Date.now().toString(36);
22716
- return `${prefix}-${time}${random}`;
22731
+ return makeTimedId(prefix);
22717
22732
  }
22718
22733
  function optionalString4(value, maxLen) {
22719
22734
  if (typeof value !== "string")
@@ -22867,11 +22882,8 @@ async function updateDailyJournalState(root, updater) {
22867
22882
  async function updateJournalTaskState(root, updater) {
22868
22883
  return journalTaskStore.update(root, updater);
22869
22884
  }
22870
- function insertOptionCount2(input) {
22871
- return (input.before_id ? 1 : 0) + (input.after_id ? 1 : 0) + (input.position !== undefined ? 1 : 0);
22872
- }
22873
22885
  function taskInsertIndex(tasks, status, input) {
22874
- if (insertOptionCount2(input) > 1)
22886
+ if (orderedInsertOptionCount(input) > 1)
22875
22887
  return { ok: false, error: "use only one of before, after, or position" };
22876
22888
  if (input.before_id || input.after_id) {
22877
22889
  const anchorId = input.before_id || input.after_id || "";
@@ -23039,6 +23051,16 @@ function addJournalTask(state, input, now, makeId3 = makeJournalId) {
23039
23051
  tasks.splice(insertAt.index, 0, task);
23040
23052
  return { ok: true, state: { version: 1, tasks }, task };
23041
23053
  }
23054
+ function journalTaskResult(state, task) {
23055
+ return {
23056
+ ok: true,
23057
+ state: {
23058
+ version: 1,
23059
+ tasks: state.tasks.map((item) => item.id === task.id ? task : item)
23060
+ },
23061
+ task
23062
+ };
23063
+ }
23042
23064
  function updateJournalTask(state, id, patch, now) {
23043
23065
  const task = state.tasks.find((item) => item.id === id);
23044
23066
  if (!task)
@@ -23093,14 +23115,7 @@ function updateJournalTask(state, id, patch, now) {
23093
23115
  else
23094
23116
  delete next.journal_entry_id;
23095
23117
  }
23096
- return {
23097
- ok: true,
23098
- state: {
23099
- version: 1,
23100
- tasks: state.tasks.map((item) => item.id === id ? next : item)
23101
- },
23102
- task: next
23103
- };
23118
+ return journalTaskResult(state, next);
23104
23119
  }
23105
23120
  function moveJournalTask(state, id, input, now) {
23106
23121
  const source = state.tasks.find((task) => task.id === id);
@@ -23210,8 +23225,7 @@ function claimJournalTask(state, id, input, now) {
23210
23225
  if (!task)
23211
23226
  return { ok: false, error: "task not found" };
23212
23227
  const nowMs = Date.parse(now);
23213
- const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
23214
- if (activeClaim)
23228
+ if (taskClaimActive(task, nowMs))
23215
23229
  return { ok: false, error: "task is already claimed" };
23216
23230
  if (task.status !== "todo" && task.status !== "doing")
23217
23231
  return {
@@ -23222,12 +23236,9 @@ function claimJournalTask(state, id, input, now) {
23222
23236
  const wipLimit = input.wip_limit;
23223
23237
  if (wipLimit !== undefined && wipLimit > 0) {
23224
23238
  const activeDoing = state.tasks.filter((item) => {
23225
- if (item.status !== "doing" || !item.claim)
23226
- return false;
23227
- if (item.claim.by !== by)
23239
+ if (item.status !== "doing" || item.claim?.by !== by)
23228
23240
  return false;
23229
- const expires = Date.parse(item.claim.lease_expires_at);
23230
- return Number.isFinite(expires) && expires > nowMs;
23241
+ return taskClaimActive(item, nowMs);
23231
23242
  }).length;
23232
23243
  if (activeDoing >= wipLimit)
23233
23244
  return { ok: false, error: "WIP limit reached" };
@@ -23244,14 +23255,7 @@ function claimJournalTask(state, id, input, now) {
23244
23255
  lease_expires_at: leaseExpiresAt
23245
23256
  }
23246
23257
  };
23247
- return {
23248
- ok: true,
23249
- state: {
23250
- version: 1,
23251
- tasks: state.tasks.map((item) => item.id === id ? next : item)
23252
- },
23253
- task: next
23254
- };
23258
+ return journalTaskResult(state, next);
23255
23259
  }
23256
23260
  function completeJournalTask(state, id, input, now, makeId3 = makeJournalId) {
23257
23261
  const task = state.tasks.find((item) => item.id === id);
@@ -23260,8 +23264,7 @@ function completeJournalTask(state, id, input, now, makeId3 = makeJournalId) {
23260
23264
  if (task.status !== "doing")
23261
23265
  return { ok: false, error: "only doing tasks can be completed" };
23262
23266
  const nowMs = Date.parse(now);
23263
- const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
23264
- if (!activeClaim)
23267
+ if (!taskClaimActive(task, nowMs))
23265
23268
  return { ok: false, error: "task must be claimed before completion" };
23266
23269
  const by = optionalString4(input.by, 128);
23267
23270
  if (!by)
@@ -23291,14 +23294,7 @@ function completeJournalTask(state, id, input, now, makeId3 = makeJournalId) {
23291
23294
  ...notes.length ? { notes } : {}
23292
23295
  };
23293
23296
  delete next.claim;
23294
- return {
23295
- ok: true,
23296
- state: {
23297
- version: 1,
23298
- tasks: state.tasks.map((item) => item.id === id ? next : item)
23299
- },
23300
- task: next
23301
- };
23297
+ return journalTaskResult(state, next);
23302
23298
  }
23303
23299
  function deleteJournalTask(state, id) {
23304
23300
  const tasks = state.tasks.filter((task) => task.id !== id);