@workscribe/cli 1.13.0 → 1.14.1

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.
package/dist/index.js CHANGED
@@ -6,6 +6,12 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __commonJS = (cb, mod) => function __require() {
13
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
14
+ };
9
15
  var __copyProps = (to, from, except, desc) => {
10
16
  if (from && typeof from === "object" || typeof from === "function") {
11
17
  for (let key of __getOwnPropNames(from))
@@ -23,74 +29,42 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
29
  mod
24
30
  ));
25
31
 
26
- // src/index.ts
27
- var import_commander = require("commander");
28
-
29
- // src/utils/node-version.ts
30
- function detectVersionManager() {
31
- if (process.env.NVM_DIR) return "nvm";
32
- if (process.env.VOLTA_HOME) return "volta";
33
- if (process.env.FNM_DIR) return "fnm";
34
- if (process.env.MISE_DATA_DIR || process.env.MISE_SHELL) return "mise";
35
- if (process.env.ASDF_DIR || process.env.ASDF_DATA_DIR) return "asdf";
36
- if (process.env.NODENV_ROOT) return "nodenv";
37
- return null;
38
- }
39
- var UPGRADE_COMMANDS = {
40
- nvm: "nvm install 22 && nvm use 22",
41
- volta: "volta install node@22",
42
- fnm: "fnm install 22 && fnm use 22",
43
- mise: "mise use --global node@22",
44
- asdf: "asdf install nodejs 22 && asdf global nodejs 22",
45
- nodenv: "nodenv install 22 && nodenv global 22"
46
- };
47
- function assertNodeVersion(nodeVersion) {
48
- const major = Number(nodeVersion.split(".")[0]);
49
- if (major < 22) {
50
- const manager = detectVersionManager();
51
- const upgradeHint = manager ? `Upgrade with ${manager}:
52
- ${UPGRADE_COMMANDS[manager]}` : "Download the latest version from https://nodejs.org";
53
- process.stderr.write(`workscribe requires Node.js 22 or later (you have v${nodeVersion}).
54
-
55
- ${upgradeHint}
56
- `);
57
- process.exit(1);
58
- }
59
- }
60
-
61
- // src/capture/index.ts
62
- var import_fs10 = __toESM(require("fs"));
63
-
64
- // src/db/index.ts
65
- var import_node_sqlite = require("node:sqlite");
66
- var import_fs2 = __toESM(require("fs"));
67
-
68
32
  // src/utils/paths.ts
69
- var import_path = __toESM(require("path"));
70
- var import_os = __toESM(require("os"));
71
- var import_fs = __toESM(require("fs"));
72
- var LEGACY_DIR = import_path.default.join(import_os.default.homedir(), ".workscribe");
73
- var USE_XDG = !import_fs.default.existsSync(LEGACY_DIR);
74
- var xdgDataDir = USE_XDG ? import_path.default.join(process.env.XDG_DATA_HOME ?? import_path.default.join(import_os.default.homedir(), ".local", "share"), "workscribe") : LEGACY_DIR;
75
- var xdgConfigDir = USE_XDG ? import_path.default.join(process.env.XDG_CONFIG_HOME ?? import_path.default.join(import_os.default.homedir(), ".config"), "workscribe") : LEGACY_DIR;
76
- var xdgStateDir = USE_XDG ? import_path.default.join(process.env.XDG_STATE_HOME ?? import_path.default.join(import_os.default.homedir(), ".local", "state"), "workscribe") : LEGACY_DIR;
77
- var REQUIRED_DIRS = [.../* @__PURE__ */ new Set([xdgDataDir, xdgConfigDir, xdgStateDir, LEGACY_DIR])];
78
- var WORKSCRIBE_DIR = xdgDataDir;
79
- var DB_PATH = import_path.default.join(xdgDataDir, "workscribe.db");
80
- var CONFIG_PATH = import_path.default.join(xdgConfigDir, "config.json");
81
- var DEBUG_LOG_PATH = import_path.default.join(xdgStateDir, "debug.log");
82
- var UPDATE_CHECK_PATH = import_path.default.join(xdgStateDir, "update-check.json");
83
- var EXPORTS_DIR = import_path.default.join(xdgDataDir, "exports");
84
- var PAUSE_FLAG_PATH = import_path.default.join(xdgStateDir, "paused");
85
- var WEEKLY_CACHE_DIR = import_path.default.join(xdgDataDir, "weekly-cache");
86
- var PROFILES_DIR = import_path.default.join(xdgDataDir, "profiles");
87
- var ALERTS_PENDING_PATH = import_path.default.join(LEGACY_DIR, "alerts.pending");
33
+ var import_path, import_os, import_fs, LEGACY_DIR, USE_XDG, xdgDataDir, xdgConfigDir, xdgStateDir, REQUIRED_DIRS, WORKSCRIBE_DIR, DB_PATH, CONFIG_PATH, DEBUG_LOG_PATH, UPDATE_CHECK_PATH, EXPORTS_DIR, PAUSE_FLAG_PATH, WEEKLY_CACHE_DIR, PROFILES_DIR, ALERTS_PENDING_PATH;
34
+ var init_paths = __esm({
35
+ "src/utils/paths.ts"() {
36
+ "use strict";
37
+ import_path = __toESM(require("path"));
38
+ import_os = __toESM(require("os"));
39
+ import_fs = __toESM(require("fs"));
40
+ LEGACY_DIR = import_path.default.join(import_os.default.homedir(), ".workscribe");
41
+ USE_XDG = !import_fs.default.existsSync(LEGACY_DIR);
42
+ xdgDataDir = USE_XDG ? import_path.default.join(process.env.XDG_DATA_HOME ?? import_path.default.join(import_os.default.homedir(), ".local", "share"), "workscribe") : LEGACY_DIR;
43
+ xdgConfigDir = USE_XDG ? import_path.default.join(process.env.XDG_CONFIG_HOME ?? import_path.default.join(import_os.default.homedir(), ".config"), "workscribe") : LEGACY_DIR;
44
+ xdgStateDir = USE_XDG ? import_path.default.join(process.env.XDG_STATE_HOME ?? import_path.default.join(import_os.default.homedir(), ".local", "state"), "workscribe") : LEGACY_DIR;
45
+ REQUIRED_DIRS = [.../* @__PURE__ */ new Set([xdgDataDir, xdgConfigDir, xdgStateDir, LEGACY_DIR])];
46
+ WORKSCRIBE_DIR = xdgDataDir;
47
+ DB_PATH = import_path.default.join(xdgDataDir, "workscribe.db");
48
+ CONFIG_PATH = import_path.default.join(xdgConfigDir, "config.json");
49
+ DEBUG_LOG_PATH = import_path.default.join(xdgStateDir, "debug.log");
50
+ UPDATE_CHECK_PATH = import_path.default.join(xdgStateDir, "update-check.json");
51
+ EXPORTS_DIR = import_path.default.join(xdgDataDir, "exports");
52
+ PAUSE_FLAG_PATH = import_path.default.join(xdgStateDir, "paused");
53
+ WEEKLY_CACHE_DIR = import_path.default.join(xdgDataDir, "weekly-cache");
54
+ PROFILES_DIR = import_path.default.join(xdgDataDir, "profiles");
55
+ ALERTS_PENDING_PATH = import_path.default.join(LEGACY_DIR, "alerts.pending");
56
+ }
57
+ });
88
58
 
89
59
  // src/db/migrations/001_initial_schema.ts
90
- var initial_schema_default = {
91
- version: 1,
92
- description: "initial schema",
93
- sql: `
60
+ var initial_schema_default;
61
+ var init_initial_schema = __esm({
62
+ "src/db/migrations/001_initial_schema.ts"() {
63
+ "use strict";
64
+ initial_schema_default = {
65
+ version: 1,
66
+ description: "initial schema",
67
+ sql: `
94
68
  CREATE TABLE IF NOT EXISTS events (
95
69
  id INTEGER PRIMARY KEY AUTOINCREMENT,
96
70
  captured_at TEXT NOT NULL,
@@ -125,13 +99,19 @@ var initial_schema_default = {
125
99
  PRIMARY KEY (session_id, event_id)
126
100
  );
127
101
  `
128
- };
102
+ };
103
+ }
104
+ });
129
105
 
130
106
  // src/db/migrations/002_semantic_categories.ts
131
- var migration = {
132
- version: 2,
133
- description: "Rename granular categories to 13 universal semantic categories",
134
- sql: `
107
+ var migration, semantic_categories_default;
108
+ var init_semantic_categories = __esm({
109
+ "src/db/migrations/002_semantic_categories.ts"() {
110
+ "use strict";
111
+ migration = {
112
+ version: 2,
113
+ description: "Rename granular categories to 13 universal semantic categories",
114
+ sql: `
135
115
  UPDATE events SET category = 'shipping' WHERE category IN ('code_commit','code_push','code_merge');
136
116
  UPDATE events SET category = 'versioning' WHERE category IN ('code_pull','branch_switch','code_stash','git_other');
137
117
  UPDATE events SET category = 'building' WHERE category = 'build';
@@ -143,14 +123,20 @@ var migration = {
143
123
  UPDATE events SET category = 'files' WHERE category IN ('file_ops','environment');
144
124
  UPDATE events SET category = 'exploring' WHERE category IN ('code_search','code_read');
145
125
  `
146
- };
147
- var semantic_categories_default = migration;
126
+ };
127
+ semantic_categories_default = migration;
128
+ }
129
+ });
148
130
 
149
131
  // src/db/migrations/003_alerts.ts
150
- var migration2 = {
151
- version: 3,
152
- description: "Add alerts table for AI session secret detection",
153
- sql: `
132
+ var migration2, alerts_default;
133
+ var init_alerts = __esm({
134
+ "src/db/migrations/003_alerts.ts"() {
135
+ "use strict";
136
+ migration2 = {
137
+ version: 3,
138
+ description: "Add alerts table for AI session secret detection",
139
+ sql: `
154
140
  CREATE TABLE IF NOT EXISTS alerts (
155
141
  id INTEGER PRIMARY KEY AUTOINCREMENT,
156
142
  created_at INTEGER NOT NULL,
@@ -161,26 +147,43 @@ var migration2 = {
161
147
  );
162
148
  CREATE INDEX IF NOT EXISTS alerts_cleared ON alerts (cleared);
163
149
  `
164
- };
165
- var alerts_default = migration2;
150
+ };
151
+ alerts_default = migration2;
152
+ }
153
+ });
166
154
 
167
155
  // src/db/migrations/004_ide_columns.ts
168
- var migration3 = {
169
- version: 4,
170
- description: "Add source and file_path columns to events for IDE integration",
171
- sql: `
156
+ var migration3, ide_columns_default;
157
+ var init_ide_columns = __esm({
158
+ "src/db/migrations/004_ide_columns.ts"() {
159
+ "use strict";
160
+ migration3 = {
161
+ version: 4,
162
+ description: "Add source and file_path columns to events for IDE integration",
163
+ sql: `
172
164
  ALTER TABLE events ADD COLUMN source TEXT;
173
165
  ALTER TABLE events ADD COLUMN file_path TEXT;
174
166
  CREATE INDEX IF NOT EXISTS idx_events_file_path ON events (file_path, cwd);
175
167
  `
176
- };
177
- var ide_columns_default = migration3;
168
+ };
169
+ ide_columns_default = migration3;
170
+ }
171
+ });
178
172
 
179
173
  // src/db/migrations/index.ts
180
- var MIGRATIONS = [initial_schema_default, semantic_categories_default, alerts_default, ide_columns_default];
174
+ var MIGRATIONS;
175
+ var init_migrations = __esm({
176
+ "src/db/migrations/index.ts"() {
177
+ "use strict";
178
+ init_initial_schema();
179
+ init_semantic_categories();
180
+ init_alerts();
181
+ init_ide_columns();
182
+ MIGRATIONS = [initial_schema_default, semantic_categories_default, alerts_default, ide_columns_default];
183
+ }
184
+ });
181
185
 
182
186
  // src/db/index.ts
183
- var _db = null;
184
187
  function getDb() {
185
188
  if (_db) return _db;
186
189
  if (!import_fs2.default.existsSync(WORKSCRIBE_DIR)) {
@@ -386,36 +389,19 @@ function clearSessionsForDate(date) {
386
389
  ).run({ date });
387
390
  db.prepare("DELETE FROM sessions WHERE date = $date").run({ date });
388
391
  }
392
+ var import_node_sqlite, import_fs2, _db;
393
+ var init_db = __esm({
394
+ "src/db/index.ts"() {
395
+ "use strict";
396
+ import_node_sqlite = require("node:sqlite");
397
+ import_fs2 = __toESM(require("fs"));
398
+ init_paths();
399
+ init_migrations();
400
+ _db = null;
401
+ }
402
+ });
389
403
 
390
404
  // src/capture/redact.ts
391
- var BUILTIN_PATTERNS = [
392
- // AWS access keys
393
- /AKIA[0-9A-Z]{16}/g,
394
- // AWS secret access keys (40 char base64-ish after common env vars)
395
- /(?<=AWS_SECRET_ACCESS_KEY[=\s]+)\S+/gi,
396
- // GitHub tokens
397
- /ghp_[a-zA-Z0-9]{36}/g,
398
- /github_pat_[a-zA-Z0-9_]{82}/g,
399
- // GitLab tokens
400
- /glpat-[a-zA-Z0-9\-]{20}/g,
401
- // Generic JWTs (three base64url segments)
402
- /eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
403
- // Slack tokens and incoming webhook URLs
404
- /xox[baprs]-[a-zA-Z0-9\-]+/g,
405
- /https:\/\/hooks\.slack\.com\/services\/[A-Z0-9]+\/[A-Z0-9]+\/[a-zA-Z0-9]+/g,
406
- // Stripe keys
407
- /sk_(live|test)_[a-zA-Z0-9]{24,}/g,
408
- // Values after long-form secret flags (single-char flags like -t/-p/-k are too ambiguous)
409
- /(?<=--(password|passwd|secret|token|key|api[-_]?key|auth[-_]?token)\s+)\S+/gi,
410
- // export VAR=VALUE where VAR looks sensitive
411
- /(?<=export\s+(?:[A-Z_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL)[A-Z_]*)=)\S+/gi,
412
- // Inline assignment: VAR_WITH_SENSITIVE_KEYWORD=value cmd (e.g. API_KEY=secret npm start)
413
- // Lookbehind matches only the variable-name= prefix, so only the value is replaced
414
- /(?<=[A-Z_][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL)[A-Z0-9_]*=)\S+/gi,
415
- // Credentials embedded in connection string URLs (e.g. DATABASE_URL=postgres://user:pass@host/db)
416
- // Matches the password segment between :// user : password @ — preserves scheme and host
417
- /(?<=[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^:@\s/]+:)[^@\s]+(?=@)/g
418
- ];
419
405
  function redact(command, extraPatterns = []) {
420
406
  let result = command;
421
407
  const allPatterns = [...BUILTIN_PATTERNS];
@@ -431,20 +417,60 @@ function redact(command, extraPatterns = []) {
431
417
  }
432
418
  return result;
433
419
  }
420
+ var BUILTIN_PATTERNS;
421
+ var init_redact = __esm({
422
+ "src/capture/redact.ts"() {
423
+ "use strict";
424
+ BUILTIN_PATTERNS = [
425
+ // AWS access keys
426
+ /AKIA[0-9A-Z]{16}/g,
427
+ // AWS secret access keys (40 char base64-ish after common env vars)
428
+ /(?<=AWS_SECRET_ACCESS_KEY[=\s]+)\S+/gi,
429
+ // GitHub tokens
430
+ /ghp_[a-zA-Z0-9]{36}/g,
431
+ /github_pat_[a-zA-Z0-9_]{82}/g,
432
+ // GitLab tokens
433
+ /glpat-[a-zA-Z0-9\-]{20}/g,
434
+ // Generic JWTs (three base64url segments)
435
+ /eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
436
+ // Slack tokens and incoming webhook URLs
437
+ /xox[baprs]-[a-zA-Z0-9\-]+/g,
438
+ /https:\/\/hooks\.slack\.com\/services\/[A-Z0-9]+\/[A-Z0-9]+\/[a-zA-Z0-9]+/g,
439
+ // Stripe keys
440
+ /sk_(live|test)_[a-zA-Z0-9]{24,}/g,
441
+ // Values after long-form secret flags (single-char flags like -t/-p/-k are too ambiguous)
442
+ /(?<=--(password|passwd|secret|token|key|api[-_]?key|auth[-_]?token)\s+)\S+/gi,
443
+ // export VAR=VALUE where VAR looks sensitive
444
+ /(?<=export\s+(?:[A-Z_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL)[A-Z_]*)=)\S+/gi,
445
+ // Inline assignment: VAR_WITH_SENSITIVE_KEYWORD=value cmd (e.g. API_KEY=secret npm start)
446
+ // Lookbehind matches only the variable-name= prefix, so only the value is replaced
447
+ /(?<=[A-Z_][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL)[A-Z0-9_]*=)\S+/gi,
448
+ // Credentials embedded in connection string URLs (e.g. DATABASE_URL=postgres://user:pass@host/db)
449
+ // Matches the password segment between :// user : password @ — preserves scheme and host
450
+ /(?<=[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^:@\s/]+:)[^@\s]+(?=@)/g
451
+ ];
452
+ }
453
+ });
434
454
 
435
455
  // src/capture/git.ts
436
- var import_child_process = require("child_process");
437
- var import_path2 = __toESM(require("path"));
438
- var import_fs3 = __toESM(require("fs"));
439
- function nameFromRemote(root) {
456
+ function parseOwnerAndRepo(url) {
457
+ const normalized = url.trim().replace(/^[^/]+:(?!\/\/)/, (match) => match.replace(":", "/"));
458
+ const segments = normalized.split("/").filter(Boolean);
459
+ if (segments.length < 2) return null;
460
+ const repo = segments[segments.length - 1].replace(/\.git$/, "");
461
+ const owner = segments[segments.length - 2];
462
+ return owner && repo ? { owner, repo } : null;
463
+ }
464
+ function nameFromRemote(root, qualifyWithOwner) {
440
465
  try {
441
466
  const url = (0, import_child_process.execSync)("git remote get-url origin", {
442
467
  cwd: root,
443
468
  timeout: 2e3,
444
469
  stdio: "pipe"
445
470
  }).toString().trim();
446
- const match = url.match(/\/([^/]+?)(?:\.git)?$/);
447
- return match ? match[1] : null;
471
+ const parsed = parseOwnerAndRepo(url);
472
+ if (!parsed) return null;
473
+ return qualifyWithOwner ? `${parsed.owner}/${parsed.repo}` : parsed.repo;
448
474
  } catch {
449
475
  return null;
450
476
  }
@@ -459,14 +485,14 @@ function nameFromPackageJson(root) {
459
485
  return null;
460
486
  }
461
487
  }
462
- function getGitContext(cwd, nameOverride) {
488
+ function getGitContext(cwd, nameOverride, qualifyRepoName = false) {
463
489
  try {
464
490
  const root = (0, import_child_process.execSync)("git rev-parse --show-toplevel", {
465
491
  cwd,
466
492
  timeout: 2e3,
467
493
  stdio: "pipe"
468
494
  }).toString().trim();
469
- const repoName = nameOverride ?? nameFromRemote(root) ?? nameFromPackageJson(root) ?? import_path2.default.basename(root);
495
+ const repoName = nameOverride ?? nameFromRemote(root, qualifyRepoName) ?? nameFromPackageJson(root) ?? import_path2.default.basename(root);
470
496
  let branch = null;
471
497
  try {
472
498
  branch = (0, import_child_process.execSync)("git branch --show-current", {
@@ -481,9 +507,17 @@ function getGitContext(cwd, nameOverride) {
481
507
  return { repoName: null, branch: null };
482
508
  }
483
509
  }
510
+ var import_child_process, import_path2, import_fs3;
511
+ var init_git = __esm({
512
+ "src/capture/git.ts"() {
513
+ "use strict";
514
+ import_child_process = require("child_process");
515
+ import_path2 = __toESM(require("path"));
516
+ import_fs3 = __toESM(require("fs"));
517
+ }
518
+ });
484
519
 
485
520
  // src/capture/git-snapshot.ts
486
- var import_child_process2 = require("child_process");
487
521
  function getCommitsSince(sha, cwd) {
488
522
  if (!sha) return [];
489
523
  try {
@@ -493,12 +527,15 @@ function getCommitsSince(sha, cwd) {
493
527
  return [];
494
528
  }
495
529
  }
530
+ var import_child_process2;
531
+ var init_git_snapshot = __esm({
532
+ "src/capture/git-snapshot.ts"() {
533
+ "use strict";
534
+ import_child_process2 = require("child_process");
535
+ }
536
+ });
496
537
 
497
538
  // src/capture/aider.ts
498
- var import_fs4 = __toESM(require("fs"));
499
- var import_path3 = __toESM(require("path"));
500
- var HISTORY_FILE = ".aider.chat.history.md";
501
- var PROMPT_MAX_LENGTH = 200;
502
539
  function readLastAiderPrompt(cwd) {
503
540
  try {
504
541
  const histPath = import_path3.default.join(cwd, HISTORY_FILE);
@@ -510,135 +547,135 @@ function readLastAiderPrompt(cwd) {
510
547
  return null;
511
548
  }
512
549
  }
513
-
514
- // src/config/registry.ts
515
- var import_fs6 = __toESM(require("fs"));
516
- var import_os2 = __toESM(require("os"));
517
- var import_path4 = __toESM(require("path"));
550
+ var import_fs4, import_path3, HISTORY_FILE, PROMPT_MAX_LENGTH;
551
+ var init_aider = __esm({
552
+ "src/capture/aider.ts"() {
553
+ "use strict";
554
+ import_fs4 = __toESM(require("fs"));
555
+ import_path3 = __toESM(require("path"));
556
+ HISTORY_FILE = ".aider.chat.history.md";
557
+ PROMPT_MAX_LENGTH = 200;
558
+ }
559
+ });
518
560
 
519
561
  // src/config/ai-tools.json
520
- var ai_tools_default = [
521
- {
522
- tool: "claude",
523
- displayName: "Claude Code",
524
- pattern: "^claude\\b",
525
- hook: "claude-code",
526
- contextSource: "hook",
527
- historyFormat: null,
528
- securityWarning: null
529
- },
530
- {
531
- tool: "aider",
532
- displayName: "Aider",
533
- pattern: "^aider\\b",
534
- hook: null,
535
- contextSource: "history-file",
536
- historyFormat: "markdown",
537
- securityWarning: null
538
- },
539
- {
540
- tool: "codex",
541
- displayName: "Codex CLI",
542
- pattern: "^codex\\b",
543
- hook: null,
544
- contextSource: "history-file",
545
- historyFormat: "jsonl",
546
- securityWarning: null
547
- },
548
- {
549
- tool: "gemini",
550
- displayName: "Gemini CLI",
551
- pattern: "^gemini\\b",
552
- hook: null,
553
- contextSource: "history-file",
554
- historyFormat: "json",
555
- securityWarning: null
556
- },
557
- {
558
- tool: "gh copilot",
559
- displayName: "GitHub Copilot",
560
- pattern: "^gh\\s+copilot\\b",
561
- hook: null,
562
- contextSource: "history-file",
563
- historyFormat: "jsonl",
564
- securityWarning: null
565
- },
566
- {
567
- tool: "amazon-q",
568
- displayName: "Amazon Q",
569
- pattern: "^q\\s+(chat|dev)\\b",
570
- hook: null,
571
- contextSource: "history-file",
572
- historyFormat: "json",
573
- securityWarning: null
574
- },
575
- {
576
- tool: "opencode",
577
- displayName: "OpenCode",
578
- pattern: "^opencode\\b",
579
- hook: null,
580
- contextSource: "sqlite",
581
- historyFormat: "sqlite",
582
- securityWarning: null
583
- },
584
- {
585
- tool: "kilo",
586
- displayName: "Kilo Code",
587
- pattern: "^kilo\\b",
588
- hook: null,
589
- contextSource: null,
590
- historyFormat: null,
591
- securityWarning: null
592
- },
593
- {
594
- tool: "grok",
595
- displayName: "Grok Build",
596
- pattern: "^grok\\b",
597
- hook: null,
598
- contextSource: null,
599
- historyFormat: null,
600
- securityWarning: "Grok Build was found to silently upload entire local git repositories \u2014 including .env files and untracked secrets \u2014 to Google Cloud Storage without user disclosure (July 2026 incident). Workscribe does not read Grok Build session files. Consider whether this tool is appropriate for use in repositories containing sensitive credentials."
601
- },
602
- {
603
- tool: "continue",
604
- displayName: "Continue",
605
- pattern: "^continue\\b",
606
- hook: null,
607
- contextSource: null,
608
- historyFormat: null,
609
- securityWarning: null
610
- },
611
- {
612
- tool: "cody",
613
- displayName: "Cody",
614
- pattern: "^cody\\b",
615
- hook: null,
616
- contextSource: null,
617
- historyFormat: null,
618
- securityWarning: null
619
- },
620
- {
621
- tool: "cursor",
622
- displayName: "Cursor",
623
- pattern: "^cursor\\b",
624
- hook: null,
625
- contextSource: null,
626
- historyFormat: null,
627
- securityWarning: null
628
- }
629
- ];
562
+ var ai_tools_default;
563
+ var init_ai_tools = __esm({
564
+ "src/config/ai-tools.json"() {
565
+ ai_tools_default = [
566
+ {
567
+ tool: "claude",
568
+ displayName: "Claude Code",
569
+ pattern: "^claude\\b",
570
+ hook: "claude-code",
571
+ contextSource: "hook",
572
+ historyFormat: null,
573
+ securityWarning: null
574
+ },
575
+ {
576
+ tool: "aider",
577
+ displayName: "Aider",
578
+ pattern: "^aider\\b",
579
+ hook: null,
580
+ contextSource: "history-file",
581
+ historyFormat: "markdown",
582
+ securityWarning: null
583
+ },
584
+ {
585
+ tool: "codex",
586
+ displayName: "Codex CLI",
587
+ pattern: "^codex\\b",
588
+ hook: null,
589
+ contextSource: "history-file",
590
+ historyFormat: "jsonl",
591
+ securityWarning: null
592
+ },
593
+ {
594
+ tool: "gemini",
595
+ displayName: "Gemini CLI",
596
+ pattern: "^gemini\\b",
597
+ hook: null,
598
+ contextSource: "history-file",
599
+ historyFormat: "json",
600
+ securityWarning: null
601
+ },
602
+ {
603
+ tool: "gh copilot",
604
+ displayName: "GitHub Copilot",
605
+ pattern: "^gh\\s+copilot\\b",
606
+ hook: null,
607
+ contextSource: "history-file",
608
+ historyFormat: "jsonl",
609
+ securityWarning: null
610
+ },
611
+ {
612
+ tool: "amazon-q",
613
+ displayName: "Amazon Q",
614
+ pattern: "^q\\s+(chat|dev)\\b",
615
+ hook: null,
616
+ contextSource: "history-file",
617
+ historyFormat: "json",
618
+ securityWarning: null
619
+ },
620
+ {
621
+ tool: "opencode",
622
+ displayName: "OpenCode",
623
+ pattern: "^opencode\\b",
624
+ hook: null,
625
+ contextSource: "sqlite",
626
+ historyFormat: "sqlite",
627
+ securityWarning: null
628
+ },
629
+ {
630
+ tool: "kilo",
631
+ displayName: "Kilo Code",
632
+ pattern: "^kilo\\b",
633
+ hook: null,
634
+ contextSource: null,
635
+ historyFormat: null,
636
+ securityWarning: null
637
+ },
638
+ {
639
+ tool: "grok",
640
+ displayName: "Grok Build",
641
+ pattern: "^grok\\b",
642
+ hook: null,
643
+ contextSource: null,
644
+ historyFormat: null,
645
+ securityWarning: "Grok Build was found to silently upload entire local git repositories \u2014 including .env files and untracked secrets \u2014 to Google Cloud Storage without user disclosure (July 2026 incident). Workscribe does not read Grok Build session files. Consider whether this tool is appropriate for use in repositories containing sensitive credentials."
646
+ },
647
+ {
648
+ tool: "continue",
649
+ displayName: "Continue",
650
+ pattern: "^continue\\b",
651
+ hook: null,
652
+ contextSource: null,
653
+ historyFormat: null,
654
+ securityWarning: null
655
+ },
656
+ {
657
+ tool: "cody",
658
+ displayName: "Cody",
659
+ pattern: "^cody\\b",
660
+ hook: null,
661
+ contextSource: null,
662
+ historyFormat: null,
663
+ securityWarning: null
664
+ },
665
+ {
666
+ tool: "cursor",
667
+ displayName: "Cursor",
668
+ pattern: "^cursor\\b",
669
+ hook: null,
670
+ contextSource: null,
671
+ historyFormat: null,
672
+ securityWarning: null
673
+ }
674
+ ];
675
+ }
676
+ });
630
677
 
631
678
  // src/utils/logger.ts
632
- var import_chalk = __toESM(require("chalk"));
633
- var import_fs5 = __toESM(require("fs"));
634
- var isDebug = process.env.WORKSCRIBE_DEBUG === "1";
635
- var log = {
636
- info: (msg) => console.log(import_chalk.default.cyan("\u2192"), msg),
637
- success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
638
- warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
639
- error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
640
- plain: (msg) => console.log(msg)
641
- };
642
679
  function debugLog(context, error) {
643
680
  if (!isDebug) return;
644
681
  const entry = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${context}] ${error instanceof Error ? error.stack : String(error)}
@@ -648,9 +685,25 @@ function debugLog(context, error) {
648
685
  } catch {
649
686
  }
650
687
  }
688
+ var import_chalk, import_fs5, isDebug, log;
689
+ var init_logger = __esm({
690
+ "src/utils/logger.ts"() {
691
+ "use strict";
692
+ import_chalk = __toESM(require("chalk"));
693
+ import_fs5 = __toESM(require("fs"));
694
+ init_paths();
695
+ isDebug = process.env.WORKSCRIBE_DEBUG === "1";
696
+ log = {
697
+ info: (msg) => console.log(import_chalk.default.cyan("\u2192"), msg),
698
+ success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
699
+ warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
700
+ error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
701
+ plain: (msg) => console.log(msg)
702
+ };
703
+ }
704
+ });
651
705
 
652
706
  // src/config/registry.ts
653
- var USER_REGISTRY_PATH = import_path4.default.join(import_os2.default.homedir(), ".workscribe", "ai-tools.json");
654
707
  function loadAIToolRegistry() {
655
708
  let userEntries = [];
656
709
  try {
@@ -677,36 +730,20 @@ function loadAIToolRegistry() {
677
730
  const filteredBuiltins = ai_tools_default.filter((entry) => !userToolSlugs.has(entry.tool));
678
731
  return [...filteredBuiltins, ...userEntries];
679
732
  }
680
-
681
- // src/profiles/loader.ts
682
- var import_fs9 = __toESM(require("fs"));
683
- var import_path6 = __toESM(require("path"));
733
+ var import_fs6, import_os2, import_path4, USER_REGISTRY_PATH;
734
+ var init_registry = __esm({
735
+ "src/config/registry.ts"() {
736
+ "use strict";
737
+ import_fs6 = __toESM(require("fs"));
738
+ import_os2 = __toESM(require("os"));
739
+ import_path4 = __toESM(require("path"));
740
+ init_ai_tools();
741
+ init_logger();
742
+ USER_REGISTRY_PATH = import_path4.default.join(import_os2.default.homedir(), ".workscribe", "ai-tools.json");
743
+ }
744
+ });
684
745
 
685
746
  // src/config/index.ts
686
- var import_fs7 = __toESM(require("fs"));
687
- var DEFAULTS = {
688
- ai: {
689
- provider: "",
690
- apiKey: "",
691
- model: "",
692
- baseUrl: ""
693
- },
694
- session: {
695
- idleTimeout: 30
696
- },
697
- capture: {
698
- ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"],
699
- aiTools: true,
700
- includeAiderPrompt: true,
701
- profiles: []
702
- },
703
- redact: {
704
- extra: []
705
- },
706
- summary: {
707
- format: "markdown"
708
- }
709
- };
710
747
  function readConfig() {
711
748
  if (!import_fs7.default.existsSync(CONFIG_PATH)) return { ...DEFAULTS };
712
749
  try {
@@ -763,6 +800,40 @@ function deepMerge(defaults, overrides) {
763
800
  }
764
801
  return result;
765
802
  }
803
+ var import_fs7, DEFAULTS;
804
+ var init_config = __esm({
805
+ "src/config/index.ts"() {
806
+ "use strict";
807
+ import_fs7 = __toESM(require("fs"));
808
+ init_paths();
809
+ DEFAULTS = {
810
+ ai: {
811
+ provider: "",
812
+ apiKey: "",
813
+ model: "",
814
+ baseUrl: ""
815
+ },
816
+ session: {
817
+ idleTimeout: 30
818
+ },
819
+ git: {
820
+ qualifyRepoName: false
821
+ },
822
+ capture: {
823
+ ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"],
824
+ aiTools: true,
825
+ includeAiderPrompt: true,
826
+ profiles: []
827
+ },
828
+ redact: {
829
+ extra: []
830
+ },
831
+ summary: {
832
+ format: "markdown"
833
+ }
834
+ };
835
+ }
836
+ });
766
837
 
767
838
  // src/profiles/schema.ts
768
839
  function isNonEmptyString(value) {
@@ -783,21 +854,13 @@ function isValidProfile(candidate) {
783
854
  function isValidManifest(m) {
784
855
  return Array.isArray(m) && m.every((e) => e && typeof e === "object" && typeof e.name === "string");
785
856
  }
786
-
787
- // src/profiles/fetcher.ts
788
- var import_fs8 = __toESM(require("fs"));
789
- var import_path5 = __toESM(require("path"));
857
+ var init_schema = __esm({
858
+ "src/profiles/schema.ts"() {
859
+ "use strict";
860
+ }
861
+ });
790
862
 
791
863
  // src/utils/fetch.ts
792
- var DEFAULT_TIMEOUT_MS = 3e4;
793
- var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
794
- "ENOTFOUND",
795
- "ECONNREFUSED",
796
- "ECONNRESET",
797
- "ETIMEDOUT",
798
- "EHOSTUNREACH",
799
- "ENETUNREACH"
800
- ]);
801
864
  function isNetworkError(err) {
802
865
  if (!(err instanceof Error)) return false;
803
866
  const cause = err.cause;
@@ -821,9 +884,23 @@ async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_TIMEOUT_MS) {
821
884
  clearTimeout(timer);
822
885
  }
823
886
  }
887
+ var DEFAULT_TIMEOUT_MS, NETWORK_ERROR_CODES;
888
+ var init_fetch = __esm({
889
+ "src/utils/fetch.ts"() {
890
+ "use strict";
891
+ DEFAULT_TIMEOUT_MS = 3e4;
892
+ NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
893
+ "ENOTFOUND",
894
+ "ECONNREFUSED",
895
+ "ECONNRESET",
896
+ "ETIMEDOUT",
897
+ "EHOSTUNREACH",
898
+ "ENETUNREACH"
899
+ ]);
900
+ }
901
+ });
824
902
 
825
903
  // src/profiles/fetcher.ts
826
- var REGISTRY_BASE = "https://raw.githubusercontent.com/workscribe/profiles/main";
827
904
  function manifestCachePath() {
828
905
  return import_path5.default.join(PROFILES_DIR, "manifest.json");
829
906
  }
@@ -869,6 +946,19 @@ function ensureProfilesDir() {
869
946
  import_fs8.default.mkdirSync(PROFILES_DIR, { recursive: true, mode: 448 });
870
947
  }
871
948
  }
949
+ var import_fs8, import_path5, REGISTRY_BASE;
950
+ var init_fetcher = __esm({
951
+ "src/profiles/fetcher.ts"() {
952
+ "use strict";
953
+ import_fs8 = __toESM(require("fs"));
954
+ import_path5 = __toESM(require("path"));
955
+ init_paths();
956
+ init_schema();
957
+ init_logger();
958
+ init_fetch();
959
+ REGISTRY_BASE = "https://raw.githubusercontent.com/workscribe/profiles/main";
960
+ }
961
+ });
872
962
 
873
963
  // src/profiles/loader.ts
874
964
  function compileRule(profileName, rule) {
@@ -947,6 +1037,19 @@ function detectProfilesForDirectory(dir) {
947
1037
  return indicatorFiles.length > 0 && indicatorFiles.every((file) => import_fs9.default.existsSync(import_path6.default.join(dir, file)));
948
1038
  });
949
1039
  }
1040
+ var import_fs9, import_path6;
1041
+ var init_loader = __esm({
1042
+ "src/profiles/loader.ts"() {
1043
+ "use strict";
1044
+ import_fs9 = __toESM(require("fs"));
1045
+ import_path6 = __toESM(require("path"));
1046
+ init_paths();
1047
+ init_config();
1048
+ init_schema();
1049
+ init_fetcher();
1050
+ init_logger();
1051
+ }
1052
+ });
950
1053
 
951
1054
  // src/normalize/patterns.ts
952
1055
  function makeRule(pattern, category, extract = () => ({})) {
@@ -963,208 +1066,6 @@ function extractPackages(cmd, afterKeyword) {
963
1066
  if (idx === -1) return [];
964
1067
  return cmd.slice(idx + afterKeyword.length).trim().split(/\s+/).filter((part) => part && !part.startsWith("-"));
965
1068
  }
966
- var BASE_RULES = [
967
- // ─── Git operations ───────────────────────────────────────────────────────
968
- makeRule(/^git\s+commit\b/, "shipping", (_m, cmd) => {
969
- const msgMatch = cmd.match(/-m\s+["']?([^"']+)["']?/);
970
- return { message: msgMatch ? msgMatch[1].trim() : null };
971
- }),
972
- makeRule(/^git\s+push\b/, "shipping", (_m, cmd) => {
973
- const parts = cmd.split(/\s+/);
974
- return { remote: parts[2] ?? "origin", branch: parts[3] ?? null };
975
- }),
976
- makeRule(/^git\s+(pull|fetch)\b/, "versioning", (_m, cmd) => {
977
- const parts = cmd.split(/\s+/);
978
- return { subcommand: parts[1], remote: parts[2] ?? "origin" };
979
- }),
980
- makeRule(/^git\s+(checkout|switch)\b/, "versioning", (_m, cmd) => {
981
- const parts = cmd.split(/\s+/);
982
- const branch = parts.find((part) => !part.startsWith("-") && part !== "git" && part !== parts[1]);
983
- return { branch: branch ?? null };
984
- }),
985
- makeRule(/^git\s+(merge|rebase)\b/, "shipping", (_m, cmd) => {
986
- const parts = cmd.split(/\s+/);
987
- return { subcommand: parts[1], target: parts[2] ?? null };
988
- }),
989
- makeRule(/^git\s+stash\b/, "versioning", (_m, cmd) => {
990
- const parts = cmd.split(/\s+/);
991
- return { subcommand: parts[2] ?? "push" };
992
- }),
993
- makeRule(/^git\s+/, "versioning", (_m, cmd) => {
994
- const parts = cmd.split(/\s+/);
995
- return { subcommand: parts[1] ?? null };
996
- }),
997
- // ─── Releasing ────────────────────────────────────────────────────────────
998
- makeRule(/^npm\s+publish\b/, "releasing", () => ({ tool: "npm" })),
999
- makeRule(/^cargo\s+publish\b/, "releasing", () => ({ tool: "cargo" })),
1000
- makeRule(/^gem\s+push\b/, "releasing", () => ({ tool: "gem" })),
1001
- makeRule(/^docker\s+push\b/, "releasing", (_m, cmd) => ({ tool: "docker", image: cmd.split(/\s+/)[2] ?? null })),
1002
- makeRule(/^gh\s+release\b/, "releasing", (_m, cmd) => ({ tool: "gh", subcommand: cmd.split(/\s+/)[2] ?? null })),
1003
- makeRule(/^fly\s+(deploy|launch)\b/, "releasing", (match) => ({ tool: "fly", subcommand: match[1] })),
1004
- makeRule(/^vercel\s+(deploy|build|--prod)\b/, "releasing", () => ({ tool: "vercel" })),
1005
- makeRule(/^netlify\s+deploy\b/, "releasing", () => ({ tool: "netlify" })),
1006
- makeRule(/^railway\s+up\b/, "releasing", () => ({ tool: "railway" })),
1007
- makeRule(/^wrangler\s+deploy\b/, "releasing", () => ({ tool: "wrangler" })),
1008
- // ─── Dependencies ─────────────────────────────────────────────────────────
1009
- makeRule(/^npm\s+(install|i|add)\b/, "dependencies", (_m, cmd) => ({
1010
- tool: "npm",
1011
- packages: extractPackages(cmd, cmd.includes("install") ? "install" : cmd.includes(" i ") ? " i " : "add")
1012
- })),
1013
- makeRule(/^(yarn|pnpm)\s+(install|add)\b/, "dependencies", (m, cmd) => ({
1014
- tool: m[1],
1015
- packages: extractPackages(cmd, "add")
1016
- })),
1017
- makeRule(/^bun\s+(install|add)\b/, "dependencies", (_m, cmd) => ({
1018
- tool: "bun",
1019
- packages: extractPackages(cmd, "add")
1020
- })),
1021
- makeRule(/^pip\s+(install)\b/, "dependencies", (_m, cmd) => ({
1022
- tool: "pip",
1023
- packages: extractPackages(cmd, "install")
1024
- })),
1025
- makeRule(/^cargo\s+add\b/, "dependencies", (_m, cmd) => ({
1026
- tool: "cargo",
1027
- packages: extractPackages(cmd, "add")
1028
- })),
1029
- makeRule(/^(brew)\s+install\b/, "dependencies", (_m, cmd) => ({
1030
- tool: "brew",
1031
- packages: extractPackages(cmd, "install")
1032
- })),
1033
- makeRule(/^go\s+get\b/, "dependencies", (_m, cmd) => ({
1034
- tool: "go",
1035
- packages: extractPackages(cmd, "get")
1036
- })),
1037
- makeRule(/^npm\s+(uninstall|remove|rm)\b/, "dependencies", (_m, cmd) => ({
1038
- tool: "npm",
1039
- packages: extractPackages(cmd, cmd.split(/\s+/)[1])
1040
- })),
1041
- makeRule(/^(yarn|pnpm)\s+remove\b/, "dependencies", (m, cmd) => ({
1042
- tool: m[1],
1043
- packages: extractPackages(cmd, "remove")
1044
- })),
1045
- makeRule(/^bun\s+remove\b/, "dependencies", (_m, cmd) => ({
1046
- tool: "bun",
1047
- packages: extractPackages(cmd, "remove")
1048
- })),
1049
- makeRule(/^pip\s+uninstall\b/, "dependencies", (_m, cmd) => ({
1050
- tool: "pip",
1051
- packages: extractPackages(cmd, "uninstall")
1052
- })),
1053
- // Runtime version managers
1054
- makeRule(/^nvm\b/, "dependencies", (_m, cmd) => ({ tool: "nvm", subcommand: cmd.split(/\s+/)[1] ?? null })),
1055
- makeRule(/^(fnm|volta)\b/, "dependencies", (m, cmd) => ({ tool: m[1], subcommand: cmd.split(/\s+/)[1] ?? null })),
1056
- makeRule(/^(rbenv|pyenv|asdf)\b/, "dependencies", (m, cmd) => ({
1057
- tool: m[1],
1058
- subcommand: cmd.split(/\s+/)[1] ?? null
1059
- })),
1060
- // ─── Tests ────────────────────────────────────────────────────────────────
1061
- makeRule(/^npm\s+(test|run\s+test)\b/, "testing", () => ({ tool: "npm" })),
1062
- makeRule(/^yarn\s+test\b/, "testing", () => ({ tool: "yarn" })),
1063
- makeRule(/^pnpm\s+test\b/, "testing", () => ({ tool: "pnpm" })),
1064
- makeRule(/^bun\s+test\b/, "testing", () => ({ tool: "bun" })),
1065
- makeRule(/^(jest|vitest)\b/, "testing", (match) => ({ tool: match[1] })),
1066
- makeRule(/^pytest\b/, "testing", () => ({ tool: "pytest" })),
1067
- makeRule(/^go\s+test\b/, "testing", () => ({ tool: "go" })),
1068
- makeRule(/^cargo\s+test\b/, "testing", () => ({ tool: "cargo" })),
1069
- makeRule(/^rspec\b/, "testing", () => ({ tool: "rspec" })),
1070
- makeRule(/^(php\s+)?phpunit\b/, "testing", () => ({ tool: "phpunit" })),
1071
- // ─── Build ────────────────────────────────────────────────────────────────
1072
- makeRule(/^npm\s+run\s+build\b/, "building", () => ({ tool: "npm" })),
1073
- makeRule(/^(yarn|pnpm)\s+build\b/, "building", (match) => ({ tool: match[1] })),
1074
- makeRule(/^bun\s+run\s+build\b/, "building", () => ({ tool: "bun" })),
1075
- makeRule(/^(make|gmake)\b/, "building", (match) => ({ tool: match[1] })),
1076
- makeRule(/^cargo\s+build\b/, "building", () => ({ tool: "cargo" })),
1077
- makeRule(/^go\s+build\b/, "building", () => ({ tool: "go" })),
1078
- makeRule(/^docker\s+build\b/, "building", () => ({ tool: "docker" })),
1079
- makeRule(/^(mvn|gradle)\b/, "building", (match) => ({ tool: match[1] })),
1080
- // ─── Dev server / running ─────────────────────────────────────────────────
1081
- makeRule(/^npm\s+run\s+(dev|start|serve)\b/, "running", (_m, cmd) => ({
1082
- tool: "npm",
1083
- script: cmd.split(/\s+/)[2]
1084
- })),
1085
- makeRule(/^(yarn|pnpm)\s+(dev|start)\b/, "running", (match) => ({ tool: match[1], script: match[2] })),
1086
- makeRule(/^bun\s+run\s+(dev|start|serve)\b/, "running", (_m, cmd) => ({
1087
- tool: "bun",
1088
- script: cmd.split(/\s+/)[2]
1089
- })),
1090
- makeRule(/^python\s+.*manage\.py\s+runserver\b/, "running", () => ({ tool: "django" })),
1091
- // ─── Infrastructure ───────────────────────────────────────────────────────
1092
- makeRule(/^ssh\b/, "infrastructure", (_m, cmd) => {
1093
- const parts = cmd.split(/\s+/);
1094
- return { tool: "ssh", target: parts[parts.length - 1] };
1095
- }),
1096
- makeRule(/^kubectl\b/, "infrastructure", (_m, cmd) => {
1097
- const parts = cmd.split(/\s+/);
1098
- return { tool: "kubectl", subcommand: parts[1] ?? null };
1099
- }),
1100
- makeRule(/^(helm|terraform|pulumi)\b/, "infrastructure", (m, cmd) => {
1101
- const parts = cmd.split(/\s+/);
1102
- return { tool: m[1], subcommand: parts[1] ?? null };
1103
- }),
1104
- makeRule(/^docker\s+(run|compose|ps|stop|start|rm|exec)\b/, "infrastructure", (_m, cmd) => {
1105
- const parts = cmd.split(/\s+/);
1106
- return { tool: "docker", subcommand: parts[1] };
1107
- }),
1108
- makeRule(/^(aws|gcloud|az)\b/, "infrastructure", (m, cmd) => {
1109
- const parts = cmd.split(/\s+/);
1110
- return { tool: m[1], subcommand: parts[1] ?? null };
1111
- }),
1112
- // ─── Database ─────────────────────────────────────────────────────────────
1113
- makeRule(/^(psql|pgcli)\b/, "database", (m) => ({ tool: m[1] })),
1114
- makeRule(/^(mysql|mycli)\b/, "database", (m) => ({ tool: m[1] })),
1115
- makeRule(/^sqlite3\b/, "database", () => ({ tool: "sqlite3" })),
1116
- makeRule(/^(mongosh|mongo)\b/, "database", (m) => ({ tool: m[1] })),
1117
- makeRule(/^redis-cli\b/, "database", () => ({ tool: "redis-cli" })),
1118
- // ─── npx ─────────────────────────────────────────────────────────────────
1119
- makeRule(/^npx\s+(jest|vitest|mocha|ava|tap)\b/, "testing", (_m, cmd) => ({
1120
- tool: cmd.split(/\s+/)[1]
1121
- })),
1122
- makeRule(/^npx\s+tsc\b/, "building", () => ({ tool: "tsc" })),
1123
- makeRule(/^npx\s+(eslint|tslint|biome)\b/, "linting", (_m, cmd) => ({
1124
- tool: cmd.split(/\s+/)[1]
1125
- })),
1126
- makeRule(/^npx\s+(prettier|dprint)\b/, "linting", (_m, cmd) => ({
1127
- tool: cmd.split(/\s+/)[1]
1128
- })),
1129
- makeRule(/^npx\s+\S+/, "running", (_m, cmd) => ({
1130
- tool: cmd.split(/\s+/)[1]
1131
- })),
1132
- // ─── Scripts ──────────────────────────────────────────────────────────────
1133
- makeRule(/^(bash|sh|zsh|fish)\s+\S+/, "running", (match) => ({ shell: match[1] })),
1134
- makeRule(/^node\s+\S+\.(?:js|mjs|cjs)\b/, "running", () => ({ runtime: "node" })),
1135
- makeRule(/^python3?\s+\S+\.py\b/, "running", () => ({ runtime: "python" })),
1136
- // ─── Linting / code quality ───────────────────────────────────────────────
1137
- makeRule(/^(eslint|tslint|biome)\b/, "linting", (match) => ({ tool: match[1] })),
1138
- makeRule(/^(prettier|dprint)\b/, "linting", (match) => ({ tool: match[1] })),
1139
- // ─── generic run scripts ──────────────────────────────────────────────────
1140
- makeRule(/^npm\s+run\s+\S+/, "running", (_m, cmd) => ({
1141
- tool: "npm",
1142
- script: cmd.split(/\s+/)[2]
1143
- })),
1144
- makeRule(/^bun\s+run\s+\S+/, "running", (_m, cmd) => ({
1145
- tool: "bun",
1146
- script: cmd.split(/\s+/)[2]
1147
- })),
1148
- // ─── Code search ─────────────────────────────────────────────────────────
1149
- makeRule(/^(grep|egrep|fgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
1150
- makeRule(/^(rg|ag|ack|ripgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
1151
- makeRule(/^find\s/, "exploring", () => ({ tool: "find" })),
1152
- // ─── Code read ────────────────────────────────────────────────────────────
1153
- makeRule(/^(cat|bat|less|more)\s+\S+/, "exploring", (match) => ({ tool: match[1] })),
1154
- makeRule(/^(head|tail)\b/, "exploring", (match) => ({ tool: match[1] })),
1155
- // ─── File operations ──────────────────────────────────────────────────────
1156
- makeRule(/^(cp|rsync)\b/, "files", (match) => ({ tool: match[1] })),
1157
- makeRule(/^(mv)\b/, "files", () => ({ tool: "mv" })),
1158
- makeRule(/^(rm|rmdir)\b/, "files", (match) => ({ tool: match[1] })),
1159
- makeRule(/^(mkdir|touch|ln)\b/, "files", (match) => ({ tool: match[1] })),
1160
- makeRule(/^(chmod|chown|chgrp)\b/, "files", (match) => ({ tool: match[1] })),
1161
- // ─── Environment ──────────────────────────────────────────────────────────
1162
- makeRule(/^export\s+\w+=/, "files", (_m, cmd) => {
1163
- const varName = cmd.match(/^export\s+(\w+)=/)?.[1] ?? null;
1164
- return { variable: varName };
1165
- }),
1166
- makeRule(/^source\s+/, "files", () => ({}))
1167
- ];
1168
1069
  function aiToolRules() {
1169
1070
  return loadAIToolRegistry().flatMap((entry) => {
1170
1071
  try {
@@ -1178,7 +1079,6 @@ function aiToolRules() {
1178
1079
  function buildRules(profileRules = []) {
1179
1080
  return [...BASE_RULES, ...profileRules, ...aiToolRules()];
1180
1081
  }
1181
- var RULES = buildRules(getActiveProfileRules());
1182
1082
  function normalizeCommand(command) {
1183
1083
  let trimmed = command.trim().replace(/\s+/g, " ");
1184
1084
  trimmed = trimmed.replace(/^(git)\s+-C\s+\S+\s+/, "$1 ");
@@ -1188,12 +1088,223 @@ function normalizeCommand(command) {
1188
1088
  }
1189
1089
  return { category: "other", metadata: {} };
1190
1090
  }
1091
+ var BASE_RULES, RULES;
1092
+ var init_patterns = __esm({
1093
+ "src/normalize/patterns.ts"() {
1094
+ "use strict";
1095
+ init_registry();
1096
+ init_loader();
1097
+ init_logger();
1098
+ BASE_RULES = [
1099
+ // ─── Git operations ───────────────────────────────────────────────────────
1100
+ makeRule(/^git\s+commit\b/, "shipping", (_m, cmd) => {
1101
+ const msgMatch = cmd.match(/-m\s+["']?([^"']+)["']?/);
1102
+ return { message: msgMatch ? msgMatch[1].trim() : null };
1103
+ }),
1104
+ makeRule(/^git\s+push\b/, "shipping", (_m, cmd) => {
1105
+ const parts = cmd.split(/\s+/);
1106
+ return { remote: parts[2] ?? "origin", branch: parts[3] ?? null };
1107
+ }),
1108
+ makeRule(/^git\s+(pull|fetch)\b/, "versioning", (_m, cmd) => {
1109
+ const parts = cmd.split(/\s+/);
1110
+ return { subcommand: parts[1], remote: parts[2] ?? "origin" };
1111
+ }),
1112
+ makeRule(/^git\s+(checkout|switch)\b/, "versioning", (_m, cmd) => {
1113
+ const parts = cmd.split(/\s+/);
1114
+ const branch = parts.find((part) => !part.startsWith("-") && part !== "git" && part !== parts[1]);
1115
+ return { branch: branch ?? null };
1116
+ }),
1117
+ makeRule(/^git\s+(merge|rebase)\b/, "shipping", (_m, cmd) => {
1118
+ const parts = cmd.split(/\s+/);
1119
+ return { subcommand: parts[1], target: parts[2] ?? null };
1120
+ }),
1121
+ makeRule(/^git\s+stash\b/, "versioning", (_m, cmd) => {
1122
+ const parts = cmd.split(/\s+/);
1123
+ return { subcommand: parts[2] ?? "push" };
1124
+ }),
1125
+ makeRule(/^git\s+/, "versioning", (_m, cmd) => {
1126
+ const parts = cmd.split(/\s+/);
1127
+ return { subcommand: parts[1] ?? null };
1128
+ }),
1129
+ // ─── Releasing ────────────────────────────────────────────────────────────
1130
+ makeRule(/^npm\s+publish\b/, "releasing", () => ({ tool: "npm" })),
1131
+ makeRule(/^cargo\s+publish\b/, "releasing", () => ({ tool: "cargo" })),
1132
+ makeRule(/^gem\s+push\b/, "releasing", () => ({ tool: "gem" })),
1133
+ makeRule(/^docker\s+push\b/, "releasing", (_m, cmd) => ({ tool: "docker", image: cmd.split(/\s+/)[2] ?? null })),
1134
+ makeRule(/^gh\s+release\b/, "releasing", (_m, cmd) => ({ tool: "gh", subcommand: cmd.split(/\s+/)[2] ?? null })),
1135
+ makeRule(/^fly\s+(deploy|launch)\b/, "releasing", (match) => ({ tool: "fly", subcommand: match[1] })),
1136
+ makeRule(/^vercel\s+(deploy|build|--prod)\b/, "releasing", () => ({ tool: "vercel" })),
1137
+ makeRule(/^netlify\s+deploy\b/, "releasing", () => ({ tool: "netlify" })),
1138
+ makeRule(/^railway\s+up\b/, "releasing", () => ({ tool: "railway" })),
1139
+ makeRule(/^wrangler\s+deploy\b/, "releasing", () => ({ tool: "wrangler" })),
1140
+ // ─── Dependencies ─────────────────────────────────────────────────────────
1141
+ makeRule(/^npm\s+(install|i|add)\b/, "dependencies", (_m, cmd) => ({
1142
+ tool: "npm",
1143
+ packages: extractPackages(cmd, cmd.includes("install") ? "install" : cmd.includes(" i ") ? " i " : "add")
1144
+ })),
1145
+ makeRule(/^(yarn|pnpm)\s+(install|add)\b/, "dependencies", (m, cmd) => ({
1146
+ tool: m[1],
1147
+ packages: extractPackages(cmd, "add")
1148
+ })),
1149
+ makeRule(/^bun\s+(install|add)\b/, "dependencies", (_m, cmd) => ({
1150
+ tool: "bun",
1151
+ packages: extractPackages(cmd, "add")
1152
+ })),
1153
+ makeRule(/^pip\s+(install)\b/, "dependencies", (_m, cmd) => ({
1154
+ tool: "pip",
1155
+ packages: extractPackages(cmd, "install")
1156
+ })),
1157
+ makeRule(/^cargo\s+add\b/, "dependencies", (_m, cmd) => ({
1158
+ tool: "cargo",
1159
+ packages: extractPackages(cmd, "add")
1160
+ })),
1161
+ makeRule(/^(brew)\s+install\b/, "dependencies", (_m, cmd) => ({
1162
+ tool: "brew",
1163
+ packages: extractPackages(cmd, "install")
1164
+ })),
1165
+ makeRule(/^go\s+get\b/, "dependencies", (_m, cmd) => ({
1166
+ tool: "go",
1167
+ packages: extractPackages(cmd, "get")
1168
+ })),
1169
+ makeRule(/^npm\s+(uninstall|remove|rm)\b/, "dependencies", (_m, cmd) => ({
1170
+ tool: "npm",
1171
+ packages: extractPackages(cmd, cmd.split(/\s+/)[1])
1172
+ })),
1173
+ makeRule(/^(yarn|pnpm)\s+remove\b/, "dependencies", (m, cmd) => ({
1174
+ tool: m[1],
1175
+ packages: extractPackages(cmd, "remove")
1176
+ })),
1177
+ makeRule(/^bun\s+remove\b/, "dependencies", (_m, cmd) => ({
1178
+ tool: "bun",
1179
+ packages: extractPackages(cmd, "remove")
1180
+ })),
1181
+ makeRule(/^pip\s+uninstall\b/, "dependencies", (_m, cmd) => ({
1182
+ tool: "pip",
1183
+ packages: extractPackages(cmd, "uninstall")
1184
+ })),
1185
+ // Runtime version managers
1186
+ makeRule(/^nvm\b/, "dependencies", (_m, cmd) => ({ tool: "nvm", subcommand: cmd.split(/\s+/)[1] ?? null })),
1187
+ makeRule(/^(fnm|volta)\b/, "dependencies", (m, cmd) => ({ tool: m[1], subcommand: cmd.split(/\s+/)[1] ?? null })),
1188
+ makeRule(/^(rbenv|pyenv|asdf)\b/, "dependencies", (m, cmd) => ({
1189
+ tool: m[1],
1190
+ subcommand: cmd.split(/\s+/)[1] ?? null
1191
+ })),
1192
+ // ─── Tests ────────────────────────────────────────────────────────────────
1193
+ makeRule(/^npm\s+(test|run\s+test)\b/, "testing", () => ({ tool: "npm" })),
1194
+ makeRule(/^yarn\s+test\b/, "testing", () => ({ tool: "yarn" })),
1195
+ makeRule(/^pnpm\s+test\b/, "testing", () => ({ tool: "pnpm" })),
1196
+ makeRule(/^bun\s+test\b/, "testing", () => ({ tool: "bun" })),
1197
+ makeRule(/^(jest|vitest)\b/, "testing", (match) => ({ tool: match[1] })),
1198
+ makeRule(/^pytest\b/, "testing", () => ({ tool: "pytest" })),
1199
+ makeRule(/^go\s+test\b/, "testing", () => ({ tool: "go" })),
1200
+ makeRule(/^cargo\s+test\b/, "testing", () => ({ tool: "cargo" })),
1201
+ makeRule(/^rspec\b/, "testing", () => ({ tool: "rspec" })),
1202
+ makeRule(/^(php\s+)?phpunit\b/, "testing", () => ({ tool: "phpunit" })),
1203
+ // ─── Build ────────────────────────────────────────────────────────────────
1204
+ makeRule(/^npm\s+run\s+build\b/, "building", () => ({ tool: "npm" })),
1205
+ makeRule(/^(yarn|pnpm)\s+build\b/, "building", (match) => ({ tool: match[1] })),
1206
+ makeRule(/^bun\s+run\s+build\b/, "building", () => ({ tool: "bun" })),
1207
+ makeRule(/^(make|gmake)\b/, "building", (match) => ({ tool: match[1] })),
1208
+ makeRule(/^cargo\s+build\b/, "building", () => ({ tool: "cargo" })),
1209
+ makeRule(/^go\s+build\b/, "building", () => ({ tool: "go" })),
1210
+ makeRule(/^docker\s+build\b/, "building", () => ({ tool: "docker" })),
1211
+ makeRule(/^(mvn|gradle)\b/, "building", (match) => ({ tool: match[1] })),
1212
+ // ─── Dev server / running ─────────────────────────────────────────────────
1213
+ makeRule(/^npm\s+run\s+(dev|start|serve)\b/, "running", (_m, cmd) => ({
1214
+ tool: "npm",
1215
+ script: cmd.split(/\s+/)[2]
1216
+ })),
1217
+ makeRule(/^(yarn|pnpm)\s+(dev|start)\b/, "running", (match) => ({ tool: match[1], script: match[2] })),
1218
+ makeRule(/^bun\s+run\s+(dev|start|serve)\b/, "running", (_m, cmd) => ({
1219
+ tool: "bun",
1220
+ script: cmd.split(/\s+/)[2]
1221
+ })),
1222
+ makeRule(/^python\s+.*manage\.py\s+runserver\b/, "running", () => ({ tool: "django" })),
1223
+ // ─── Infrastructure ───────────────────────────────────────────────────────
1224
+ makeRule(/^ssh\b/, "infrastructure", (_m, cmd) => {
1225
+ const parts = cmd.split(/\s+/);
1226
+ return { tool: "ssh", target: parts[parts.length - 1] };
1227
+ }),
1228
+ makeRule(/^kubectl\b/, "infrastructure", (_m, cmd) => {
1229
+ const parts = cmd.split(/\s+/);
1230
+ return { tool: "kubectl", subcommand: parts[1] ?? null };
1231
+ }),
1232
+ makeRule(/^(helm|terraform|pulumi)\b/, "infrastructure", (m, cmd) => {
1233
+ const parts = cmd.split(/\s+/);
1234
+ return { tool: m[1], subcommand: parts[1] ?? null };
1235
+ }),
1236
+ makeRule(/^docker\s+(run|compose|ps|stop|start|rm|exec)\b/, "infrastructure", (_m, cmd) => {
1237
+ const parts = cmd.split(/\s+/);
1238
+ return { tool: "docker", subcommand: parts[1] };
1239
+ }),
1240
+ makeRule(/^(aws|gcloud|az)\b/, "infrastructure", (m, cmd) => {
1241
+ const parts = cmd.split(/\s+/);
1242
+ return { tool: m[1], subcommand: parts[1] ?? null };
1243
+ }),
1244
+ // ─── Database ─────────────────────────────────────────────────────────────
1245
+ makeRule(/^(psql|pgcli)\b/, "database", (m) => ({ tool: m[1] })),
1246
+ makeRule(/^(mysql|mycli)\b/, "database", (m) => ({ tool: m[1] })),
1247
+ makeRule(/^sqlite3\b/, "database", () => ({ tool: "sqlite3" })),
1248
+ makeRule(/^(mongosh|mongo)\b/, "database", (m) => ({ tool: m[1] })),
1249
+ makeRule(/^redis-cli\b/, "database", () => ({ tool: "redis-cli" })),
1250
+ // ─── npx ─────────────────────────────────────────────────────────────────
1251
+ makeRule(/^npx\s+(jest|vitest|mocha|ava|tap)\b/, "testing", (_m, cmd) => ({
1252
+ tool: cmd.split(/\s+/)[1]
1253
+ })),
1254
+ makeRule(/^npx\s+tsc\b/, "building", () => ({ tool: "tsc" })),
1255
+ makeRule(/^npx\s+(eslint|tslint|biome)\b/, "linting", (_m, cmd) => ({
1256
+ tool: cmd.split(/\s+/)[1]
1257
+ })),
1258
+ makeRule(/^npx\s+(prettier|dprint)\b/, "linting", (_m, cmd) => ({
1259
+ tool: cmd.split(/\s+/)[1]
1260
+ })),
1261
+ makeRule(/^npx\s+\S+/, "running", (_m, cmd) => ({
1262
+ tool: cmd.split(/\s+/)[1]
1263
+ })),
1264
+ // ─── Scripts ──────────────────────────────────────────────────────────────
1265
+ makeRule(/^(bash|sh|zsh|fish)\s+\S+/, "running", (match) => ({ shell: match[1] })),
1266
+ makeRule(/^node\s+\S+\.(?:js|mjs|cjs)\b/, "running", () => ({ runtime: "node" })),
1267
+ makeRule(/^python3?\s+\S+\.py\b/, "running", () => ({ runtime: "python" })),
1268
+ // ─── Linting / code quality ───────────────────────────────────────────────
1269
+ makeRule(/^(eslint|tslint|biome)\b/, "linting", (match) => ({ tool: match[1] })),
1270
+ makeRule(/^(prettier|dprint)\b/, "linting", (match) => ({ tool: match[1] })),
1271
+ // ─── generic run scripts ──────────────────────────────────────────────────
1272
+ makeRule(/^npm\s+run\s+\S+/, "running", (_m, cmd) => ({
1273
+ tool: "npm",
1274
+ script: cmd.split(/\s+/)[2]
1275
+ })),
1276
+ makeRule(/^bun\s+run\s+\S+/, "running", (_m, cmd) => ({
1277
+ tool: "bun",
1278
+ script: cmd.split(/\s+/)[2]
1279
+ })),
1280
+ // ─── Code search ─────────────────────────────────────────────────────────
1281
+ makeRule(/^(grep|egrep|fgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
1282
+ makeRule(/^(rg|ag|ack|ripgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
1283
+ makeRule(/^find\s/, "exploring", () => ({ tool: "find" })),
1284
+ // ─── Code read ────────────────────────────────────────────────────────────
1285
+ makeRule(/^(cat|bat|less|more)\s+\S+/, "exploring", (match) => ({ tool: match[1] })),
1286
+ makeRule(/^(head|tail)\b/, "exploring", (match) => ({ tool: match[1] })),
1287
+ // ─── File operations ──────────────────────────────────────────────────────
1288
+ makeRule(/^(cp|rsync)\b/, "files", (match) => ({ tool: match[1] })),
1289
+ makeRule(/^(mv)\b/, "files", () => ({ tool: "mv" })),
1290
+ makeRule(/^(rm|rmdir)\b/, "files", (match) => ({ tool: match[1] })),
1291
+ makeRule(/^(mkdir|touch|ln)\b/, "files", (match) => ({ tool: match[1] })),
1292
+ makeRule(/^(chmod|chown|chgrp)\b/, "files", (match) => ({ tool: match[1] })),
1293
+ // ─── Environment ──────────────────────────────────────────────────────────
1294
+ makeRule(/^export\s+\w+=/, "files", (_m, cmd) => {
1295
+ const varName = cmd.match(/^export\s+(\w+)=/)?.[1] ?? null;
1296
+ return { variable: varName };
1297
+ }),
1298
+ makeRule(/^source\s+/, "files", () => ({}))
1299
+ ];
1300
+ RULES = buildRules(getActiveProfileRules());
1301
+ }
1302
+ });
1191
1303
 
1192
1304
  // src/capture/filter.ts
1193
1305
  function normalizeForFilter(cmd) {
1194
1306
  return cmd.trim().replace(/\s+/g, " ");
1195
1307
  }
1196
- var BUILTIN_IGNORE = /* @__PURE__ */ new Set(["workscribe"]);
1197
1308
  function shouldFilter(command, ignoreList) {
1198
1309
  const normalized = normalizeForFilter(command);
1199
1310
  if (!normalized) return true;
@@ -1209,9 +1320,22 @@ function isDuplicate(command, cwd, lastEvent, dedupWindowMs = 5e3, now = Date.no
1209
1320
  const lastTime = new Date(lastEvent.captured_at).getTime();
1210
1321
  return lastEvent.command === normalizeForFilter(command) && lastEvent.cwd === cwd && now - lastTime < dedupWindowMs;
1211
1322
  }
1323
+ var BUILTIN_IGNORE;
1324
+ var init_filter = __esm({
1325
+ "src/capture/filter.ts"() {
1326
+ "use strict";
1327
+ BUILTIN_IGNORE = /* @__PURE__ */ new Set(["workscribe"]);
1328
+ }
1329
+ });
1212
1330
 
1213
1331
  // src/capture/ide.ts
1214
- var IDE_DEDUP_WINDOW_MS = 3e4;
1332
+ var IDE_DEDUP_WINDOW_MS;
1333
+ var init_ide = __esm({
1334
+ "src/capture/ide.ts"() {
1335
+ "use strict";
1336
+ IDE_DEDUP_WINDOW_MS = 3e4;
1337
+ }
1338
+ });
1215
1339
 
1216
1340
  // src/capture/index.ts
1217
1341
  function captureCommand(opts) {
@@ -1228,7 +1352,7 @@ function captureCommand(opts) {
1228
1352
  }
1229
1353
  const command2 = opts.command ?? opts.file ?? opts.type;
1230
1354
  const nameOverride2 = config.projects?.[cwd] ?? null;
1231
- const { repoName: repoName2, branch: branch2 } = getGitContext(cwd, nameOverride2);
1355
+ const { repoName: repoName2, branch: branch2 } = getGitContext(cwd, nameOverride2, config.git.qualifyRepoName);
1232
1356
  insertEvent({
1233
1357
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1234
1358
  command: command2,
@@ -1285,11 +1409,28 @@ function captureCommand(opts) {
1285
1409
  file_path: null
1286
1410
  });
1287
1411
  }
1412
+ var import_fs10;
1413
+ var init_capture = __esm({
1414
+ "src/capture/index.ts"() {
1415
+ "use strict";
1416
+ import_fs10 = __toESM(require("fs"));
1417
+ init_db();
1418
+ init_redact();
1419
+ init_git();
1420
+ init_git_snapshot();
1421
+ init_aider();
1422
+ init_patterns();
1423
+ init_config();
1424
+ init_filter();
1425
+ init_ide();
1426
+ init_paths();
1427
+ init_registry();
1428
+ }
1429
+ });
1288
1430
 
1289
1431
  // src/cli/capture.ts
1290
- var CAPTURE_HANG_TIMEOUT_MS = 5e3;
1291
- function registerCapture(program2) {
1292
- program2.command("_capture").description("Internal: capture a shell command or IDE event (called by shell hook or IDE plugin)").option("--cmd <command>", "command that was executed (shell events)").option("--cwd <path>", "working directory").option("--exit <code>", "exit code", parseInt).option("--duration <ms>", "duration in milliseconds", parseInt).option("--git-before <sha>", "git HEAD SHA before this command ran").option("--type <type>", "event type override \u2014 skips normalization (IDE integrations)").option("--source <source>", "event source identifier (e.g. vscode, neovim, jupyter)").option("--file <path>", "file path for code_edit events").action((opts) => {
1432
+ function registerCapture(program) {
1433
+ program.command("_capture").description("Internal: capture a shell command or IDE event (called by shell hook or IDE plugin)").option("--cmd <command>", "command that was executed (shell events)").option("--cwd <path>", "working directory").option("--exit <code>", "exit code", parseInt).option("--duration <ms>", "duration in milliseconds", parseInt).option("--git-before <sha>", "git HEAD SHA before this command ran").option("--type <type>", "event type override \u2014 skips normalization (IDE integrations)").option("--source <source>", "event source identifier (e.g. vscode, neovim, jupyter)").option("--file <path>", "file path for code_edit events").action((opts) => {
1293
1434
  const hangGuard = setTimeout(() => process.exit(0), CAPTURE_HANG_TIMEOUT_MS);
1294
1435
  hangGuard.unref();
1295
1436
  try {
@@ -1310,15 +1451,28 @@ function registerCapture(program2) {
1310
1451
  }
1311
1452
  });
1312
1453
  }
1454
+ var CAPTURE_HANG_TIMEOUT_MS;
1455
+ var init_capture2 = __esm({
1456
+ "src/cli/capture.ts"() {
1457
+ "use strict";
1458
+ init_capture();
1459
+ init_logger();
1460
+ CAPTURE_HANG_TIMEOUT_MS = 5e3;
1461
+ }
1462
+ });
1313
1463
 
1314
1464
  // src/capture/detect-secrets.ts
1315
1465
  function containsSecrets(text) {
1316
1466
  return redact(text) !== text;
1317
1467
  }
1468
+ var init_detect_secrets = __esm({
1469
+ "src/capture/detect-secrets.ts"() {
1470
+ "use strict";
1471
+ init_redact();
1472
+ }
1473
+ });
1318
1474
 
1319
1475
  // src/db/alerts.ts
1320
- var import_fs11 = __toESM(require("fs"));
1321
- var import_path7 = __toESM(require("path"));
1322
1476
  function insertAlert(tool, sessionId, context) {
1323
1477
  getDb().prepare(
1324
1478
  `INSERT INTO alerts (created_at, tool, session_id, context)
@@ -1342,6 +1496,16 @@ function clearAlerts() {
1342
1496
  }
1343
1497
  return Number(result.changes);
1344
1498
  }
1499
+ var import_fs11, import_path7;
1500
+ var init_alerts2 = __esm({
1501
+ "src/db/alerts.ts"() {
1502
+ "use strict";
1503
+ import_fs11 = __toESM(require("fs"));
1504
+ import_path7 = __toESM(require("path"));
1505
+ init_db();
1506
+ init_paths();
1507
+ }
1508
+ });
1345
1509
 
1346
1510
  // src/cli/hook.ts
1347
1511
  function parseHookPayload(raw) {
@@ -1362,8 +1526,8 @@ function parseUserPromptPayload(raw) {
1362
1526
  if (!prompt) return null;
1363
1527
  return { session_id: sessionId, prompt, cwd };
1364
1528
  }
1365
- function registerHook(program2) {
1366
- program2.command("_hook").description("Internal: capture events from AI tool hooks (stdin JSON payload)").option("--source <source>", "hook source identifier (e.g. claude-code)").option("--event <event>", "hook event type (PostToolUse or UserPromptSubmit)", "PostToolUse").action((opts) => {
1529
+ function registerHook(program) {
1530
+ program.command("_hook").description("Internal: capture events from AI tool hooks (stdin JSON payload)").option("--source <source>", "hook source identifier (e.g. claude-code)").option("--event <event>", "hook event type (PostToolUse or UserPromptSubmit)", "PostToolUse").action((opts) => {
1367
1531
  let raw = "";
1368
1532
  process.stdin.setEncoding("utf-8");
1369
1533
  process.stdin.on("data", (chunk) => {
@@ -1404,13 +1568,16 @@ function registerHook(program2) {
1404
1568
  });
1405
1569
  });
1406
1570
  }
1407
-
1408
- // src/cli/init.ts
1409
- var import_fs14 = __toESM(require("fs"));
1410
- var import_path10 = __toESM(require("path"));
1411
- var import_os5 = __toESM(require("os"));
1412
- var import_prompts = __toESM(require("prompts"));
1413
- var import_chalk2 = __toESM(require("chalk"));
1571
+ var init_hook = __esm({
1572
+ "src/cli/hook.ts"() {
1573
+ "use strict";
1574
+ init_capture();
1575
+ init_config();
1576
+ init_logger();
1577
+ init_detect_secrets();
1578
+ init_alerts2();
1579
+ }
1580
+ });
1414
1581
 
1415
1582
  // src/utils/format.ts
1416
1583
  function formatDuration(minutes) {
@@ -1470,57 +1637,18 @@ function isoWeekKey(dateStr) {
1470
1637
  const weekNum = Math.ceil(((d.getTime() - jan4.getTime()) / 864e5 + jan4.getDay() + 1) / 7);
1471
1638
  return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
1472
1639
  }
1640
+ var init_format = __esm({
1641
+ "src/utils/format.ts"() {
1642
+ "use strict";
1643
+ }
1644
+ });
1473
1645
 
1474
1646
  // src/ai/prompt.ts
1475
- var SYSTEM_PROMPT = `You write short developer work summaries. Focus on what the developer accomplished and what changed \u2014 not which tools or commands were used.
1476
-
1477
- Here is an example:
1478
-
1479
- INPUT:
1480
- Project: api-service
1481
- Branch: feature/auth
1482
- Duration: 2h 00m
1483
- Events:
1484
- - versioning {"branch":"feature/auth"}
1485
- - 2x dependencies {"tool":"npm","packages":["bcrypt","jsonwebtoken"]}
1486
- - 8x testing [failed]
1487
- - testing
1488
- - 2x files {"tool":"mkdir"}
1489
- - shipping {"message":"fix: auth token validation"}
1490
- - shipping {"remote":"origin","branch":"feature/auth"}
1491
-
1492
- OUTPUT:
1493
- - Added authentication dependencies (bcrypt, JWT)
1494
- - Debugged and resolved failing auth tests
1495
- - Committed and pushed the token validation fix
1496
-
1497
- RULES \u2014 follow these exactly:
1498
-
1499
- 1. Interpret repeated events as patterns, not individual steps.
1500
- - Many testing [failed] followed by a passing test = debugging a failure. Say "resolved failing X tests", not "ran tests multiple times".
1501
- - Many versioning events = branch housekeeping. Fold into context, do not list each git command.
1502
-
1503
- 2. Omit utility commands entirely. File size checks, file reads, grep searches, and cat commands are research steps \u2014 they are not outcomes and must not appear in the summary.
1504
-
1505
- 3. Do not name the AI tool. For ai_session events: focus on what was built or committed. If a commits array is present in metadata, use those commit messages. Never write "via Claude Code", "using Claude Code", or similar \u2014 omit the tool name entirely.
1506
-
1507
- 4. For other events with a hint field: describe the developer's intent, not the raw command.
1508
-
1509
- 5. For dependencies events: infer the purpose from package names and describe the domain (bcrypt/jsonwebtoken \u2192 authentication, prisma/pg \u2192 database, jest/vitest \u2192 testing). For unfamiliar packages, name them directly.
1510
-
1511
- 6. Fold supporting events into the narrative. Building, shipping, versioning, and file operations are context \u2014 only make them a bullet when they are the primary activity (e.g. a standalone build fix or deploy with no accompanying commits).
1512
-
1513
- 7. Stick to what the events show. Do not infer subject matter from the branch name. Do not add details not derivable from the events.
1514
-
1515
- 8. For code_edit events: these represent file saves from an IDE integration. Describe what was being built based on repository context (branch name, commit messages). Never list individual filenames.
1516
-
1517
- Maximum 5 bullets. Output ONLY the bullet points, nothing else.`;
1518
1647
  function getSystemPrompt() {
1519
1648
  const contexts = getActiveProfileAiContexts();
1520
1649
  if (contexts.length === 0) return SYSTEM_PROMPT;
1521
1650
  return SYSTEM_PROMPT + "\n\n" + contexts.join("\n");
1522
1651
  }
1523
- var DEFAULT_EXCLUDED_CATEGORIES = /* @__PURE__ */ new Set(["exploring", "note", "linting"]);
1524
1652
  function buildExcludedSet(includeCategories = []) {
1525
1653
  const excluded = new Set(DEFAULT_EXCLUDED_CATEGORIES);
1526
1654
  for (const cat of includeCategories) excluded.delete(cat);
@@ -1572,39 +1700,6 @@ function buildUserMessage(payload, includeCategories = []) {
1572
1700
  }
1573
1701
  return lines.join("\n");
1574
1702
  }
1575
- var WEEKLY_SYSTEM_PROMPT = `You write short weekly developer summaries. Here is an example:
1576
-
1577
- INPUT:
1578
- Week: 2026-05-18 to 2026-05-22
1579
-
1580
- Monday 2026-05-18:
1581
- Project: api-service (feature/auth) \u2014 2h 10m
1582
- Events: 3x code_commit, 2x test_run, dependency_install
1583
-
1584
- Tuesday 2026-05-19:
1585
- Project: api-service (feature/auth) \u2014 1h 30m
1586
- Events: 2x code_commit, test_run, build
1587
-
1588
- Wednesday 2026-05-20:
1589
- Project: dashboard (main) \u2014 3h 00m
1590
- Events: 4x code_commit, 2x build, test_run
1591
-
1592
- Thursday 2026-05-21:
1593
- Project: api-service (feature/auth) \u2014 1h 00m
1594
- Events: code_commit {"message":"feat: merge auth"}, branch_switch
1595
-
1596
- Friday 2026-05-22:
1597
- Project: dashboard (main) \u2014 2h 00m
1598
- Events: 3x code_commit, build
1599
-
1600
- OUTPUT:
1601
- ## Week of 18 May \u2014 9h 40m across 2 projects
1602
-
1603
- **api-service** (Mon\u2013Thu): Built and shipped the auth feature \u2014 JWT setup, token validation fixes, and final merge.
1604
- **dashboard** (Wed, Fri): Active development across 7 commits with builds passing.
1605
-
1606
- Now summarise the following week. Output ONLY the summary \u2014 a one-line header then one bullet per active project. Maximum 4 bullets.`;
1607
- var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1608
1703
  function buildWeeklyMessage(payload) {
1609
1704
  const lines = [`Week: ${payload.weekStart} to ${payload.weekEnd}`, ""];
1610
1705
  for (const { date, sessions } of payload.days) {
@@ -1642,194 +1737,293 @@ function buildWeeklyMessage(payload) {
1642
1737
  }
1643
1738
  return lines.join("\n");
1644
1739
  }
1740
+ var SYSTEM_PROMPT, DEFAULT_EXCLUDED_CATEGORIES, WEEKLY_SYSTEM_PROMPT, DAY_NAMES;
1741
+ var init_prompt = __esm({
1742
+ "src/ai/prompt.ts"() {
1743
+ "use strict";
1744
+ init_format();
1745
+ init_loader();
1746
+ SYSTEM_PROMPT = `You write short developer work summaries. Focus on what the developer accomplished and what changed \u2014 not which tools or commands were used.
1645
1747
 
1646
- // src/ai/generic-provider.ts
1647
- var GenericProvider = class {
1648
- constructor(config, model, apiKey, baseUrl, includeCategories = []) {
1649
- this.config = config;
1650
- this.model = model;
1651
- this.apiKey = apiKey;
1652
- this.baseUrl = baseUrl;
1653
- this.includeCategories = includeCategories;
1654
- }
1655
- config;
1656
- model;
1657
- apiKey;
1658
- baseUrl;
1659
- includeCategories;
1660
- async call(system, userMsg, maxTokens) {
1661
- const response = await fetchWithTimeout(this.config.chatUrl(this.baseUrl, this.apiKey, this.model), {
1662
- method: "POST",
1663
- headers: this.config.headers(this.apiKey),
1664
- body: JSON.stringify(this.config.buildChatBody(this.model, system, userMsg, maxTokens))
1665
- });
1666
- if (!response.ok) {
1667
- throw new Error(this.config.chatError(response.status, await response.text()));
1668
- }
1669
- return this.config.parseText(await response.json());
1670
- }
1671
- async summarize(payload) {
1672
- return this.call(getSystemPrompt(), buildUserMessage(payload, this.includeCategories), this.config.dailyTokens);
1673
- }
1674
- async summarizeWeek(payload) {
1675
- return this.call(WEEKLY_SYSTEM_PROMPT, buildWeeklyMessage(payload), this.config.weeklyTokens);
1748
+ Here is an example:
1749
+
1750
+ INPUT:
1751
+ Project: api-service
1752
+ Branch: feature/auth
1753
+ Duration: 2h 00m
1754
+ Events:
1755
+ - versioning {"branch":"feature/auth"}
1756
+ - 2x dependencies {"tool":"npm","packages":["bcrypt","jsonwebtoken"]}
1757
+ - 8x testing [failed]
1758
+ - testing
1759
+ - 2x files {"tool":"mkdir"}
1760
+ - shipping {"message":"fix: auth token validation"}
1761
+ - shipping {"remote":"origin","branch":"feature/auth"}
1762
+
1763
+ OUTPUT:
1764
+ - Added authentication dependencies (bcrypt, JWT)
1765
+ - Debugged and resolved failing auth tests
1766
+ - Committed and pushed the token validation fix
1767
+
1768
+ RULES \u2014 follow these exactly:
1769
+
1770
+ 1. Interpret repeated events as patterns, not individual steps.
1771
+ - Many testing [failed] followed by a passing test = debugging a failure. Say "resolved failing X tests", not "ran tests multiple times".
1772
+ - Many versioning events = branch housekeeping. Fold into context, do not list each git command.
1773
+
1774
+ 2. Omit utility commands entirely. File size checks, file reads, grep searches, and cat commands are research steps \u2014 they are not outcomes and must not appear in the summary.
1775
+
1776
+ 3. Do not name the AI tool. For ai_session events: focus on what was built or committed. If a commits array is present in metadata, use those commit messages. Never write "via Claude Code", "using Claude Code", or similar \u2014 omit the tool name entirely.
1777
+
1778
+ 4. For other events with a hint field: describe the developer's intent, not the raw command.
1779
+
1780
+ 5. For dependencies events: infer the purpose from package names and describe the domain (bcrypt/jsonwebtoken \u2192 authentication, prisma/pg \u2192 database, jest/vitest \u2192 testing). For unfamiliar packages, name them directly.
1781
+
1782
+ 6. Fold supporting events into the narrative. Building, shipping, versioning, and file operations are context \u2014 only make them a bullet when they are the primary activity (e.g. a standalone build fix or deploy with no accompanying commits).
1783
+
1784
+ 7. Stick to what the events show. Do not infer subject matter from the branch name. Do not add details not derivable from the events.
1785
+
1786
+ 8. For code_edit events: these represent file saves from an IDE integration. Describe what was being built based on repository context (branch name, commit messages). Never list individual filenames.
1787
+
1788
+ Maximum 5 bullets. Output ONLY the bullet points, nothing else.`;
1789
+ DEFAULT_EXCLUDED_CATEGORIES = /* @__PURE__ */ new Set(["exploring", "note", "linting"]);
1790
+ WEEKLY_SYSTEM_PROMPT = `You write short weekly developer summaries. Here is an example:
1791
+
1792
+ INPUT:
1793
+ Week: 2026-05-18 to 2026-05-22
1794
+
1795
+ Monday 2026-05-18:
1796
+ Project: api-service (feature/auth) \u2014 2h 10m
1797
+ Events: 3x code_commit, 2x test_run, dependency_install
1798
+
1799
+ Tuesday 2026-05-19:
1800
+ Project: api-service (feature/auth) \u2014 1h 30m
1801
+ Events: 2x code_commit, test_run, build
1802
+
1803
+ Wednesday 2026-05-20:
1804
+ Project: dashboard (main) \u2014 3h 00m
1805
+ Events: 4x code_commit, 2x build, test_run
1806
+
1807
+ Thursday 2026-05-21:
1808
+ Project: api-service (feature/auth) \u2014 1h 00m
1809
+ Events: code_commit {"message":"feat: merge auth"}, branch_switch
1810
+
1811
+ Friday 2026-05-22:
1812
+ Project: dashboard (main) \u2014 2h 00m
1813
+ Events: 3x code_commit, build
1814
+
1815
+ OUTPUT:
1816
+ ## Week of 18 May \u2014 9h 40m across 2 projects
1817
+
1818
+ **api-service** (Mon\u2013Thu): Built and shipped the auth feature \u2014 JWT setup, token validation fixes, and final merge.
1819
+ **dashboard** (Wed, Fri): Active development across 7 commits with builds passing.
1820
+
1821
+ Now summarise the following week. Output ONLY the summary \u2014 a one-line header then one bullet per active project. Maximum 4 bullets.`;
1822
+ DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1676
1823
  }
1677
- async validate() {
1678
- const isPost = this.config.validateMethod === "POST";
1679
- const response = await fetchWithTimeout(this.config.validateUrl(this.baseUrl, this.apiKey, this.model), {
1680
- method: this.config.validateMethod,
1681
- headers: isPost ? this.config.headers(this.apiKey) : void 0,
1682
- body: isPost && this.config.buildValidateBody ? JSON.stringify(this.config.buildValidateBody(this.model)) : void 0
1683
- });
1684
- if (!response.ok) {
1685
- throw new Error(this.config.validateError(response.status, await response.text(), this.baseUrl));
1686
- }
1824
+ });
1825
+
1826
+ // src/ai/generic-provider.ts
1827
+ var GenericProvider;
1828
+ var init_generic_provider = __esm({
1829
+ "src/ai/generic-provider.ts"() {
1830
+ "use strict";
1831
+ init_prompt();
1832
+ init_fetch();
1833
+ GenericProvider = class {
1834
+ constructor(config, model, apiKey, baseUrl, includeCategories = []) {
1835
+ this.config = config;
1836
+ this.model = model;
1837
+ this.apiKey = apiKey;
1838
+ this.baseUrl = baseUrl;
1839
+ this.includeCategories = includeCategories;
1840
+ }
1841
+ config;
1842
+ model;
1843
+ apiKey;
1844
+ baseUrl;
1845
+ includeCategories;
1846
+ async call(system, userMsg, maxTokens) {
1847
+ const response = await fetchWithTimeout(this.config.chatUrl(this.baseUrl, this.apiKey, this.model), {
1848
+ method: "POST",
1849
+ headers: this.config.headers(this.apiKey),
1850
+ body: JSON.stringify(this.config.buildChatBody(this.model, system, userMsg, maxTokens))
1851
+ });
1852
+ if (!response.ok) {
1853
+ throw new Error(this.config.chatError(response.status, await response.text()));
1854
+ }
1855
+ return this.config.parseText(await response.json());
1856
+ }
1857
+ async summarize(payload) {
1858
+ return this.call(getSystemPrompt(), buildUserMessage(payload, this.includeCategories), this.config.dailyTokens);
1859
+ }
1860
+ async summarizeWeek(payload) {
1861
+ return this.call(WEEKLY_SYSTEM_PROMPT, buildWeeklyMessage(payload), this.config.weeklyTokens);
1862
+ }
1863
+ async validate() {
1864
+ const isPost = this.config.validateMethod === "POST";
1865
+ const response = await fetchWithTimeout(this.config.validateUrl(this.baseUrl, this.apiKey, this.model), {
1866
+ method: this.config.validateMethod,
1867
+ headers: isPost ? this.config.headers(this.apiKey) : void 0,
1868
+ body: isPost && this.config.buildValidateBody ? JSON.stringify(this.config.buildValidateBody(this.model)) : void 0
1869
+ });
1870
+ if (!response.ok) {
1871
+ throw new Error(this.config.validateError(response.status, await response.text(), this.baseUrl));
1872
+ }
1873
+ }
1874
+ };
1687
1875
  }
1688
- };
1876
+ });
1689
1877
 
1690
1878
  // src/ai/provider-config.ts
1691
- var PROVIDER_CONFIGS = {
1692
- anthropic: {
1693
- defaultModel: "claude-haiku-4-5-20251001",
1694
- defaultBaseUrl: "https://api.anthropic.com/v1",
1695
- dailyTokens: 1024,
1696
- weeklyTokens: 1024,
1697
- chatUrl: (baseUrl) => `${baseUrl}/messages`,
1698
- validateUrl: (baseUrl) => `${baseUrl}/messages`,
1699
- validateMethod: "POST",
1700
- headers: (apiKey) => ({
1701
- "Content-Type": "application/json",
1702
- "x-api-key": apiKey,
1703
- "anthropic-version": "2023-06-01"
1704
- }),
1705
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1706
- model,
1707
- max_tokens: maxTokens,
1708
- system,
1709
- messages: [{ role: "user", content: userMsg }]
1710
- }),
1711
- buildValidateBody: (model) => ({
1712
- model,
1713
- max_tokens: 1,
1714
- messages: [{ role: "user", content: "." }]
1715
- }),
1716
- parseText: (json) => {
1717
- const data = json;
1718
- return data.content.find((b) => b.type === "text")?.text ?? "";
1719
- },
1720
- chatError: (status, body) => `Anthropic API error ${status}: ${body}`,
1721
- validateError: (status, body) => `Anthropic API error ${status}: ${body}`
1722
- },
1723
- openai: {
1724
- defaultModel: "gpt-4o-mini",
1725
- defaultBaseUrl: "https://api.openai.com/v1",
1726
- dailyTokens: 1024,
1727
- weeklyTokens: 1024,
1728
- chatUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1729
- validateUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1730
- validateMethod: "POST",
1731
- headers: (apiKey) => ({
1732
- "Content-Type": "application/json",
1733
- Authorization: `Bearer ${apiKey}`
1734
- }),
1735
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1736
- model,
1737
- messages: [
1738
- { role: "system", content: system },
1739
- { role: "user", content: userMsg }
1740
- ],
1741
- max_tokens: maxTokens
1742
- }),
1743
- buildValidateBody: (model) => ({
1744
- model,
1745
- messages: [{ role: "user", content: "." }],
1746
- max_tokens: 1
1747
- }),
1748
- parseText: (json) => {
1749
- const data = json;
1750
- return data.choices[0]?.message?.content ?? "";
1751
- },
1752
- chatError: (status, body) => `OpenAI API error ${status}: ${body}`,
1753
- validateError: (status, body) => `OpenAI API error ${status}: ${body}`
1754
- },
1755
- ollama: {
1756
- defaultModel: "llama3",
1757
- defaultBaseUrl: "http://localhost:11434",
1758
- dailyTokens: 300,
1759
- weeklyTokens: 400,
1760
- chatUrl: (baseUrl) => `${baseUrl}/api/chat`,
1761
- validateUrl: (baseUrl) => `${baseUrl}/api/tags`,
1762
- validateMethod: "GET",
1763
- headers: () => ({ "Content-Type": "application/json" }),
1764
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1765
- model,
1766
- stream: false,
1767
- messages: [
1768
- { role: "system", content: system },
1769
- { role: "user", content: userMsg }
1770
- ],
1771
- options: { num_predict: maxTokens, temperature: 0.3 }
1772
- }),
1773
- parseText: (json) => {
1774
- return json.message?.content ?? "";
1775
- },
1776
- chatError: (status, body) => `Ollama error ${status}: ${body}. Is Ollama running? Try: ollama serve`,
1777
- validateError: (_status, _body, baseUrl) => `Ollama is not reachable at ${baseUrl}. Is it running? Try: ollama serve`
1778
- },
1779
- gemini: {
1780
- defaultModel: "gemini-2.0-flash-lite",
1781
- defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
1782
- dailyTokens: 1024,
1783
- weeklyTokens: 1024,
1784
- // API key and model are embedded in the URL — not in headers
1785
- chatUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
1786
- validateUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
1787
- validateMethod: "POST",
1788
- headers: () => ({ "Content-Type": "application/json" }),
1789
- buildChatBody: (_model, system, userMsg, maxTokens) => ({
1790
- system_instruction: { parts: [{ text: system }] },
1791
- contents: [{ role: "user", parts: [{ text: userMsg }] }],
1792
- generationConfig: { maxOutputTokens: maxTokens, temperature: 0.3 }
1793
- }),
1794
- buildValidateBody: () => ({
1795
- contents: [{ role: "user", parts: [{ text: "." }] }],
1796
- generationConfig: { maxOutputTokens: 1 }
1797
- }),
1798
- parseText: (json) => {
1799
- const data = json;
1800
- return data.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
1801
- },
1802
- chatError: (status, body) => `Gemini API error ${status}: ${body}`,
1803
- validateError: (status, body) => `Gemini API error ${status}: ${body}`
1804
- },
1805
- groq: {
1806
- defaultModel: "llama-3.1-8b-instant",
1807
- defaultBaseUrl: "https://api.groq.com/openai/v1",
1808
- dailyTokens: 1024,
1809
- weeklyTokens: 1024,
1810
- chatUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1811
- validateUrl: (baseUrl) => `${baseUrl}/models`,
1812
- validateMethod: "GET",
1813
- headers: (apiKey) => ({
1814
- "Content-Type": "application/json",
1815
- Authorization: `Bearer ${apiKey}`
1816
- }),
1817
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1818
- model,
1819
- messages: [
1820
- { role: "system", content: system },
1821
- { role: "user", content: userMsg }
1822
- ],
1823
- max_tokens: maxTokens
1824
- }),
1825
- parseText: (json) => {
1826
- const data = json;
1827
- return data.choices?.[0]?.message?.content ?? "";
1828
- },
1829
- chatError: (status, body) => `Groq API error ${status}: ${body}`,
1830
- validateError: (status, body) => `Groq API error ${status}: ${body}`
1879
+ var PROVIDER_CONFIGS;
1880
+ var init_provider_config = __esm({
1881
+ "src/ai/provider-config.ts"() {
1882
+ "use strict";
1883
+ PROVIDER_CONFIGS = {
1884
+ anthropic: {
1885
+ defaultModel: "claude-haiku-4-5-20251001",
1886
+ defaultBaseUrl: "https://api.anthropic.com/v1",
1887
+ dailyTokens: 1024,
1888
+ weeklyTokens: 1024,
1889
+ chatUrl: (baseUrl) => `${baseUrl}/messages`,
1890
+ validateUrl: (baseUrl) => `${baseUrl}/messages`,
1891
+ validateMethod: "POST",
1892
+ headers: (apiKey) => ({
1893
+ "Content-Type": "application/json",
1894
+ "x-api-key": apiKey,
1895
+ "anthropic-version": "2023-06-01"
1896
+ }),
1897
+ buildChatBody: (model, system, userMsg, maxTokens) => ({
1898
+ model,
1899
+ max_tokens: maxTokens,
1900
+ system,
1901
+ messages: [{ role: "user", content: userMsg }]
1902
+ }),
1903
+ buildValidateBody: (model) => ({
1904
+ model,
1905
+ max_tokens: 1,
1906
+ messages: [{ role: "user", content: "." }]
1907
+ }),
1908
+ parseText: (json) => {
1909
+ const data = json;
1910
+ return data.content.find((b) => b.type === "text")?.text ?? "";
1911
+ },
1912
+ chatError: (status, body) => `Anthropic API error ${status}: ${body}`,
1913
+ validateError: (status, body) => `Anthropic API error ${status}: ${body}`
1914
+ },
1915
+ openai: {
1916
+ defaultModel: "gpt-4o-mini",
1917
+ defaultBaseUrl: "https://api.openai.com/v1",
1918
+ dailyTokens: 1024,
1919
+ weeklyTokens: 1024,
1920
+ chatUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1921
+ validateUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1922
+ validateMethod: "POST",
1923
+ headers: (apiKey) => ({
1924
+ "Content-Type": "application/json",
1925
+ Authorization: `Bearer ${apiKey}`
1926
+ }),
1927
+ buildChatBody: (model, system, userMsg, maxTokens) => ({
1928
+ model,
1929
+ messages: [
1930
+ { role: "system", content: system },
1931
+ { role: "user", content: userMsg }
1932
+ ],
1933
+ max_tokens: maxTokens
1934
+ }),
1935
+ buildValidateBody: (model) => ({
1936
+ model,
1937
+ messages: [{ role: "user", content: "." }],
1938
+ max_tokens: 1
1939
+ }),
1940
+ parseText: (json) => {
1941
+ const data = json;
1942
+ return data.choices[0]?.message?.content ?? "";
1943
+ },
1944
+ chatError: (status, body) => `OpenAI API error ${status}: ${body}`,
1945
+ validateError: (status, body) => `OpenAI API error ${status}: ${body}`
1946
+ },
1947
+ ollama: {
1948
+ defaultModel: "llama3",
1949
+ defaultBaseUrl: "http://localhost:11434",
1950
+ dailyTokens: 300,
1951
+ weeklyTokens: 400,
1952
+ chatUrl: (baseUrl) => `${baseUrl}/api/chat`,
1953
+ validateUrl: (baseUrl) => `${baseUrl}/api/tags`,
1954
+ validateMethod: "GET",
1955
+ headers: () => ({ "Content-Type": "application/json" }),
1956
+ buildChatBody: (model, system, userMsg, maxTokens) => ({
1957
+ model,
1958
+ stream: false,
1959
+ messages: [
1960
+ { role: "system", content: system },
1961
+ { role: "user", content: userMsg }
1962
+ ],
1963
+ options: { num_predict: maxTokens, temperature: 0.3 }
1964
+ }),
1965
+ parseText: (json) => {
1966
+ return json.message?.content ?? "";
1967
+ },
1968
+ chatError: (status, body) => `Ollama error ${status}: ${body}. Is Ollama running? Try: ollama serve`,
1969
+ validateError: (_status, _body, baseUrl) => `Ollama is not reachable at ${baseUrl}. Is it running? Try: ollama serve`
1970
+ },
1971
+ gemini: {
1972
+ defaultModel: "gemini-2.0-flash-lite",
1973
+ defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
1974
+ dailyTokens: 1024,
1975
+ weeklyTokens: 1024,
1976
+ // API key and model are embedded in the URL — not in headers
1977
+ chatUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
1978
+ validateUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
1979
+ validateMethod: "POST",
1980
+ headers: () => ({ "Content-Type": "application/json" }),
1981
+ buildChatBody: (_model, system, userMsg, maxTokens) => ({
1982
+ system_instruction: { parts: [{ text: system }] },
1983
+ contents: [{ role: "user", parts: [{ text: userMsg }] }],
1984
+ generationConfig: { maxOutputTokens: maxTokens, temperature: 0.3 }
1985
+ }),
1986
+ buildValidateBody: () => ({
1987
+ contents: [{ role: "user", parts: [{ text: "." }] }],
1988
+ generationConfig: { maxOutputTokens: 1 }
1989
+ }),
1990
+ parseText: (json) => {
1991
+ const data = json;
1992
+ return data.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
1993
+ },
1994
+ chatError: (status, body) => `Gemini API error ${status}: ${body}`,
1995
+ validateError: (status, body) => `Gemini API error ${status}: ${body}`
1996
+ },
1997
+ groq: {
1998
+ defaultModel: "llama-3.1-8b-instant",
1999
+ defaultBaseUrl: "https://api.groq.com/openai/v1",
2000
+ dailyTokens: 1024,
2001
+ weeklyTokens: 1024,
2002
+ chatUrl: (baseUrl) => `${baseUrl}/chat/completions`,
2003
+ validateUrl: (baseUrl) => `${baseUrl}/models`,
2004
+ validateMethod: "GET",
2005
+ headers: (apiKey) => ({
2006
+ "Content-Type": "application/json",
2007
+ Authorization: `Bearer ${apiKey}`
2008
+ }),
2009
+ buildChatBody: (model, system, userMsg, maxTokens) => ({
2010
+ model,
2011
+ messages: [
2012
+ { role: "system", content: system },
2013
+ { role: "user", content: userMsg }
2014
+ ],
2015
+ max_tokens: maxTokens
2016
+ }),
2017
+ parseText: (json) => {
2018
+ const data = json;
2019
+ return data.choices?.[0]?.message?.content ?? "";
2020
+ },
2021
+ chatError: (status, body) => `Groq API error ${status}: ${body}`,
2022
+ validateError: (status, body) => `Groq API error ${status}: ${body}`
2023
+ }
2024
+ };
1831
2025
  }
1832
- };
2026
+ });
1833
2027
 
1834
2028
  // src/ai/router.ts
1835
2029
  function getProvider(config) {
@@ -1862,12 +2056,13 @@ Run: workscribe config set ai.apiKey <your-key>`);
1862
2056
  config.summary?.includeCategories ?? []
1863
2057
  );
1864
2058
  }
1865
-
1866
- // src/capture/shell-hook.ts
1867
- var import_fs12 = __toESM(require("fs"));
1868
- var import_path8 = __toESM(require("path"));
1869
- var import_os3 = __toESM(require("os"));
1870
- var import_child_process3 = require("child_process");
2059
+ var init_router = __esm({
2060
+ "src/ai/router.ts"() {
2061
+ "use strict";
2062
+ init_generic_provider();
2063
+ init_provider_config();
2064
+ }
2065
+ });
1871
2066
 
1872
2067
  // src/utils/completion-scripts.ts
1873
2068
  function bashCompletion(commands) {
@@ -1922,12 +2117,13 @@ function fishCompletion(commands) {
1922
2117
  ];
1923
2118
  return lines.join("\n");
1924
2119
  }
2120
+ var init_completion_scripts = __esm({
2121
+ "src/utils/completion-scripts.ts"() {
2122
+ "use strict";
2123
+ }
2124
+ });
1925
2125
 
1926
2126
  // src/capture/shell-hook.ts
1927
- var FISH_COMPLETIONS_DIR = import_path8.default.join(import_os3.default.homedir(), ".config", "fish", "completions");
1928
- var HOOK_START = "# workscribe:hook:start";
1929
- var HOOK_END = "# workscribe:hook:end";
1930
- var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
1931
2127
  function getInstalledHookBlock(rcFile) {
1932
2128
  if (!import_fs12.default.existsSync(rcFile)) return null;
1933
2129
  const content = import_fs12.default.readFileSync(rcFile, "utf-8");
@@ -1982,9 +2178,10 @@ function resolveWorkscribeBinary() {
1982
2178
  return null;
1983
2179
  }
1984
2180
  }
1985
- function injectBinaryPath(hookContent, binaryPath) {
2181
+ function injectBinaryPath(hookContent, binaryPath, nodePath) {
2182
+ const pinnedInvocation = `"${nodePath}" "${binaryPath}"`;
1986
2183
  return hookContent.replace("command -v workscribe > /dev/null 2>&1 || return 0", `[[ -x "${binaryPath}" ]] || return 0`).replace("command -v workscribe &>/dev/null || return 0", `[[ -x "${binaryPath}" ]] || return 0`).replace("type -q workscribe\n or return 0", `test -x "${binaryPath}"
1987
- or return 0`).replaceAll("command workscribe _capture", `"${binaryPath}" _capture`).replaceAll("command workscribe alerts", `"${binaryPath}" alerts`);
2184
+ or return 0`).replaceAll("command workscribe _capture", `${pinnedInvocation} _capture`).replaceAll("command workscribe alerts", `${pinnedInvocation} alerts`);
1988
2185
  }
1989
2186
  function isCompletionsInstalled(shell) {
1990
2187
  if (shell.name === "fish") {
@@ -2017,14 +2214,25 @@ source "${cacheFile}"
2017
2214
  import_fs12.default.appendFileSync(shell.rcFile, sourceLine);
2018
2215
  }
2019
2216
  }
2020
-
2021
- // src/capture/hooks/claude-code.ts
2022
- var import_fs13 = __toESM(require("fs"));
2023
- var import_path9 = __toESM(require("path"));
2024
- var import_os4 = __toESM(require("os"));
2217
+ var import_fs12, import_path8, import_os3, import_child_process3, FISH_COMPLETIONS_DIR, HOOK_START, HOOK_END, HOOK_PREAMBLE;
2218
+ var init_shell_hook = __esm({
2219
+ "src/capture/shell-hook.ts"() {
2220
+ "use strict";
2221
+ import_fs12 = __toESM(require("fs"));
2222
+ import_path8 = __toESM(require("path"));
2223
+ import_os3 = __toESM(require("os"));
2224
+ import_child_process3 = require("child_process");
2225
+ init_logger();
2226
+ init_completion_scripts();
2227
+ init_paths();
2228
+ FISH_COMPLETIONS_DIR = import_path8.default.join(import_os3.default.homedir(), ".config", "fish", "completions");
2229
+ HOOK_START = "# workscribe:hook:start";
2230
+ HOOK_END = "# workscribe:hook:end";
2231
+ HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
2232
+ }
2233
+ });
2025
2234
 
2026
2235
  // src/capture/hooks/utils.ts
2027
- var import_child_process4 = require("child_process");
2028
2236
  function isCommandInstalled(cmd) {
2029
2237
  try {
2030
2238
  (0, import_child_process4.execSync)(`which ${cmd}`, { stdio: "ignore" });
@@ -2033,9 +2241,15 @@ function isCommandInstalled(cmd) {
2033
2241
  return false;
2034
2242
  }
2035
2243
  }
2244
+ var import_child_process4;
2245
+ var init_utils = __esm({
2246
+ "src/capture/hooks/utils.ts"() {
2247
+ "use strict";
2248
+ import_child_process4 = require("child_process");
2249
+ }
2250
+ });
2036
2251
 
2037
2252
  // src/capture/hooks/claude-code.ts
2038
- var CLAUDE_SETTINGS_PATH = import_path9.default.join(import_os4.default.homedir(), ".claude", "settings.json");
2039
2253
  function readSettings() {
2040
2254
  if (!import_fs13.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
2041
2255
  try {
@@ -2044,86 +2258,155 @@ function readSettings() {
2044
2258
  return {};
2045
2259
  }
2046
2260
  }
2047
- var claudeCodeIntegration = {
2048
- displayName: "Claude Code",
2049
- isInstalled() {
2050
- return isCommandInstalled("claude");
2051
- },
2052
- isHookInstalled() {
2053
- const settings = readSettings();
2054
- const hooks = settings.hooks ?? {};
2055
- const postToolUse = hooks.PostToolUse ?? [];
2056
- const hasPostToolUse = postToolUse.some((entry) => {
2057
- const entryHooks = entry.hooks ?? [];
2058
- return entryHooks.some((hookItem) => {
2059
- const hook = hookItem;
2060
- return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
2061
- });
2062
- });
2063
- const userPromptSubmit = hooks.UserPromptSubmit ?? [];
2064
- const hasUserPromptSubmit = userPromptSubmit.some((entry) => {
2065
- const entryHooks = entry.hooks ?? [];
2066
- return entryHooks.some((hookItem) => {
2067
- const hook = hookItem;
2068
- return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
2069
- });
2261
+ function commandsForEvent(hooks, eventName) {
2262
+ const entries = hooks[eventName] ?? [];
2263
+ const commands = [];
2264
+ for (const entry of entries) {
2265
+ const entryHooks = entry.hooks ?? [];
2266
+ for (const hookItem of entryHooks) {
2267
+ const hook = hookItem;
2268
+ if (typeof hook.command === "string" && hook.command.includes(HOOK_MARKER)) {
2269
+ commands.push(hook.command);
2270
+ }
2271
+ }
2272
+ }
2273
+ return commands;
2274
+ }
2275
+ function findWorkscribeHookCommands(settings) {
2276
+ const hooks = settings.hooks ?? {};
2277
+ return {
2278
+ postToolUse: commandsForEvent(hooks, "PostToolUse"),
2279
+ userPromptSubmit: commandsForEvent(hooks, "UserPromptSubmit")
2280
+ };
2281
+ }
2282
+ function isPinnedCommand(command) {
2283
+ const trimmed = command.trimStart();
2284
+ return trimmed.startsWith('"') || trimmed.startsWith("/");
2285
+ }
2286
+ function withoutWorkscribeEntries(entries) {
2287
+ return entries.map((entry) => {
2288
+ const record = entry;
2289
+ const entryHooks = record.hooks ?? [];
2290
+ const filteredHooks = entryHooks.filter((hookItem) => {
2291
+ const hook = hookItem;
2292
+ return !(typeof hook.command === "string" && hook.command.includes(HOOK_MARKER));
2070
2293
  });
2071
- return hasPostToolUse && hasUserPromptSubmit;
2072
- },
2073
- install() {
2074
- const settings = readSettings();
2075
- const existingHooks = settings.hooks ?? {};
2076
- const existingPostToolUse = existingHooks.PostToolUse ?? [];
2077
- const existingUserPromptSubmit = existingHooks.UserPromptSubmit ?? [];
2078
- settings.hooks = {
2079
- ...existingHooks,
2080
- PostToolUse: [
2081
- ...existingPostToolUse,
2082
- {
2083
- matcher: "Bash",
2084
- hooks: [{ type: "command", command: "workscribe _hook --source claude-code", async: true }]
2085
- }
2086
- ],
2087
- UserPromptSubmit: [
2088
- ...existingUserPromptSubmit,
2089
- {
2090
- hooks: [
2294
+ return { ...record, hooks: filteredHooks };
2295
+ }).filter((entry) => entry.hooks.length > 0);
2296
+ }
2297
+ function checkHookHealth() {
2298
+ const { postToolUse, userPromptSubmit } = findWorkscribeHookCommands(readSettings());
2299
+ const allCommands = [...postToolUse, ...userPromptSubmit];
2300
+ if (allCommands.length === 0) return { installed: false, pinned: true, confirmedBroken: null };
2301
+ if (allCommands.every(isPinnedCommand)) return { installed: true, pinned: true, confirmedBroken: null };
2302
+ let confirmedBroken;
2303
+ try {
2304
+ (0, import_child_process5.execFileSync)("workscribe", ["--version"], { stdio: "ignore", timeout: 2e3 });
2305
+ confirmedBroken = false;
2306
+ } catch {
2307
+ confirmedBroken = true;
2308
+ }
2309
+ return { installed: true, pinned: false, confirmedBroken };
2310
+ }
2311
+ var import_fs13, import_path9, import_os4, import_child_process5, CLAUDE_SETTINGS_PATH, HOOK_MARKER, claudeCodeIntegration;
2312
+ var init_claude_code = __esm({
2313
+ "src/capture/hooks/claude-code.ts"() {
2314
+ "use strict";
2315
+ import_fs13 = __toESM(require("fs"));
2316
+ import_path9 = __toESM(require("path"));
2317
+ import_os4 = __toESM(require("os"));
2318
+ import_child_process5 = require("child_process");
2319
+ init_utils();
2320
+ init_shell_hook();
2321
+ CLAUDE_SETTINGS_PATH = import_path9.default.join(import_os4.default.homedir(), ".claude", "settings.json");
2322
+ HOOK_MARKER = "_hook --source claude-code";
2323
+ claudeCodeIntegration = {
2324
+ displayName: "Claude Code",
2325
+ isInstalled() {
2326
+ return isCommandInstalled("claude");
2327
+ },
2328
+ // "Installed" means a *correctly pinned* hook is present, not just any
2329
+ // workscribe hook — this is deliberate: it makes `workscribe init`'s
2330
+ // existing prompt-and-install flow naturally offer to repair a stale,
2331
+ // unpinned hook from before this fix, with no new UI code needed there.
2332
+ isHookInstalled() {
2333
+ const { postToolUse, userPromptSubmit } = findWorkscribeHookCommands(readSettings());
2334
+ if (postToolUse.length === 0 || userPromptSubmit.length === 0) return false;
2335
+ return [...postToolUse, ...userPromptSubmit].every(isPinnedCommand);
2336
+ },
2337
+ install() {
2338
+ const settings = readSettings();
2339
+ const existingHooks = settings.hooks ?? {};
2340
+ const existingPostToolUse = withoutWorkscribeEntries(existingHooks.PostToolUse ?? []);
2341
+ const existingUserPromptSubmit = withoutWorkscribeEntries(existingHooks.UserPromptSubmit ?? []);
2342
+ const workscribeBinary = resolveWorkscribeBinary();
2343
+ const baseCommand = workscribeBinary ? `"${process.execPath}" "${workscribeBinary}"` : "workscribe";
2344
+ settings.hooks = {
2345
+ ...existingHooks,
2346
+ PostToolUse: [
2347
+ ...existingPostToolUse,
2348
+ {
2349
+ matcher: "Bash",
2350
+ hooks: [{ type: "command", command: `${baseCommand} ${HOOK_MARKER}`, async: true }]
2351
+ }
2352
+ ],
2353
+ UserPromptSubmit: [
2354
+ ...existingUserPromptSubmit,
2091
2355
  {
2092
- type: "command",
2093
- command: "workscribe _hook --source claude-code --event UserPromptSubmit"
2356
+ hooks: [
2357
+ {
2358
+ type: "command",
2359
+ command: `${baseCommand} ${HOOK_MARKER} --event UserPromptSubmit`
2360
+ }
2361
+ ]
2094
2362
  }
2095
2363
  ]
2096
- }
2097
- ]
2364
+ };
2365
+ import_fs13.default.mkdirSync(import_path9.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
2366
+ import_fs13.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
2367
+ }
2098
2368
  };
2099
- import_fs13.default.mkdirSync(import_path9.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
2100
- import_fs13.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
2101
2369
  }
2102
- };
2370
+ });
2103
2371
 
2104
2372
  // src/capture/hooks/index.ts
2105
- var AI_HOOK_INTEGRATIONS = {
2106
- "claude-code": claudeCodeIntegration
2107
- };
2373
+ var AI_HOOK_INTEGRATIONS;
2374
+ var init_hooks = __esm({
2375
+ "src/capture/hooks/index.ts"() {
2376
+ "use strict";
2377
+ init_claude_code();
2378
+ AI_HOOK_INTEGRATIONS = {
2379
+ "claude-code": claudeCodeIntegration
2380
+ };
2381
+ }
2382
+ });
2108
2383
 
2109
2384
  // src/cli/completions.ts
2110
- var HIDDEN_COMMANDS = /* @__PURE__ */ new Set(["_capture", "_hook"]);
2111
- function getPublicCommands(program2) {
2112
- return program2.commands.filter((command) => !HIDDEN_COMMANDS.has(command.name())).map((command) => ({ name: command.name(), description: command.description() }));
2385
+ function getPublicCommands(program) {
2386
+ return program.commands.filter((command) => !HIDDEN_COMMANDS.has(command.name())).map((command) => ({ name: command.name(), description: command.description() }));
2113
2387
  }
2114
- function registerCompletions(program2) {
2115
- program2.command("completions [shell]").description("Print shell completion script (bash, zsh, fish)").action((shell) => {
2388
+ function registerCompletions(program) {
2389
+ program.command("completions [shell]").description("Print shell completion script (bash, zsh, fish)").action((shell) => {
2116
2390
  const detected = shell ?? (process.env.SHELL ?? "").split("/").at(-1);
2117
2391
  if (!detected || !["bash", "zsh", "fish"].includes(detected)) {
2118
2392
  log.error(`Unknown shell: "${detected ?? ""}". Supported: bash, zsh, fish`);
2119
2393
  log.plain(` Usage: workscribe completions <bash|zsh|fish>`);
2120
2394
  process.exit(1);
2121
2395
  }
2122
- const commands = getPublicCommands(program2);
2396
+ const commands = getPublicCommands(program);
2123
2397
  const script = detected === "bash" ? bashCompletion(commands) : detected === "zsh" ? zshCompletion(commands) : fishCompletion(commands);
2124
2398
  process.stdout.write(script + "\n");
2125
2399
  });
2126
2400
  }
2401
+ var HIDDEN_COMMANDS;
2402
+ var init_completions = __esm({
2403
+ "src/cli/completions.ts"() {
2404
+ "use strict";
2405
+ init_logger();
2406
+ init_completion_scripts();
2407
+ HIDDEN_COMMANDS = /* @__PURE__ */ new Set(["_capture", "_hook"]);
2408
+ }
2409
+ });
2127
2410
 
2128
2411
  // src/cli/init.ts
2129
2412
  function detectShell() {
@@ -2140,8 +2423,8 @@ function step(current, total, label) {
2140
2423
  log.plain(import_chalk2.default.bold(`
2141
2424
  [${current}/${total}] ${label}`));
2142
2425
  }
2143
- function registerInit(program2) {
2144
- program2.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
2426
+ function registerInit(program) {
2427
+ program.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
2145
2428
  const TOTAL_STEPS = 5;
2146
2429
  log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
2147
2430
  step(1, TOTAL_STEPS, "Creating data directories");
@@ -2177,7 +2460,7 @@ function registerInit(program2) {
2177
2460
  } else {
2178
2461
  const hookContent = import_fs14.default.readFileSync(hookFile, "utf-8");
2179
2462
  const binaryPath = resolveWorkscribeBinary();
2180
- const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath) : hookContent;
2463
+ const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath, process.execPath) : hookContent;
2181
2464
  if (shell.name === "fish") {
2182
2465
  const confDir = import_path10.default.dirname(shell.rcFile);
2183
2466
  if (!import_fs14.default.existsSync(confDir)) import_fs14.default.mkdirSync(confDir, { recursive: true });
@@ -2219,7 +2502,7 @@ function registerInit(program2) {
2219
2502
  });
2220
2503
  if (installComps) {
2221
2504
  try {
2222
- installCompletions(shell, getPublicCommands(program2));
2505
+ installCompletions(shell, getPublicCommands(program));
2223
2506
  log.success("Shell completions installed");
2224
2507
  } catch (error) {
2225
2508
  log.warn(`Completions install failed: ${error.message}`);
@@ -2390,12 +2673,32 @@ function registerInit(program2) {
2390
2673
  log.plain("");
2391
2674
  });
2392
2675
  }
2676
+ var import_fs14, import_path10, import_os5, import_prompts, import_chalk2;
2677
+ var init_init = __esm({
2678
+ "src/cli/init.ts"() {
2679
+ "use strict";
2680
+ import_fs14 = __toESM(require("fs"));
2681
+ import_path10 = __toESM(require("path"));
2682
+ import_os5 = __toESM(require("os"));
2683
+ import_prompts = __toESM(require("prompts"));
2684
+ import_chalk2 = __toESM(require("chalk"));
2685
+ init_paths();
2686
+ init_config();
2687
+ init_loader();
2688
+ init_fetcher();
2689
+ init_db();
2690
+ init_router();
2691
+ init_logger();
2692
+ init_registry();
2693
+ init_shell_hook();
2694
+ init_hooks();
2695
+ init_completions();
2696
+ }
2697
+ });
2393
2698
 
2394
2699
  // src/cli/status.ts
2395
- var import_fs15 = __toESM(require("fs"));
2396
- var import_chalk3 = __toESM(require("chalk"));
2397
- function registerStatus(program2) {
2398
- program2.command("status").description("Show capture status and event counts").action(() => {
2700
+ function registerStatus(program) {
2701
+ program.command("status").description("Show capture status and event counts").action(() => {
2399
2702
  const config = readConfig();
2400
2703
  const total = getEventCount();
2401
2704
  const todayEvents = getEventsForDate(todayDate());
@@ -2430,6 +2733,20 @@ function registerStatus(program2) {
2430
2733
  ` ${import_chalk3.default.yellow("!")} ${import_chalk3.default.yellow(String(pendingAlerts.length))} pending security alert${pendingAlerts.length === 1 ? "" : "s"}` + import_chalk3.default.dim(" (run: workscribe alerts)")
2431
2734
  );
2432
2735
  }
2736
+ const hookHealth = checkHookHealth();
2737
+ if (hookHealth.installed && !hookHealth.pinned) {
2738
+ log.plain("");
2739
+ if (hookHealth.confirmedBroken) {
2740
+ log.plain(
2741
+ ` ${import_chalk3.default.yellow("\u26A0")} Claude Code hook is using an unpinned command and is failing right now \u2014 Claude Code activity is not being captured.`
2742
+ );
2743
+ } else {
2744
+ log.plain(
2745
+ ` ${import_chalk3.default.yellow("\u26A0")} Claude Code hook is using an unpinned command, which can silently break if your default Node version changes.`
2746
+ );
2747
+ }
2748
+ log.plain(import_chalk3.default.dim(" Run workscribe init to repair it"));
2749
+ }
2433
2750
  const { total: weekTotal, otherCount } = getRecentOtherRate(7);
2434
2751
  if (weekTotal > 0) {
2435
2752
  const otherPct = Math.round(otherCount / weekTotal * 100);
@@ -2448,30 +2765,26 @@ function registerStatus(program2) {
2448
2765
  log.plain("");
2449
2766
  });
2450
2767
  }
2768
+ var import_fs15, import_chalk3;
2769
+ var init_status = __esm({
2770
+ "src/cli/status.ts"() {
2771
+ "use strict";
2772
+ import_fs15 = __toESM(require("fs"));
2773
+ import_chalk3 = __toESM(require("chalk"));
2774
+ init_db();
2775
+ init_config();
2776
+ init_logger();
2777
+ init_format();
2778
+ init_db();
2779
+ init_paths();
2780
+ init_alerts2();
2781
+ init_claude_code();
2782
+ }
2783
+ });
2451
2784
 
2452
2785
  // src/cli/events.ts
2453
- var import_chalk4 = __toESM(require("chalk"));
2454
- var CATEGORY_COLORS = {
2455
- shipping: import_chalk4.default.green,
2456
- versioning: import_chalk4.default.blue,
2457
- building: import_chalk4.default.cyan,
2458
- running: import_chalk4.default.white,
2459
- testing: import_chalk4.default.magenta,
2460
- linting: import_chalk4.default.yellow,
2461
- dependencies: import_chalk4.default.yellow,
2462
- infrastructure: import_chalk4.default.red,
2463
- releasing: import_chalk4.default.bold.green,
2464
- database: import_chalk4.default.bold.blue,
2465
- files: import_chalk4.default.dim.yellow,
2466
- exploring: import_chalk4.default.dim.cyan,
2467
- ai_session: import_chalk4.default.bold.magenta,
2468
- other: import_chalk4.default.gray
2469
- };
2470
- var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
2471
- loadAIToolRegistry().map((entry) => [entry.tool, entry.displayName])
2472
- );
2473
- function registerEvents(program2) {
2474
- program2.command("events").description("List captured events").option("--date <YYYY-MM-DD>", "date to list events for (default: today)").option("--limit <n>", "max events to show", parseInt).action((opts) => {
2786
+ function registerEvents(program) {
2787
+ program.command("events").description("List captured events").option("--date <YYYY-MM-DD>", "date to list events for (default: today)").option("--limit <n>", "max events to show", parseInt).action((opts) => {
2475
2788
  let date = todayDate();
2476
2789
  if (opts.date) {
2477
2790
  try {
@@ -2518,12 +2831,38 @@ function registerEvents(program2) {
2518
2831
  log.plain("");
2519
2832
  });
2520
2833
  }
2521
-
2522
- // src/cli/sessions.ts
2523
- var import_chalk5 = __toESM(require("chalk"));
2834
+ var import_chalk4, CATEGORY_COLORS, AI_TOOL_DISPLAY_NAMES;
2835
+ var init_events = __esm({
2836
+ "src/cli/events.ts"() {
2837
+ "use strict";
2838
+ import_chalk4 = __toESM(require("chalk"));
2839
+ init_db();
2840
+ init_logger();
2841
+ init_format();
2842
+ init_registry();
2843
+ CATEGORY_COLORS = {
2844
+ shipping: import_chalk4.default.green,
2845
+ versioning: import_chalk4.default.blue,
2846
+ building: import_chalk4.default.cyan,
2847
+ running: import_chalk4.default.white,
2848
+ testing: import_chalk4.default.magenta,
2849
+ linting: import_chalk4.default.yellow,
2850
+ dependencies: import_chalk4.default.yellow,
2851
+ infrastructure: import_chalk4.default.red,
2852
+ releasing: import_chalk4.default.bold.green,
2853
+ database: import_chalk4.default.bold.blue,
2854
+ files: import_chalk4.default.dim.yellow,
2855
+ exploring: import_chalk4.default.dim.cyan,
2856
+ ai_session: import_chalk4.default.bold.magenta,
2857
+ other: import_chalk4.default.gray
2858
+ };
2859
+ AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
2860
+ loadAIToolRegistry().map((entry) => [entry.tool, entry.displayName])
2861
+ );
2862
+ }
2863
+ });
2524
2864
 
2525
2865
  // src/sessions/grouper.ts
2526
- var LONG_RUNNING_THRESHOLD_MS = 5 * 60 * 1e3;
2527
2866
  function splitByIdleGap(events, idleMs) {
2528
2867
  if (events.length === 0) return [];
2529
2868
  const groups = [[events[0]]];
@@ -2540,7 +2879,6 @@ function splitByIdleGap(events, idleMs) {
2540
2879
  }
2541
2880
  return groups;
2542
2881
  }
2543
- var REPO_SWITCH_GAP_MS = 10 * 60 * 1e3;
2544
2882
  function splitByRepo(events, gapMs = 0) {
2545
2883
  if (events.length === 0) return [];
2546
2884
  const groups = [[events[0]]];
@@ -2561,6 +2899,14 @@ function splitByRepo(events, gapMs = 0) {
2561
2899
  function minutesBetween(start, end) {
2562
2900
  return (new Date(end).getTime() - new Date(start).getTime()) / 6e4;
2563
2901
  }
2902
+ var LONG_RUNNING_THRESHOLD_MS, REPO_SWITCH_GAP_MS;
2903
+ var init_grouper = __esm({
2904
+ "src/sessions/grouper.ts"() {
2905
+ "use strict";
2906
+ LONG_RUNNING_THRESHOLD_MS = 5 * 60 * 1e3;
2907
+ REPO_SWITCH_GAP_MS = 10 * 60 * 1e3;
2908
+ }
2909
+ });
2564
2910
 
2565
2911
  // src/sessions/detector.ts
2566
2912
  function getOrBuildSessions(date) {
@@ -2628,10 +2974,18 @@ function buildSessions(date, events, savedSummaries = /* @__PURE__ */ new Map())
2628
2974
  }
2629
2975
  return sessions;
2630
2976
  }
2977
+ var init_detector = __esm({
2978
+ "src/sessions/detector.ts"() {
2979
+ "use strict";
2980
+ init_db();
2981
+ init_config();
2982
+ init_grouper();
2983
+ }
2984
+ });
2631
2985
 
2632
2986
  // src/cli/sessions.ts
2633
- function registerSessions(program2) {
2634
- program2.command("sessions").description("List detected work sessions").option("--date <YYYY-MM-DD>", "date to show sessions for (default: today)").action((opts) => {
2987
+ function registerSessions(program) {
2988
+ program.command("sessions").description("List detected work sessions").option("--date <YYYY-MM-DD>", "date to show sessions for (default: today)").action((opts) => {
2635
2989
  let date = todayDate();
2636
2990
  if (opts.date) {
2637
2991
  try {
@@ -2671,12 +3025,16 @@ function registerSessions(program2) {
2671
3025
  log.plain("");
2672
3026
  });
2673
3027
  }
2674
-
2675
- // src/cli/summary.ts
2676
- var import_chalk6 = __toESM(require("chalk"));
2677
- var import_fs16 = __toESM(require("fs"));
2678
- var import_os6 = __toESM(require("os"));
2679
- var import_path11 = __toESM(require("path"));
3028
+ var import_chalk5;
3029
+ var init_sessions = __esm({
3030
+ "src/cli/sessions.ts"() {
3031
+ "use strict";
3032
+ import_chalk5 = __toESM(require("chalk"));
3033
+ init_detector();
3034
+ init_logger();
3035
+ init_format();
3036
+ }
3037
+ });
2680
3038
 
2681
3039
  // src/integrations/webhook.ts
2682
3040
  async function postToWebhook(url, text) {
@@ -2694,6 +3052,12 @@ async function postToWebhook(url, text) {
2694
3052
  throw new Error(`Webhook POST failed ${response.status}: ${body}`);
2695
3053
  }
2696
3054
  }
3055
+ var init_webhook = __esm({
3056
+ "src/integrations/webhook.ts"() {
3057
+ "use strict";
3058
+ init_fetch();
3059
+ }
3060
+ });
2697
3061
 
2698
3062
  // src/integrations/slack.ts
2699
3063
  function formatForSlack(markdown, username) {
@@ -2704,6 +3068,11 @@ ${body}` : body;
2704
3068
  function isSlackWebhookUrl(url) {
2705
3069
  return url.startsWith("https://hooks.slack.com/");
2706
3070
  }
3071
+ var init_slack = __esm({
3072
+ "src/integrations/slack.ts"() {
3073
+ "use strict";
3074
+ }
3075
+ });
2707
3076
 
2708
3077
  // src/cli/summary.ts
2709
3078
  function parseBullets(text) {
@@ -2755,8 +3124,8 @@ async function handleWebhook(opts, config, summary) {
2755
3124
  log.warn("--slack requires integrations.slackWebhook to be set in config");
2756
3125
  }
2757
3126
  }
2758
- function registerSummary(program2) {
2759
- program2.command("summary").description("Generate an AI work summary").option("--date <YYYY-MM-DD>", "date to summarize (default: today)").option("--week", "summarize the current week").option("--fresh", "bypass cache and regenerate").option("--verbose", "show all captured events including those not sent to AI").option("--slack", "post summary to Slack webhook from config").option("--webhook <url>", "post summary to a custom webhook URL").action(async (opts) => {
3127
+ function registerSummary(program) {
3128
+ program.command("summary").description("Generate an AI work summary").option("--date <YYYY-MM-DD>", "date to summarize (default: today)").option("--week", "summarize the current week").option("--fresh", "bypass cache and regenerate").option("--verbose", "show all captured events including those not sent to AI").option("--slack", "post summary to Slack webhook from config").option("--webhook <url>", "post summary to a custom webhook URL").action(async (opts) => {
2760
3129
  const config = readConfig();
2761
3130
  let provider;
2762
3131
  try {
@@ -2916,10 +3285,28 @@ ${bullets.trim()}`;
2916
3285
  await handleWebhook(opts, config, fullSummary);
2917
3286
  });
2918
3287
  }
3288
+ var import_chalk6, import_fs16, import_os6, import_path11;
3289
+ var init_summary = __esm({
3290
+ "src/cli/summary.ts"() {
3291
+ "use strict";
3292
+ import_chalk6 = __toESM(require("chalk"));
3293
+ import_fs16 = __toESM(require("fs"));
3294
+ import_os6 = __toESM(require("os"));
3295
+ import_path11 = __toESM(require("path"));
3296
+ init_detector();
3297
+ init_router();
3298
+ init_config();
3299
+ init_db();
3300
+ init_logger();
3301
+ init_format();
3302
+ init_webhook();
3303
+ init_slack();
3304
+ init_paths();
3305
+ init_prompt();
3306
+ }
3307
+ });
2919
3308
 
2920
3309
  // src/cli/export.ts
2921
- var import_fs17 = __toESM(require("fs"));
2922
- var import_path12 = __toESM(require("path"));
2923
3310
  function eventRowToPayload2(row) {
2924
3311
  let metadata = {};
2925
3312
  try {
@@ -2939,8 +3326,8 @@ function eventRowToPayload2(row) {
2939
3326
  }
2940
3327
  return payload;
2941
3328
  }
2942
- function registerExport(program2) {
2943
- program2.command("export").description("Export work summary as a markdown file").option("--date <YYYY-MM-DD>", "date to export (default: today)").option("--output <path>", "output file path (default: ~/.workscribe/exports/)").action(async (opts) => {
3329
+ function registerExport(program) {
3330
+ program.command("export").description("Export work summary as a markdown file").option("--date <YYYY-MM-DD>", "date to export (default: today)").option("--output <path>", "output file path (default: ~/.workscribe/exports/)").action(async (opts) => {
2944
3331
  let date = todayDate();
2945
3332
  if (opts.date) {
2946
3333
  try {
@@ -2984,11 +3371,24 @@ ${summary}
2984
3371
  log.success(`Saved: ${outputPath}`);
2985
3372
  });
2986
3373
  }
3374
+ var import_fs17, import_path12;
3375
+ var init_export = __esm({
3376
+ "src/cli/export.ts"() {
3377
+ "use strict";
3378
+ import_fs17 = __toESM(require("fs"));
3379
+ import_path12 = __toESM(require("path"));
3380
+ init_detector();
3381
+ init_router();
3382
+ init_config();
3383
+ init_logger();
3384
+ init_format();
3385
+ init_paths();
3386
+ }
3387
+ });
2987
3388
 
2988
3389
  // src/cli/config.ts
2989
- var import_chalk7 = __toESM(require("chalk"));
2990
- function registerConfig(program2) {
2991
- const configCmd = program2.command("config").description("View or update Workscribe configuration").action(() => {
3390
+ function registerConfig(program) {
3391
+ const configCmd = program.command("config").description("View or update Workscribe configuration").action(() => {
2992
3392
  const config = readConfig();
2993
3393
  log.plain("");
2994
3394
  log.plain(import_chalk7.default.bold(" Workscribe Config"));
@@ -3019,16 +3419,6 @@ function registerConfig(program2) {
3019
3419
  }
3020
3420
  });
3021
3421
  }
3022
- var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
3023
- "apiKey",
3024
- "api_key",
3025
- "secret",
3026
- "token",
3027
- "password",
3028
- "passwd",
3029
- "webhook",
3030
- "slackWebhook"
3031
- ]);
3032
3422
  function maskValue(key, value) {
3033
3423
  if (SENSITIVE_KEYS.has(key) && typeof value === "string" && value.length > 0) {
3034
3424
  return import_chalk7.default.dim(`${value.slice(0, 4)}${"*".repeat(8)} (${value.length} chars)`);
@@ -3047,11 +3437,29 @@ function printConfig(obj, indent) {
3047
3437
  }
3048
3438
  }
3049
3439
  }
3440
+ var import_chalk7, SENSITIVE_KEYS;
3441
+ var init_config2 = __esm({
3442
+ "src/cli/config.ts"() {
3443
+ "use strict";
3444
+ import_chalk7 = __toESM(require("chalk"));
3445
+ init_config();
3446
+ init_logger();
3447
+ SENSITIVE_KEYS = /* @__PURE__ */ new Set([
3448
+ "apiKey",
3449
+ "api_key",
3450
+ "secret",
3451
+ "token",
3452
+ "password",
3453
+ "passwd",
3454
+ "webhook",
3455
+ "slackWebhook"
3456
+ ]);
3457
+ }
3458
+ });
3050
3459
 
3051
3460
  // src/cli/pause.ts
3052
- var import_fs18 = __toESM(require("fs"));
3053
- function registerPause(program2) {
3054
- program2.command("pause").description("Pause capture temporarily").action(() => {
3461
+ function registerPause(program) {
3462
+ program.command("pause").description("Pause capture temporarily").action(() => {
3055
3463
  if (import_fs18.default.existsSync(PAUSE_FLAG_PATH)) {
3056
3464
  log.info("Capture is already paused. Run workscribe resume to restart.");
3057
3465
  return;
@@ -3059,7 +3467,7 @@ function registerPause(program2) {
3059
3467
  import_fs18.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
3060
3468
  log.success("Capture paused. Run workscribe resume to restart.");
3061
3469
  });
3062
- program2.command("resume").description("Resume capture after a pause").action(() => {
3470
+ program.command("resume").description("Resume capture after a pause").action(() => {
3063
3471
  if (!import_fs18.default.existsSync(PAUSE_FLAG_PATH)) {
3064
3472
  log.info("Capture is not paused.");
3065
3473
  return;
@@ -3068,10 +3476,19 @@ function registerPause(program2) {
3068
3476
  log.success("Capture resumed.");
3069
3477
  });
3070
3478
  }
3479
+ var import_fs18;
3480
+ var init_pause = __esm({
3481
+ "src/cli/pause.ts"() {
3482
+ "use strict";
3483
+ import_fs18 = __toESM(require("fs"));
3484
+ init_paths();
3485
+ init_logger();
3486
+ }
3487
+ });
3071
3488
 
3072
3489
  // src/cli/undo.ts
3073
- function registerUndo(program2) {
3074
- program2.command("undo").description("Remove the last N captured events").option("-n, --count <number>", "number of events to remove", "1").option("--dry-run", "preview what would be deleted without deleting").action((opts) => {
3490
+ function registerUndo(program) {
3491
+ program.command("undo").description("Remove the last N captured events").option("-n, --count <number>", "number of events to remove", "1").option("--dry-run", "preview what would be deleted without deleting").action((opts) => {
3075
3492
  const count = parseInt(opts.count, 10);
3076
3493
  if (isNaN(count) || count < 1) {
3077
3494
  log.error("--count must be a positive integer");
@@ -3097,12 +3514,17 @@ function registerUndo(program2) {
3097
3514
  log.success(`Removed ${ids.length} event${ids.length > 1 ? "s" : ""}.`);
3098
3515
  });
3099
3516
  }
3517
+ var init_undo = __esm({
3518
+ "src/cli/undo.ts"() {
3519
+ "use strict";
3520
+ init_db();
3521
+ init_logger();
3522
+ }
3523
+ });
3100
3524
 
3101
3525
  // src/cli/clear.ts
3102
- var import_chalk8 = __toESM(require("chalk"));
3103
- var import_prompts2 = __toESM(require("prompts"));
3104
- function registerClear(program2) {
3105
- program2.command("clear").description("Delete all captured events for a date").option("--date <YYYY-MM-DD>", "date to clear (default: today)").option("--dry-run", "preview what would be deleted without deleting").option("--yes", "skip confirmation prompt").action(async (opts) => {
3526
+ function registerClear(program) {
3527
+ program.command("clear").description("Delete all captured events for a date").option("--date <YYYY-MM-DD>", "date to clear (default: today)").option("--dry-run", "preview what would be deleted without deleting").option("--yes", "skip confirmation prompt").action(async (opts) => {
3106
3528
  let date = todayDate();
3107
3529
  if (opts.date) {
3108
3530
  try {
@@ -3147,13 +3569,19 @@ function registerClear(program2) {
3147
3569
  log.success(`Cleared ${deleted} event${deleted !== 1 ? "s" : ""} for ${date}.`);
3148
3570
  });
3149
3571
  }
3572
+ var import_chalk8, import_prompts2;
3573
+ var init_clear = __esm({
3574
+ "src/cli/clear.ts"() {
3575
+ "use strict";
3576
+ import_chalk8 = __toESM(require("chalk"));
3577
+ import_prompts2 = __toESM(require("prompts"));
3578
+ init_db();
3579
+ init_logger();
3580
+ init_format();
3581
+ }
3582
+ });
3150
3583
 
3151
3584
  // src/cli/uninstall.ts
3152
- var import_fs19 = __toESM(require("fs"));
3153
- var import_path13 = __toESM(require("path"));
3154
- var import_os7 = __toESM(require("os"));
3155
- var import_prompts3 = __toESM(require("prompts"));
3156
- var import_chalk9 = __toESM(require("chalk"));
3157
3585
  function detectShellRcFiles() {
3158
3586
  const candidates = [
3159
3587
  { name: "zsh", rcFile: import_path13.default.join(import_os7.default.homedir(), ".zshrc") },
@@ -3163,8 +3591,8 @@ function detectShellRcFiles() {
3163
3591
  ];
3164
3592
  return candidates.filter((candidate) => import_fs19.default.existsSync(candidate.rcFile) && isHookInstalled(candidate.rcFile));
3165
3593
  }
3166
- function registerUninstall(program2) {
3167
- program2.command("uninstall").description("Remove shell hooks and optionally delete all Workscribe data").option("--hooks-only", "remove shell hooks only, skip data deletion prompt (used by npm preuninstall)").action(async (opts) => {
3594
+ function registerUninstall(program) {
3595
+ program.command("uninstall").description("Remove shell hooks and optionally delete all Workscribe data").option("--hooks-only", "remove shell hooks only, skip data deletion prompt (used by npm preuninstall)").action(async (opts) => {
3168
3596
  log.plain(import_chalk9.default.bold("\n Workscribe \u2014 Uninstall\n"));
3169
3597
  const hooked = detectShellRcFiles();
3170
3598
  if (hooked.length === 0) {
@@ -3209,14 +3637,24 @@ function registerUninstall(program2) {
3209
3637
  log.plain("");
3210
3638
  });
3211
3639
  }
3640
+ var import_fs19, import_path13, import_os7, import_prompts3, import_chalk9;
3641
+ var init_uninstall = __esm({
3642
+ "src/cli/uninstall.ts"() {
3643
+ "use strict";
3644
+ import_fs19 = __toESM(require("fs"));
3645
+ import_path13 = __toESM(require("path"));
3646
+ import_os7 = __toESM(require("os"));
3647
+ import_prompts3 = __toESM(require("prompts"));
3648
+ import_chalk9 = __toESM(require("chalk"));
3649
+ init_paths();
3650
+ init_shell_hook();
3651
+ init_logger();
3652
+ }
3653
+ });
3212
3654
 
3213
3655
  // src/cli/profile.ts
3214
- var import_fs20 = __toESM(require("fs"));
3215
- var import_path14 = __toESM(require("path"));
3216
- var import_chalk10 = __toESM(require("chalk"));
3217
- var MIN_SUGGEST_COUNT = 5;
3218
- function registerProfile(program2) {
3219
- const profile = program2.command("profile").description("Manage stack profiles for better command tracking");
3656
+ function registerProfile(program) {
3657
+ const profile = program.command("profile").description("Manage stack profiles for better command tracking");
3220
3658
  profile.command("list").description("List available profiles from the registry").action(async () => {
3221
3659
  const config = readConfig();
3222
3660
  const activeNames = new Set(config.capture.profiles);
@@ -3422,10 +3860,26 @@ function registerProfile(program2) {
3422
3860
  }
3423
3861
  });
3424
3862
  }
3863
+ var import_fs20, import_path14, import_chalk10, MIN_SUGGEST_COUNT;
3864
+ var init_profile = __esm({
3865
+ "src/cli/profile.ts"() {
3866
+ "use strict";
3867
+ import_fs20 = __toESM(require("fs"));
3868
+ import_path14 = __toESM(require("path"));
3869
+ import_chalk10 = __toESM(require("chalk"));
3870
+ init_config();
3871
+ init_paths();
3872
+ init_loader();
3873
+ init_fetcher();
3874
+ init_db();
3875
+ init_logger();
3876
+ MIN_SUGGEST_COUNT = 5;
3877
+ }
3878
+ });
3425
3879
 
3426
3880
  // src/cli/note.ts
3427
- function registerNote(program2) {
3428
- program2.command("note <text>").description("Attach a free-text annotation to the current session").action((text) => {
3881
+ function registerNote(program) {
3882
+ program.command("note <text>").description("Attach a free-text annotation to the current session").action((text) => {
3429
3883
  const { repoName, branch } = getGitContext(process.cwd());
3430
3884
  insertEvent({
3431
3885
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3443,12 +3897,16 @@ function registerNote(program2) {
3443
3897
  log.success(`Note saved${repoName ? ` to ${repoName}` : ""}`);
3444
3898
  });
3445
3899
  }
3900
+ var init_note = __esm({
3901
+ "src/cli/note.ts"() {
3902
+ "use strict";
3903
+ init_db();
3904
+ init_git();
3905
+ init_logger();
3906
+ }
3907
+ });
3446
3908
 
3447
3909
  // src/cli/week.ts
3448
- var import_chalk11 = __toESM(require("chalk"));
3449
- var import_fs21 = __toESM(require("fs"));
3450
- var import_path15 = __toESM(require("path"));
3451
- var import_os8 = __toESM(require("os"));
3452
3910
  function eventRowToPayload3(row) {
3453
3911
  let metadata = {};
3454
3912
  try {
@@ -3495,8 +3953,8 @@ async function postSummary(opts, config, summary) {
3495
3953
  log.error(`Webhook failed: ${error.message}`);
3496
3954
  }
3497
3955
  }
3498
- function registerWeek(program2) {
3499
- program2.command("week").description("Generate an AI weekly digest (Mon\u2013today)").option("--fresh", "bypass cache and regenerate").option("--slack", "post to Slack webhook from config").option("--webhook <url>", "post to a custom webhook URL").action(async (opts) => {
3956
+ function registerWeek(program) {
3957
+ program.command("week").description("Generate an AI weekly digest (Mon\u2013today)").option("--fresh", "bypass cache and regenerate").option("--slack", "post to Slack webhook from config").option("--webhook <url>", "post to a custom webhook URL").action(async (opts) => {
3500
3958
  const config = readConfig();
3501
3959
  let provider;
3502
3960
  try {
@@ -3547,14 +4005,26 @@ function registerWeek(program2) {
3547
4005
  }
3548
4006
  });
3549
4007
  }
4008
+ var import_chalk11, import_fs21, import_path15, import_os8;
4009
+ var init_week = __esm({
4010
+ "src/cli/week.ts"() {
4011
+ "use strict";
4012
+ import_chalk11 = __toESM(require("chalk"));
4013
+ import_fs21 = __toESM(require("fs"));
4014
+ import_path15 = __toESM(require("path"));
4015
+ import_os8 = __toESM(require("os"));
4016
+ init_detector();
4017
+ init_router();
4018
+ init_config();
4019
+ init_logger();
4020
+ init_format();
4021
+ init_webhook();
4022
+ init_slack();
4023
+ init_paths();
4024
+ }
4025
+ });
3550
4026
 
3551
4027
  // src/cli/bug-report.ts
3552
- var import_fs22 = __toESM(require("fs"));
3553
- var import_os9 = __toESM(require("os"));
3554
- var import_path16 = __toESM(require("path"));
3555
- var import_chalk12 = __toESM(require("chalk"));
3556
- var GITHUB_ISSUES_URL = "https://github.com/workscribe/workscribe/issues/new";
3557
- var DEBUG_LOG_TAIL_LINES = 100;
3558
4028
  function tailFile(filePath, lines) {
3559
4029
  if (!import_fs22.default.existsSync(filePath)) return "(no debug log found \u2014 set WORKSCRIBE_DEBUG=1 to enable)";
3560
4030
  const content = import_fs22.default.readFileSync(filePath, "utf-8");
@@ -3572,12 +4042,12 @@ function redactConfig() {
3572
4042
  return "(could not read config)";
3573
4043
  }
3574
4044
  }
3575
- function registerBugReport(program2) {
3576
- program2.command("bug-report").description("Generate a diagnostic report for filing a bug").action(() => {
4045
+ function registerBugReport(program) {
4046
+ program.command("bug-report").description("Generate a diagnostic report for filing a bug").action(() => {
3577
4047
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3578
4048
  const reportPath = import_path16.default.join(WORKSCRIBE_DIR, `bug-report-${timestamp}.txt`);
3579
4049
  const systemInfo = [
3580
- `Workscribe version : ${"1.13.0"}`,
4050
+ `Workscribe version : ${"1.14.1"}`,
3581
4051
  `Node.js version : ${process.version}`,
3582
4052
  `Platform : ${process.platform}`,
3583
4053
  `Architecture : ${process.arch}`,
@@ -3608,7 +4078,7 @@ function registerBugReport(program2) {
3608
4078
  process.exit(1);
3609
4079
  }
3610
4080
  const params = new URLSearchParams({
3611
- title: `Bug report \u2014 workscribe ${"1.13.0"}`,
4081
+ title: `Bug report \u2014 workscribe ${"1.14.1"}`,
3612
4082
  body: `<!-- Attach ${import_path16.default.basename(reportPath)} or paste the relevant sections below -->
3613
4083
 
3614
4084
  **Describe the issue:**
@@ -3626,9 +4096,23 @@ function registerBugReport(program2) {
3626
4096
  log.plain("");
3627
4097
  });
3628
4098
  }
4099
+ var import_fs22, import_os9, import_path16, import_chalk12, GITHUB_ISSUES_URL, DEBUG_LOG_TAIL_LINES;
4100
+ var init_bug_report = __esm({
4101
+ "src/cli/bug-report.ts"() {
4102
+ "use strict";
4103
+ import_fs22 = __toESM(require("fs"));
4104
+ import_os9 = __toESM(require("os"));
4105
+ import_path16 = __toESM(require("path"));
4106
+ import_chalk12 = __toESM(require("chalk"));
4107
+ init_paths();
4108
+ init_config();
4109
+ init_logger();
4110
+ GITHUB_ISSUES_URL = "https://github.com/workscribe/workscribe/issues/new";
4111
+ DEBUG_LOG_TAIL_LINES = 100;
4112
+ }
4113
+ });
3629
4114
 
3630
4115
  // src/cli/alerts.ts
3631
- var import_chalk13 = __toESM(require("chalk"));
3632
4116
  function formatTimestamp(ms) {
3633
4117
  return new Date(ms).toLocaleString([], {
3634
4118
  month: "short",
@@ -3637,8 +4121,8 @@ function formatTimestamp(ms) {
3637
4121
  minute: "2-digit"
3638
4122
  });
3639
4123
  }
3640
- function registerAlerts(program2) {
3641
- const alertsCmd = program2.command("alerts").description("Show and manage security alerts from AI session monitoring").option("--check", "Print warning if alerts are pending (used by shell hooks)").action((opts) => {
4124
+ function registerAlerts(program) {
4125
+ const alertsCmd = program.command("alerts").description("Show and manage security alerts from AI session monitoring").option("--check", "Print warning if alerts are pending (used by shell hooks)").action((opts) => {
3642
4126
  if (opts.check) {
3643
4127
  if (hasPendingAlerts()) {
3644
4128
  log.plain(
@@ -3675,10 +4159,29 @@ function registerAlerts(program2) {
3675
4159
  }
3676
4160
  });
3677
4161
  }
4162
+ var import_chalk13;
4163
+ var init_alerts3 = __esm({
4164
+ "src/cli/alerts.ts"() {
4165
+ "use strict";
4166
+ import_chalk13 = __toESM(require("chalk"));
4167
+ init_alerts2();
4168
+ init_logger();
4169
+ }
4170
+ });
3678
4171
 
3679
4172
  // src/cli/context.ts
3680
- var import_path17 = __toESM(require("path"));
3681
- var import_chalk14 = __toESM(require("chalk"));
4173
+ function toContextJson(session) {
4174
+ return {
4175
+ date: session.date,
4176
+ startedAt: session.started_at,
4177
+ endedAt: session.ended_at,
4178
+ timeAgo: timeAgo(session.started_at),
4179
+ durationMinutes: (new Date(session.ended_at).getTime() - new Date(session.started_at).getTime()) / 6e4,
4180
+ branch: getSessionBranch(session.id),
4181
+ eventCount: session.event_count,
4182
+ summary: session.summary
4183
+ };
4184
+ }
3682
4185
  function printSessionContext(session, heading) {
3683
4186
  const ago = timeAgo(session.started_at);
3684
4187
  const branch = getSessionBranch(session.id);
@@ -3701,35 +4204,57 @@ function printSessionContext(session, heading) {
3701
4204
  }
3702
4205
  log.plain("");
3703
4206
  }
3704
- function registerContext(program2) {
3705
- program2.command("context").description("Show the last session for the current repo").option("--file <path>", "narrow to last session that touched this file").action((opts) => {
4207
+ function registerContext(program) {
4208
+ program.command("context").description("Show the last session for the current repo").option("--file <path>", "narrow to last session that touched this file").option("--json", "output as JSON instead of formatted text \u2014 null when no session is found").action((opts) => {
3706
4209
  if (opts.file) {
3707
4210
  const normalizedFile = import_path17.default.normalize(opts.file);
3708
4211
  const session = getLastSessionTouchingFile(normalizedFile);
3709
4212
  if (!session) {
3710
- log.plain(` No sessions found touching ${normalizedFile}`);
4213
+ if (opts.json) log.plain(JSON.stringify(null));
4214
+ else log.plain(` No sessions found touching ${normalizedFile}`);
4215
+ return;
4216
+ }
4217
+ if (opts.json) {
4218
+ log.plain(JSON.stringify(toContextJson(session)));
3711
4219
  return;
3712
4220
  }
3713
4221
  printSessionContext(session, `Last session touching ${import_path17.default.basename(normalizedFile)}`);
3714
4222
  return;
3715
4223
  }
3716
- const { repoName } = getGitContext(process.cwd());
4224
+ const { repoName } = getGitContext(process.cwd(), null, readConfig().git.qualifyRepoName);
3717
4225
  if (!repoName) {
3718
- log.plain(" Not in a git repository. Run workscribe context from a project directory.");
4226
+ if (opts.json) log.plain(JSON.stringify(null));
4227
+ else log.plain(" Not in a git repository. Run workscribe context from a project directory.");
3719
4228
  return;
3720
4229
  }
3721
4230
  const sessions = getRecentSessionsByRepo(repoName, 1);
3722
4231
  if (sessions.length === 0) {
3723
- log.plain(` No sessions found for ${repoName}`);
4232
+ if (opts.json) log.plain(JSON.stringify(null));
4233
+ else log.plain(` No sessions found for ${repoName}`);
4234
+ return;
4235
+ }
4236
+ if (opts.json) {
4237
+ log.plain(JSON.stringify(toContextJson(sessions[0])));
3724
4238
  return;
3725
4239
  }
3726
4240
  printSessionContext(sessions[0], `Last session on ${repoName}`);
3727
4241
  });
3728
4242
  }
4243
+ var import_path17, import_chalk14;
4244
+ var init_context = __esm({
4245
+ "src/cli/context.ts"() {
4246
+ "use strict";
4247
+ import_path17 = __toESM(require("path"));
4248
+ import_chalk14 = __toESM(require("chalk"));
4249
+ init_db();
4250
+ init_git();
4251
+ init_config();
4252
+ init_format();
4253
+ init_logger();
4254
+ }
4255
+ });
3729
4256
 
3730
4257
  // src/utils/update-check.ts
3731
- var import_fs23 = __toESM(require("fs"));
3732
- var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3733
4258
  function readCache() {
3734
4259
  try {
3735
4260
  return JSON.parse(import_fs23.default.readFileSync(UPDATE_CHECK_PATH, "utf-8"));
@@ -3752,7 +4277,7 @@ function isNewer(latest, current) {
3752
4277
  return latestPatch > currentPatch;
3753
4278
  }
3754
4279
  function checkForUpdate() {
3755
- const current = "1.13.0";
4280
+ const current = "1.14.1";
3756
4281
  const cache = readCache();
3757
4282
  if (cache && isNewer(cache.latestVersion, current)) {
3758
4283
  process.stderr.write(
@@ -3772,31 +4297,102 @@ function checkForUpdate() {
3772
4297
  });
3773
4298
  }
3774
4299
  }
4300
+ var import_fs23, CHECK_INTERVAL_MS;
4301
+ var init_update_check = __esm({
4302
+ "src/utils/update-check.ts"() {
4303
+ "use strict";
4304
+ import_fs23 = __toESM(require("fs"));
4305
+ init_paths();
4306
+ CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
4307
+ }
4308
+ });
4309
+
4310
+ // src/main.ts
4311
+ var require_main = __commonJS({
4312
+ "src/main.ts"() {
4313
+ "use strict";
4314
+ var import_commander = require("commander");
4315
+ init_capture2();
4316
+ init_hook();
4317
+ init_init();
4318
+ init_status();
4319
+ init_events();
4320
+ init_sessions();
4321
+ init_summary();
4322
+ init_export();
4323
+ init_config2();
4324
+ init_pause();
4325
+ init_undo();
4326
+ init_clear();
4327
+ init_uninstall();
4328
+ init_profile();
4329
+ init_note();
4330
+ init_week();
4331
+ init_bug_report();
4332
+ init_completions();
4333
+ init_alerts3();
4334
+ init_context();
4335
+ init_update_check();
4336
+ checkForUpdate();
4337
+ var program = new import_commander.Command();
4338
+ program.name("workscribe").description("Automatic developer work journal").version("1.14.1");
4339
+ registerCapture(program);
4340
+ registerHook(program);
4341
+ registerInit(program);
4342
+ registerStatus(program);
4343
+ registerEvents(program);
4344
+ registerSessions(program);
4345
+ registerSummary(program);
4346
+ registerExport(program);
4347
+ registerConfig(program);
4348
+ registerPause(program);
4349
+ registerUndo(program);
4350
+ registerClear(program);
4351
+ registerUninstall(program);
4352
+ registerProfile(program);
4353
+ registerNote(program);
4354
+ registerWeek(program);
4355
+ registerBugReport(program);
4356
+ registerCompletions(program);
4357
+ registerAlerts(program);
4358
+ registerContext(program);
4359
+ program.parse();
4360
+ }
4361
+ });
4362
+
4363
+ // src/utils/node-version.ts
4364
+ function detectVersionManager() {
4365
+ if (process.env.NVM_DIR) return "nvm";
4366
+ if (process.env.VOLTA_HOME) return "volta";
4367
+ if (process.env.FNM_DIR) return "fnm";
4368
+ if (process.env.MISE_DATA_DIR || process.env.MISE_SHELL) return "mise";
4369
+ if (process.env.ASDF_DIR || process.env.ASDF_DATA_DIR) return "asdf";
4370
+ if (process.env.NODENV_ROOT) return "nodenv";
4371
+ return null;
4372
+ }
4373
+ var UPGRADE_COMMANDS = {
4374
+ nvm: "nvm install 22 && nvm use 22",
4375
+ volta: "volta install node@22",
4376
+ fnm: "fnm install 22 && fnm use 22",
4377
+ mise: "mise use --global node@22",
4378
+ asdf: "asdf install nodejs 22 && asdf global nodejs 22",
4379
+ nodenv: "nodenv install 22 && nodenv global 22"
4380
+ };
4381
+ function assertNodeVersion(nodeVersion) {
4382
+ const major = Number(nodeVersion.split(".")[0]);
4383
+ if (major < 22) {
4384
+ const manager = detectVersionManager();
4385
+ const upgradeHint = manager ? `Upgrade with ${manager}:
4386
+ ${UPGRADE_COMMANDS[manager]}` : "Download the latest version from https://nodejs.org";
4387
+ process.stderr.write(`workscribe requires Node.js 22 or later (you have v${nodeVersion}).
4388
+
4389
+ ${upgradeHint}
4390
+ `);
4391
+ process.exit(1);
4392
+ }
4393
+ }
3775
4394
 
3776
4395
  // src/index.ts
3777
4396
  assertNodeVersion(process.versions.node);
3778
- checkForUpdate();
3779
- var program = new import_commander.Command();
3780
- program.name("workscribe").description("Automatic developer work journal").version("1.13.0");
3781
- registerCapture(program);
3782
- registerHook(program);
3783
- registerInit(program);
3784
- registerStatus(program);
3785
- registerEvents(program);
3786
- registerSessions(program);
3787
- registerSummary(program);
3788
- registerExport(program);
3789
- registerConfig(program);
3790
- registerPause(program);
3791
- registerUndo(program);
3792
- registerClear(program);
3793
- registerUninstall(program);
3794
- registerProfile(program);
3795
- registerNote(program);
3796
- registerWeek(program);
3797
- registerBugReport(program);
3798
- registerCompletions(program);
3799
- registerAlerts(program);
3800
- registerContext(program);
3801
- program.parse();
4397
+ require_main();
3802
4398
  //# sourceMappingURL=index.js.map