@workscribe/cli 1.14.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,11 +417,42 @@ 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
456
  function parseOwnerAndRepo(url) {
440
457
  const normalized = url.trim().replace(/^[^/]+:(?!\/\/)/, (match) => match.replace(":", "/"));
441
458
  const segments = normalized.split("/").filter(Boolean);
@@ -490,9 +507,17 @@ function getGitContext(cwd, nameOverride, qualifyRepoName = false) {
490
507
  return { repoName: null, branch: null };
491
508
  }
492
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
+ });
493
519
 
494
520
  // src/capture/git-snapshot.ts
495
- var import_child_process2 = require("child_process");
496
521
  function getCommitsSince(sha, cwd) {
497
522
  if (!sha) return [];
498
523
  try {
@@ -502,12 +527,15 @@ function getCommitsSince(sha, cwd) {
502
527
  return [];
503
528
  }
504
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
+ });
505
537
 
506
538
  // src/capture/aider.ts
507
- var import_fs4 = __toESM(require("fs"));
508
- var import_path3 = __toESM(require("path"));
509
- var HISTORY_FILE = ".aider.chat.history.md";
510
- var PROMPT_MAX_LENGTH = 200;
511
539
  function readLastAiderPrompt(cwd) {
512
540
  try {
513
541
  const histPath = import_path3.default.join(cwd, HISTORY_FILE);
@@ -519,135 +547,135 @@ function readLastAiderPrompt(cwd) {
519
547
  return null;
520
548
  }
521
549
  }
522
-
523
- // src/config/registry.ts
524
- var import_fs6 = __toESM(require("fs"));
525
- var import_os2 = __toESM(require("os"));
526
- 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
+ });
527
560
 
528
561
  // src/config/ai-tools.json
529
- var ai_tools_default = [
530
- {
531
- tool: "claude",
532
- displayName: "Claude Code",
533
- pattern: "^claude\\b",
534
- hook: "claude-code",
535
- contextSource: "hook",
536
- historyFormat: null,
537
- securityWarning: null
538
- },
539
- {
540
- tool: "aider",
541
- displayName: "Aider",
542
- pattern: "^aider\\b",
543
- hook: null,
544
- contextSource: "history-file",
545
- historyFormat: "markdown",
546
- securityWarning: null
547
- },
548
- {
549
- tool: "codex",
550
- displayName: "Codex CLI",
551
- pattern: "^codex\\b",
552
- hook: null,
553
- contextSource: "history-file",
554
- historyFormat: "jsonl",
555
- securityWarning: null
556
- },
557
- {
558
- tool: "gemini",
559
- displayName: "Gemini CLI",
560
- pattern: "^gemini\\b",
561
- hook: null,
562
- contextSource: "history-file",
563
- historyFormat: "json",
564
- securityWarning: null
565
- },
566
- {
567
- tool: "gh copilot",
568
- displayName: "GitHub Copilot",
569
- pattern: "^gh\\s+copilot\\b",
570
- hook: null,
571
- contextSource: "history-file",
572
- historyFormat: "jsonl",
573
- securityWarning: null
574
- },
575
- {
576
- tool: "amazon-q",
577
- displayName: "Amazon Q",
578
- pattern: "^q\\s+(chat|dev)\\b",
579
- hook: null,
580
- contextSource: "history-file",
581
- historyFormat: "json",
582
- securityWarning: null
583
- },
584
- {
585
- tool: "opencode",
586
- displayName: "OpenCode",
587
- pattern: "^opencode\\b",
588
- hook: null,
589
- contextSource: "sqlite",
590
- historyFormat: "sqlite",
591
- securityWarning: null
592
- },
593
- {
594
- tool: "kilo",
595
- displayName: "Kilo Code",
596
- pattern: "^kilo\\b",
597
- hook: null,
598
- contextSource: null,
599
- historyFormat: null,
600
- securityWarning: null
601
- },
602
- {
603
- tool: "grok",
604
- displayName: "Grok Build",
605
- pattern: "^grok\\b",
606
- hook: null,
607
- contextSource: null,
608
- historyFormat: null,
609
- 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."
610
- },
611
- {
612
- tool: "continue",
613
- displayName: "Continue",
614
- pattern: "^continue\\b",
615
- hook: null,
616
- contextSource: null,
617
- historyFormat: null,
618
- securityWarning: null
619
- },
620
- {
621
- tool: "cody",
622
- displayName: "Cody",
623
- pattern: "^cody\\b",
624
- hook: null,
625
- contextSource: null,
626
- historyFormat: null,
627
- securityWarning: null
628
- },
629
- {
630
- tool: "cursor",
631
- displayName: "Cursor",
632
- pattern: "^cursor\\b",
633
- hook: null,
634
- contextSource: null,
635
- historyFormat: null,
636
- securityWarning: null
637
- }
638
- ];
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
+ });
639
677
 
640
678
  // src/utils/logger.ts
641
- var import_chalk = __toESM(require("chalk"));
642
- var import_fs5 = __toESM(require("fs"));
643
- var isDebug = process.env.WORKSCRIBE_DEBUG === "1";
644
- var log = {
645
- info: (msg) => console.log(import_chalk.default.cyan("\u2192"), msg),
646
- success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
647
- warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
648
- error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
649
- plain: (msg) => console.log(msg)
650
- };
651
679
  function debugLog(context, error) {
652
680
  if (!isDebug) return;
653
681
  const entry = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${context}] ${error instanceof Error ? error.stack : String(error)}
@@ -657,9 +685,25 @@ function debugLog(context, error) {
657
685
  } catch {
658
686
  }
659
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
+ });
660
705
 
661
706
  // src/config/registry.ts
662
- var USER_REGISTRY_PATH = import_path4.default.join(import_os2.default.homedir(), ".workscribe", "ai-tools.json");
663
707
  function loadAIToolRegistry() {
664
708
  let userEntries = [];
665
709
  try {
@@ -686,39 +730,20 @@ function loadAIToolRegistry() {
686
730
  const filteredBuiltins = ai_tools_default.filter((entry) => !userToolSlugs.has(entry.tool));
687
731
  return [...filteredBuiltins, ...userEntries];
688
732
  }
689
-
690
- // src/profiles/loader.ts
691
- var import_fs9 = __toESM(require("fs"));
692
- 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
+ });
693
745
 
694
746
  // src/config/index.ts
695
- var import_fs7 = __toESM(require("fs"));
696
- var DEFAULTS = {
697
- ai: {
698
- provider: "",
699
- apiKey: "",
700
- model: "",
701
- baseUrl: ""
702
- },
703
- session: {
704
- idleTimeout: 30
705
- },
706
- git: {
707
- qualifyRepoName: false
708
- },
709
- capture: {
710
- ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"],
711
- aiTools: true,
712
- includeAiderPrompt: true,
713
- profiles: []
714
- },
715
- redact: {
716
- extra: []
717
- },
718
- summary: {
719
- format: "markdown"
720
- }
721
- };
722
747
  function readConfig() {
723
748
  if (!import_fs7.default.existsSync(CONFIG_PATH)) return { ...DEFAULTS };
724
749
  try {
@@ -775,6 +800,40 @@ function deepMerge(defaults, overrides) {
775
800
  }
776
801
  return result;
777
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
+ });
778
837
 
779
838
  // src/profiles/schema.ts
780
839
  function isNonEmptyString(value) {
@@ -795,21 +854,13 @@ function isValidProfile(candidate) {
795
854
  function isValidManifest(m) {
796
855
  return Array.isArray(m) && m.every((e) => e && typeof e === "object" && typeof e.name === "string");
797
856
  }
798
-
799
- // src/profiles/fetcher.ts
800
- var import_fs8 = __toESM(require("fs"));
801
- var import_path5 = __toESM(require("path"));
857
+ var init_schema = __esm({
858
+ "src/profiles/schema.ts"() {
859
+ "use strict";
860
+ }
861
+ });
802
862
 
803
863
  // src/utils/fetch.ts
804
- var DEFAULT_TIMEOUT_MS = 3e4;
805
- var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
806
- "ENOTFOUND",
807
- "ECONNREFUSED",
808
- "ECONNRESET",
809
- "ETIMEDOUT",
810
- "EHOSTUNREACH",
811
- "ENETUNREACH"
812
- ]);
813
864
  function isNetworkError(err) {
814
865
  if (!(err instanceof Error)) return false;
815
866
  const cause = err.cause;
@@ -833,9 +884,23 @@ async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_TIMEOUT_MS) {
833
884
  clearTimeout(timer);
834
885
  }
835
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
+ });
836
902
 
837
903
  // src/profiles/fetcher.ts
838
- var REGISTRY_BASE = "https://raw.githubusercontent.com/workscribe/profiles/main";
839
904
  function manifestCachePath() {
840
905
  return import_path5.default.join(PROFILES_DIR, "manifest.json");
841
906
  }
@@ -881,6 +946,19 @@ function ensureProfilesDir() {
881
946
  import_fs8.default.mkdirSync(PROFILES_DIR, { recursive: true, mode: 448 });
882
947
  }
883
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
+ });
884
962
 
885
963
  // src/profiles/loader.ts
886
964
  function compileRule(profileName, rule) {
@@ -959,6 +1037,19 @@ function detectProfilesForDirectory(dir) {
959
1037
  return indicatorFiles.length > 0 && indicatorFiles.every((file) => import_fs9.default.existsSync(import_path6.default.join(dir, file)));
960
1038
  });
961
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
+ });
962
1053
 
963
1054
  // src/normalize/patterns.ts
964
1055
  function makeRule(pattern, category, extract = () => ({})) {
@@ -975,208 +1066,6 @@ function extractPackages(cmd, afterKeyword) {
975
1066
  if (idx === -1) return [];
976
1067
  return cmd.slice(idx + afterKeyword.length).trim().split(/\s+/).filter((part) => part && !part.startsWith("-"));
977
1068
  }
978
- var BASE_RULES = [
979
- // ─── Git operations ───────────────────────────────────────────────────────
980
- makeRule(/^git\s+commit\b/, "shipping", (_m, cmd) => {
981
- const msgMatch = cmd.match(/-m\s+["']?([^"']+)["']?/);
982
- return { message: msgMatch ? msgMatch[1].trim() : null };
983
- }),
984
- makeRule(/^git\s+push\b/, "shipping", (_m, cmd) => {
985
- const parts = cmd.split(/\s+/);
986
- return { remote: parts[2] ?? "origin", branch: parts[3] ?? null };
987
- }),
988
- makeRule(/^git\s+(pull|fetch)\b/, "versioning", (_m, cmd) => {
989
- const parts = cmd.split(/\s+/);
990
- return { subcommand: parts[1], remote: parts[2] ?? "origin" };
991
- }),
992
- makeRule(/^git\s+(checkout|switch)\b/, "versioning", (_m, cmd) => {
993
- const parts = cmd.split(/\s+/);
994
- const branch = parts.find((part) => !part.startsWith("-") && part !== "git" && part !== parts[1]);
995
- return { branch: branch ?? null };
996
- }),
997
- makeRule(/^git\s+(merge|rebase)\b/, "shipping", (_m, cmd) => {
998
- const parts = cmd.split(/\s+/);
999
- return { subcommand: parts[1], target: parts[2] ?? null };
1000
- }),
1001
- makeRule(/^git\s+stash\b/, "versioning", (_m, cmd) => {
1002
- const parts = cmd.split(/\s+/);
1003
- return { subcommand: parts[2] ?? "push" };
1004
- }),
1005
- makeRule(/^git\s+/, "versioning", (_m, cmd) => {
1006
- const parts = cmd.split(/\s+/);
1007
- return { subcommand: parts[1] ?? null };
1008
- }),
1009
- // ─── Releasing ────────────────────────────────────────────────────────────
1010
- makeRule(/^npm\s+publish\b/, "releasing", () => ({ tool: "npm" })),
1011
- makeRule(/^cargo\s+publish\b/, "releasing", () => ({ tool: "cargo" })),
1012
- makeRule(/^gem\s+push\b/, "releasing", () => ({ tool: "gem" })),
1013
- makeRule(/^docker\s+push\b/, "releasing", (_m, cmd) => ({ tool: "docker", image: cmd.split(/\s+/)[2] ?? null })),
1014
- makeRule(/^gh\s+release\b/, "releasing", (_m, cmd) => ({ tool: "gh", subcommand: cmd.split(/\s+/)[2] ?? null })),
1015
- makeRule(/^fly\s+(deploy|launch)\b/, "releasing", (match) => ({ tool: "fly", subcommand: match[1] })),
1016
- makeRule(/^vercel\s+(deploy|build|--prod)\b/, "releasing", () => ({ tool: "vercel" })),
1017
- makeRule(/^netlify\s+deploy\b/, "releasing", () => ({ tool: "netlify" })),
1018
- makeRule(/^railway\s+up\b/, "releasing", () => ({ tool: "railway" })),
1019
- makeRule(/^wrangler\s+deploy\b/, "releasing", () => ({ tool: "wrangler" })),
1020
- // ─── Dependencies ─────────────────────────────────────────────────────────
1021
- makeRule(/^npm\s+(install|i|add)\b/, "dependencies", (_m, cmd) => ({
1022
- tool: "npm",
1023
- packages: extractPackages(cmd, cmd.includes("install") ? "install" : cmd.includes(" i ") ? " i " : "add")
1024
- })),
1025
- makeRule(/^(yarn|pnpm)\s+(install|add)\b/, "dependencies", (m, cmd) => ({
1026
- tool: m[1],
1027
- packages: extractPackages(cmd, "add")
1028
- })),
1029
- makeRule(/^bun\s+(install|add)\b/, "dependencies", (_m, cmd) => ({
1030
- tool: "bun",
1031
- packages: extractPackages(cmd, "add")
1032
- })),
1033
- makeRule(/^pip\s+(install)\b/, "dependencies", (_m, cmd) => ({
1034
- tool: "pip",
1035
- packages: extractPackages(cmd, "install")
1036
- })),
1037
- makeRule(/^cargo\s+add\b/, "dependencies", (_m, cmd) => ({
1038
- tool: "cargo",
1039
- packages: extractPackages(cmd, "add")
1040
- })),
1041
- makeRule(/^(brew)\s+install\b/, "dependencies", (_m, cmd) => ({
1042
- tool: "brew",
1043
- packages: extractPackages(cmd, "install")
1044
- })),
1045
- makeRule(/^go\s+get\b/, "dependencies", (_m, cmd) => ({
1046
- tool: "go",
1047
- packages: extractPackages(cmd, "get")
1048
- })),
1049
- makeRule(/^npm\s+(uninstall|remove|rm)\b/, "dependencies", (_m, cmd) => ({
1050
- tool: "npm",
1051
- packages: extractPackages(cmd, cmd.split(/\s+/)[1])
1052
- })),
1053
- makeRule(/^(yarn|pnpm)\s+remove\b/, "dependencies", (m, cmd) => ({
1054
- tool: m[1],
1055
- packages: extractPackages(cmd, "remove")
1056
- })),
1057
- makeRule(/^bun\s+remove\b/, "dependencies", (_m, cmd) => ({
1058
- tool: "bun",
1059
- packages: extractPackages(cmd, "remove")
1060
- })),
1061
- makeRule(/^pip\s+uninstall\b/, "dependencies", (_m, cmd) => ({
1062
- tool: "pip",
1063
- packages: extractPackages(cmd, "uninstall")
1064
- })),
1065
- // Runtime version managers
1066
- makeRule(/^nvm\b/, "dependencies", (_m, cmd) => ({ tool: "nvm", subcommand: cmd.split(/\s+/)[1] ?? null })),
1067
- makeRule(/^(fnm|volta)\b/, "dependencies", (m, cmd) => ({ tool: m[1], subcommand: cmd.split(/\s+/)[1] ?? null })),
1068
- makeRule(/^(rbenv|pyenv|asdf)\b/, "dependencies", (m, cmd) => ({
1069
- tool: m[1],
1070
- subcommand: cmd.split(/\s+/)[1] ?? null
1071
- })),
1072
- // ─── Tests ────────────────────────────────────────────────────────────────
1073
- makeRule(/^npm\s+(test|run\s+test)\b/, "testing", () => ({ tool: "npm" })),
1074
- makeRule(/^yarn\s+test\b/, "testing", () => ({ tool: "yarn" })),
1075
- makeRule(/^pnpm\s+test\b/, "testing", () => ({ tool: "pnpm" })),
1076
- makeRule(/^bun\s+test\b/, "testing", () => ({ tool: "bun" })),
1077
- makeRule(/^(jest|vitest)\b/, "testing", (match) => ({ tool: match[1] })),
1078
- makeRule(/^pytest\b/, "testing", () => ({ tool: "pytest" })),
1079
- makeRule(/^go\s+test\b/, "testing", () => ({ tool: "go" })),
1080
- makeRule(/^cargo\s+test\b/, "testing", () => ({ tool: "cargo" })),
1081
- makeRule(/^rspec\b/, "testing", () => ({ tool: "rspec" })),
1082
- makeRule(/^(php\s+)?phpunit\b/, "testing", () => ({ tool: "phpunit" })),
1083
- // ─── Build ────────────────────────────────────────────────────────────────
1084
- makeRule(/^npm\s+run\s+build\b/, "building", () => ({ tool: "npm" })),
1085
- makeRule(/^(yarn|pnpm)\s+build\b/, "building", (match) => ({ tool: match[1] })),
1086
- makeRule(/^bun\s+run\s+build\b/, "building", () => ({ tool: "bun" })),
1087
- makeRule(/^(make|gmake)\b/, "building", (match) => ({ tool: match[1] })),
1088
- makeRule(/^cargo\s+build\b/, "building", () => ({ tool: "cargo" })),
1089
- makeRule(/^go\s+build\b/, "building", () => ({ tool: "go" })),
1090
- makeRule(/^docker\s+build\b/, "building", () => ({ tool: "docker" })),
1091
- makeRule(/^(mvn|gradle)\b/, "building", (match) => ({ tool: match[1] })),
1092
- // ─── Dev server / running ─────────────────────────────────────────────────
1093
- makeRule(/^npm\s+run\s+(dev|start|serve)\b/, "running", (_m, cmd) => ({
1094
- tool: "npm",
1095
- script: cmd.split(/\s+/)[2]
1096
- })),
1097
- makeRule(/^(yarn|pnpm)\s+(dev|start)\b/, "running", (match) => ({ tool: match[1], script: match[2] })),
1098
- makeRule(/^bun\s+run\s+(dev|start|serve)\b/, "running", (_m, cmd) => ({
1099
- tool: "bun",
1100
- script: cmd.split(/\s+/)[2]
1101
- })),
1102
- makeRule(/^python\s+.*manage\.py\s+runserver\b/, "running", () => ({ tool: "django" })),
1103
- // ─── Infrastructure ───────────────────────────────────────────────────────
1104
- makeRule(/^ssh\b/, "infrastructure", (_m, cmd) => {
1105
- const parts = cmd.split(/\s+/);
1106
- return { tool: "ssh", target: parts[parts.length - 1] };
1107
- }),
1108
- makeRule(/^kubectl\b/, "infrastructure", (_m, cmd) => {
1109
- const parts = cmd.split(/\s+/);
1110
- return { tool: "kubectl", subcommand: parts[1] ?? null };
1111
- }),
1112
- makeRule(/^(helm|terraform|pulumi)\b/, "infrastructure", (m, cmd) => {
1113
- const parts = cmd.split(/\s+/);
1114
- return { tool: m[1], subcommand: parts[1] ?? null };
1115
- }),
1116
- makeRule(/^docker\s+(run|compose|ps|stop|start|rm|exec)\b/, "infrastructure", (_m, cmd) => {
1117
- const parts = cmd.split(/\s+/);
1118
- return { tool: "docker", subcommand: parts[1] };
1119
- }),
1120
- makeRule(/^(aws|gcloud|az)\b/, "infrastructure", (m, cmd) => {
1121
- const parts = cmd.split(/\s+/);
1122
- return { tool: m[1], subcommand: parts[1] ?? null };
1123
- }),
1124
- // ─── Database ─────────────────────────────────────────────────────────────
1125
- makeRule(/^(psql|pgcli)\b/, "database", (m) => ({ tool: m[1] })),
1126
- makeRule(/^(mysql|mycli)\b/, "database", (m) => ({ tool: m[1] })),
1127
- makeRule(/^sqlite3\b/, "database", () => ({ tool: "sqlite3" })),
1128
- makeRule(/^(mongosh|mongo)\b/, "database", (m) => ({ tool: m[1] })),
1129
- makeRule(/^redis-cli\b/, "database", () => ({ tool: "redis-cli" })),
1130
- // ─── npx ─────────────────────────────────────────────────────────────────
1131
- makeRule(/^npx\s+(jest|vitest|mocha|ava|tap)\b/, "testing", (_m, cmd) => ({
1132
- tool: cmd.split(/\s+/)[1]
1133
- })),
1134
- makeRule(/^npx\s+tsc\b/, "building", () => ({ tool: "tsc" })),
1135
- makeRule(/^npx\s+(eslint|tslint|biome)\b/, "linting", (_m, cmd) => ({
1136
- tool: cmd.split(/\s+/)[1]
1137
- })),
1138
- makeRule(/^npx\s+(prettier|dprint)\b/, "linting", (_m, cmd) => ({
1139
- tool: cmd.split(/\s+/)[1]
1140
- })),
1141
- makeRule(/^npx\s+\S+/, "running", (_m, cmd) => ({
1142
- tool: cmd.split(/\s+/)[1]
1143
- })),
1144
- // ─── Scripts ──────────────────────────────────────────────────────────────
1145
- makeRule(/^(bash|sh|zsh|fish)\s+\S+/, "running", (match) => ({ shell: match[1] })),
1146
- makeRule(/^node\s+\S+\.(?:js|mjs|cjs)\b/, "running", () => ({ runtime: "node" })),
1147
- makeRule(/^python3?\s+\S+\.py\b/, "running", () => ({ runtime: "python" })),
1148
- // ─── Linting / code quality ───────────────────────────────────────────────
1149
- makeRule(/^(eslint|tslint|biome)\b/, "linting", (match) => ({ tool: match[1] })),
1150
- makeRule(/^(prettier|dprint)\b/, "linting", (match) => ({ tool: match[1] })),
1151
- // ─── generic run scripts ──────────────────────────────────────────────────
1152
- makeRule(/^npm\s+run\s+\S+/, "running", (_m, cmd) => ({
1153
- tool: "npm",
1154
- script: cmd.split(/\s+/)[2]
1155
- })),
1156
- makeRule(/^bun\s+run\s+\S+/, "running", (_m, cmd) => ({
1157
- tool: "bun",
1158
- script: cmd.split(/\s+/)[2]
1159
- })),
1160
- // ─── Code search ─────────────────────────────────────────────────────────
1161
- makeRule(/^(grep|egrep|fgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
1162
- makeRule(/^(rg|ag|ack|ripgrep)\b/, "exploring", (match) => ({ tool: match[1] })),
1163
- makeRule(/^find\s/, "exploring", () => ({ tool: "find" })),
1164
- // ─── Code read ────────────────────────────────────────────────────────────
1165
- makeRule(/^(cat|bat|less|more)\s+\S+/, "exploring", (match) => ({ tool: match[1] })),
1166
- makeRule(/^(head|tail)\b/, "exploring", (match) => ({ tool: match[1] })),
1167
- // ─── File operations ──────────────────────────────────────────────────────
1168
- makeRule(/^(cp|rsync)\b/, "files", (match) => ({ tool: match[1] })),
1169
- makeRule(/^(mv)\b/, "files", () => ({ tool: "mv" })),
1170
- makeRule(/^(rm|rmdir)\b/, "files", (match) => ({ tool: match[1] })),
1171
- makeRule(/^(mkdir|touch|ln)\b/, "files", (match) => ({ tool: match[1] })),
1172
- makeRule(/^(chmod|chown|chgrp)\b/, "files", (match) => ({ tool: match[1] })),
1173
- // ─── Environment ──────────────────────────────────────────────────────────
1174
- makeRule(/^export\s+\w+=/, "files", (_m, cmd) => {
1175
- const varName = cmd.match(/^export\s+(\w+)=/)?.[1] ?? null;
1176
- return { variable: varName };
1177
- }),
1178
- makeRule(/^source\s+/, "files", () => ({}))
1179
- ];
1180
1069
  function aiToolRules() {
1181
1070
  return loadAIToolRegistry().flatMap((entry) => {
1182
1071
  try {
@@ -1190,7 +1079,6 @@ function aiToolRules() {
1190
1079
  function buildRules(profileRules = []) {
1191
1080
  return [...BASE_RULES, ...profileRules, ...aiToolRules()];
1192
1081
  }
1193
- var RULES = buildRules(getActiveProfileRules());
1194
1082
  function normalizeCommand(command) {
1195
1083
  let trimmed = command.trim().replace(/\s+/g, " ");
1196
1084
  trimmed = trimmed.replace(/^(git)\s+-C\s+\S+\s+/, "$1 ");
@@ -1200,12 +1088,223 @@ function normalizeCommand(command) {
1200
1088
  }
1201
1089
  return { category: "other", metadata: {} };
1202
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
+ });
1203
1303
 
1204
1304
  // src/capture/filter.ts
1205
1305
  function normalizeForFilter(cmd) {
1206
1306
  return cmd.trim().replace(/\s+/g, " ");
1207
1307
  }
1208
- var BUILTIN_IGNORE = /* @__PURE__ */ new Set(["workscribe"]);
1209
1308
  function shouldFilter(command, ignoreList) {
1210
1309
  const normalized = normalizeForFilter(command);
1211
1310
  if (!normalized) return true;
@@ -1221,9 +1320,22 @@ function isDuplicate(command, cwd, lastEvent, dedupWindowMs = 5e3, now = Date.no
1221
1320
  const lastTime = new Date(lastEvent.captured_at).getTime();
1222
1321
  return lastEvent.command === normalizeForFilter(command) && lastEvent.cwd === cwd && now - lastTime < dedupWindowMs;
1223
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
+ });
1224
1330
 
1225
1331
  // src/capture/ide.ts
1226
- 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
+ });
1227
1339
 
1228
1340
  // src/capture/index.ts
1229
1341
  function captureCommand(opts) {
@@ -1297,11 +1409,28 @@ function captureCommand(opts) {
1297
1409
  file_path: null
1298
1410
  });
1299
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
+ });
1300
1430
 
1301
1431
  // src/cli/capture.ts
1302
- var CAPTURE_HANG_TIMEOUT_MS = 5e3;
1303
- function registerCapture(program2) {
1304
- 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) => {
1305
1434
  const hangGuard = setTimeout(() => process.exit(0), CAPTURE_HANG_TIMEOUT_MS);
1306
1435
  hangGuard.unref();
1307
1436
  try {
@@ -1322,15 +1451,28 @@ function registerCapture(program2) {
1322
1451
  }
1323
1452
  });
1324
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
+ });
1325
1463
 
1326
1464
  // src/capture/detect-secrets.ts
1327
1465
  function containsSecrets(text) {
1328
1466
  return redact(text) !== text;
1329
1467
  }
1468
+ var init_detect_secrets = __esm({
1469
+ "src/capture/detect-secrets.ts"() {
1470
+ "use strict";
1471
+ init_redact();
1472
+ }
1473
+ });
1330
1474
 
1331
1475
  // src/db/alerts.ts
1332
- var import_fs11 = __toESM(require("fs"));
1333
- var import_path7 = __toESM(require("path"));
1334
1476
  function insertAlert(tool, sessionId, context) {
1335
1477
  getDb().prepare(
1336
1478
  `INSERT INTO alerts (created_at, tool, session_id, context)
@@ -1354,6 +1496,16 @@ function clearAlerts() {
1354
1496
  }
1355
1497
  return Number(result.changes);
1356
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
+ });
1357
1509
 
1358
1510
  // src/cli/hook.ts
1359
1511
  function parseHookPayload(raw) {
@@ -1374,8 +1526,8 @@ function parseUserPromptPayload(raw) {
1374
1526
  if (!prompt) return null;
1375
1527
  return { session_id: sessionId, prompt, cwd };
1376
1528
  }
1377
- function registerHook(program2) {
1378
- 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) => {
1379
1531
  let raw = "";
1380
1532
  process.stdin.setEncoding("utf-8");
1381
1533
  process.stdin.on("data", (chunk) => {
@@ -1416,13 +1568,16 @@ function registerHook(program2) {
1416
1568
  });
1417
1569
  });
1418
1570
  }
1419
-
1420
- // src/cli/init.ts
1421
- var import_fs14 = __toESM(require("fs"));
1422
- var import_path10 = __toESM(require("path"));
1423
- var import_os5 = __toESM(require("os"));
1424
- var import_prompts = __toESM(require("prompts"));
1425
- 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
+ });
1426
1581
 
1427
1582
  // src/utils/format.ts
1428
1583
  function formatDuration(minutes) {
@@ -1482,57 +1637,18 @@ function isoWeekKey(dateStr) {
1482
1637
  const weekNum = Math.ceil(((d.getTime() - jan4.getTime()) / 864e5 + jan4.getDay() + 1) / 7);
1483
1638
  return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
1484
1639
  }
1640
+ var init_format = __esm({
1641
+ "src/utils/format.ts"() {
1642
+ "use strict";
1643
+ }
1644
+ });
1485
1645
 
1486
1646
  // src/ai/prompt.ts
1487
- 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.
1488
-
1489
- Here is an example:
1490
-
1491
- INPUT:
1492
- Project: api-service
1493
- Branch: feature/auth
1494
- Duration: 2h 00m
1495
- Events:
1496
- - versioning {"branch":"feature/auth"}
1497
- - 2x dependencies {"tool":"npm","packages":["bcrypt","jsonwebtoken"]}
1498
- - 8x testing [failed]
1499
- - testing
1500
- - 2x files {"tool":"mkdir"}
1501
- - shipping {"message":"fix: auth token validation"}
1502
- - shipping {"remote":"origin","branch":"feature/auth"}
1503
-
1504
- OUTPUT:
1505
- - Added authentication dependencies (bcrypt, JWT)
1506
- - Debugged and resolved failing auth tests
1507
- - Committed and pushed the token validation fix
1508
-
1509
- RULES \u2014 follow these exactly:
1510
-
1511
- 1. Interpret repeated events as patterns, not individual steps.
1512
- - Many testing [failed] followed by a passing test = debugging a failure. Say "resolved failing X tests", not "ran tests multiple times".
1513
- - Many versioning events = branch housekeeping. Fold into context, do not list each git command.
1514
-
1515
- 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.
1516
-
1517
- 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.
1518
-
1519
- 4. For other events with a hint field: describe the developer's intent, not the raw command.
1520
-
1521
- 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.
1522
-
1523
- 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).
1524
-
1525
- 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.
1526
-
1527
- 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.
1528
-
1529
- Maximum 5 bullets. Output ONLY the bullet points, nothing else.`;
1530
1647
  function getSystemPrompt() {
1531
1648
  const contexts = getActiveProfileAiContexts();
1532
1649
  if (contexts.length === 0) return SYSTEM_PROMPT;
1533
1650
  return SYSTEM_PROMPT + "\n\n" + contexts.join("\n");
1534
1651
  }
1535
- var DEFAULT_EXCLUDED_CATEGORIES = /* @__PURE__ */ new Set(["exploring", "note", "linting"]);
1536
1652
  function buildExcludedSet(includeCategories = []) {
1537
1653
  const excluded = new Set(DEFAULT_EXCLUDED_CATEGORIES);
1538
1654
  for (const cat of includeCategories) excluded.delete(cat);
@@ -1584,39 +1700,6 @@ function buildUserMessage(payload, includeCategories = []) {
1584
1700
  }
1585
1701
  return lines.join("\n");
1586
1702
  }
1587
- var WEEKLY_SYSTEM_PROMPT = `You write short weekly developer summaries. Here is an example:
1588
-
1589
- INPUT:
1590
- Week: 2026-05-18 to 2026-05-22
1591
-
1592
- Monday 2026-05-18:
1593
- Project: api-service (feature/auth) \u2014 2h 10m
1594
- Events: 3x code_commit, 2x test_run, dependency_install
1595
-
1596
- Tuesday 2026-05-19:
1597
- Project: api-service (feature/auth) \u2014 1h 30m
1598
- Events: 2x code_commit, test_run, build
1599
-
1600
- Wednesday 2026-05-20:
1601
- Project: dashboard (main) \u2014 3h 00m
1602
- Events: 4x code_commit, 2x build, test_run
1603
-
1604
- Thursday 2026-05-21:
1605
- Project: api-service (feature/auth) \u2014 1h 00m
1606
- Events: code_commit {"message":"feat: merge auth"}, branch_switch
1607
-
1608
- Friday 2026-05-22:
1609
- Project: dashboard (main) \u2014 2h 00m
1610
- Events: 3x code_commit, build
1611
-
1612
- OUTPUT:
1613
- ## Week of 18 May \u2014 9h 40m across 2 projects
1614
-
1615
- **api-service** (Mon\u2013Thu): Built and shipped the auth feature \u2014 JWT setup, token validation fixes, and final merge.
1616
- **dashboard** (Wed, Fri): Active development across 7 commits with builds passing.
1617
-
1618
- Now summarise the following week. Output ONLY the summary \u2014 a one-line header then one bullet per active project. Maximum 4 bullets.`;
1619
- var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1620
1703
  function buildWeeklyMessage(payload) {
1621
1704
  const lines = [`Week: ${payload.weekStart} to ${payload.weekEnd}`, ""];
1622
1705
  for (const { date, sessions } of payload.days) {
@@ -1654,194 +1737,293 @@ function buildWeeklyMessage(payload) {
1654
1737
  }
1655
1738
  return lines.join("\n");
1656
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.
1657
1747
 
1658
- // src/ai/generic-provider.ts
1659
- var GenericProvider = class {
1660
- constructor(config, model, apiKey, baseUrl, includeCategories = []) {
1661
- this.config = config;
1662
- this.model = model;
1663
- this.apiKey = apiKey;
1664
- this.baseUrl = baseUrl;
1665
- this.includeCategories = includeCategories;
1666
- }
1667
- config;
1668
- model;
1669
- apiKey;
1670
- baseUrl;
1671
- includeCategories;
1672
- async call(system, userMsg, maxTokens) {
1673
- const response = await fetchWithTimeout(this.config.chatUrl(this.baseUrl, this.apiKey, this.model), {
1674
- method: "POST",
1675
- headers: this.config.headers(this.apiKey),
1676
- body: JSON.stringify(this.config.buildChatBody(this.model, system, userMsg, maxTokens))
1677
- });
1678
- if (!response.ok) {
1679
- throw new Error(this.config.chatError(response.status, await response.text()));
1680
- }
1681
- return this.config.parseText(await response.json());
1682
- }
1683
- async summarize(payload) {
1684
- return this.call(getSystemPrompt(), buildUserMessage(payload, this.includeCategories), this.config.dailyTokens);
1685
- }
1686
- async summarizeWeek(payload) {
1687
- 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"];
1688
1823
  }
1689
- async validate() {
1690
- const isPost = this.config.validateMethod === "POST";
1691
- const response = await fetchWithTimeout(this.config.validateUrl(this.baseUrl, this.apiKey, this.model), {
1692
- method: this.config.validateMethod,
1693
- headers: isPost ? this.config.headers(this.apiKey) : void 0,
1694
- body: isPost && this.config.buildValidateBody ? JSON.stringify(this.config.buildValidateBody(this.model)) : void 0
1695
- });
1696
- if (!response.ok) {
1697
- throw new Error(this.config.validateError(response.status, await response.text(), this.baseUrl));
1698
- }
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
+ };
1699
1875
  }
1700
- };
1876
+ });
1701
1877
 
1702
1878
  // src/ai/provider-config.ts
1703
- var PROVIDER_CONFIGS = {
1704
- anthropic: {
1705
- defaultModel: "claude-haiku-4-5-20251001",
1706
- defaultBaseUrl: "https://api.anthropic.com/v1",
1707
- dailyTokens: 1024,
1708
- weeklyTokens: 1024,
1709
- chatUrl: (baseUrl) => `${baseUrl}/messages`,
1710
- validateUrl: (baseUrl) => `${baseUrl}/messages`,
1711
- validateMethod: "POST",
1712
- headers: (apiKey) => ({
1713
- "Content-Type": "application/json",
1714
- "x-api-key": apiKey,
1715
- "anthropic-version": "2023-06-01"
1716
- }),
1717
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1718
- model,
1719
- max_tokens: maxTokens,
1720
- system,
1721
- messages: [{ role: "user", content: userMsg }]
1722
- }),
1723
- buildValidateBody: (model) => ({
1724
- model,
1725
- max_tokens: 1,
1726
- messages: [{ role: "user", content: "." }]
1727
- }),
1728
- parseText: (json) => {
1729
- const data = json;
1730
- return data.content.find((b) => b.type === "text")?.text ?? "";
1731
- },
1732
- chatError: (status, body) => `Anthropic API error ${status}: ${body}`,
1733
- validateError: (status, body) => `Anthropic API error ${status}: ${body}`
1734
- },
1735
- openai: {
1736
- defaultModel: "gpt-4o-mini",
1737
- defaultBaseUrl: "https://api.openai.com/v1",
1738
- dailyTokens: 1024,
1739
- weeklyTokens: 1024,
1740
- chatUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1741
- validateUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1742
- validateMethod: "POST",
1743
- headers: (apiKey) => ({
1744
- "Content-Type": "application/json",
1745
- Authorization: `Bearer ${apiKey}`
1746
- }),
1747
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1748
- model,
1749
- messages: [
1750
- { role: "system", content: system },
1751
- { role: "user", content: userMsg }
1752
- ],
1753
- max_tokens: maxTokens
1754
- }),
1755
- buildValidateBody: (model) => ({
1756
- model,
1757
- messages: [{ role: "user", content: "." }],
1758
- max_tokens: 1
1759
- }),
1760
- parseText: (json) => {
1761
- const data = json;
1762
- return data.choices[0]?.message?.content ?? "";
1763
- },
1764
- chatError: (status, body) => `OpenAI API error ${status}: ${body}`,
1765
- validateError: (status, body) => `OpenAI API error ${status}: ${body}`
1766
- },
1767
- ollama: {
1768
- defaultModel: "llama3",
1769
- defaultBaseUrl: "http://localhost:11434",
1770
- dailyTokens: 300,
1771
- weeklyTokens: 400,
1772
- chatUrl: (baseUrl) => `${baseUrl}/api/chat`,
1773
- validateUrl: (baseUrl) => `${baseUrl}/api/tags`,
1774
- validateMethod: "GET",
1775
- headers: () => ({ "Content-Type": "application/json" }),
1776
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1777
- model,
1778
- stream: false,
1779
- messages: [
1780
- { role: "system", content: system },
1781
- { role: "user", content: userMsg }
1782
- ],
1783
- options: { num_predict: maxTokens, temperature: 0.3 }
1784
- }),
1785
- parseText: (json) => {
1786
- return json.message?.content ?? "";
1787
- },
1788
- chatError: (status, body) => `Ollama error ${status}: ${body}. Is Ollama running? Try: ollama serve`,
1789
- validateError: (_status, _body, baseUrl) => `Ollama is not reachable at ${baseUrl}. Is it running? Try: ollama serve`
1790
- },
1791
- gemini: {
1792
- defaultModel: "gemini-2.0-flash-lite",
1793
- defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
1794
- dailyTokens: 1024,
1795
- weeklyTokens: 1024,
1796
- // API key and model are embedded in the URL — not in headers
1797
- chatUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
1798
- validateUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
1799
- validateMethod: "POST",
1800
- headers: () => ({ "Content-Type": "application/json" }),
1801
- buildChatBody: (_model, system, userMsg, maxTokens) => ({
1802
- system_instruction: { parts: [{ text: system }] },
1803
- contents: [{ role: "user", parts: [{ text: userMsg }] }],
1804
- generationConfig: { maxOutputTokens: maxTokens, temperature: 0.3 }
1805
- }),
1806
- buildValidateBody: () => ({
1807
- contents: [{ role: "user", parts: [{ text: "." }] }],
1808
- generationConfig: { maxOutputTokens: 1 }
1809
- }),
1810
- parseText: (json) => {
1811
- const data = json;
1812
- return data.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
1813
- },
1814
- chatError: (status, body) => `Gemini API error ${status}: ${body}`,
1815
- validateError: (status, body) => `Gemini API error ${status}: ${body}`
1816
- },
1817
- groq: {
1818
- defaultModel: "llama-3.1-8b-instant",
1819
- defaultBaseUrl: "https://api.groq.com/openai/v1",
1820
- dailyTokens: 1024,
1821
- weeklyTokens: 1024,
1822
- chatUrl: (baseUrl) => `${baseUrl}/chat/completions`,
1823
- validateUrl: (baseUrl) => `${baseUrl}/models`,
1824
- validateMethod: "GET",
1825
- headers: (apiKey) => ({
1826
- "Content-Type": "application/json",
1827
- Authorization: `Bearer ${apiKey}`
1828
- }),
1829
- buildChatBody: (model, system, userMsg, maxTokens) => ({
1830
- model,
1831
- messages: [
1832
- { role: "system", content: system },
1833
- { role: "user", content: userMsg }
1834
- ],
1835
- max_tokens: maxTokens
1836
- }),
1837
- parseText: (json) => {
1838
- const data = json;
1839
- return data.choices?.[0]?.message?.content ?? "";
1840
- },
1841
- chatError: (status, body) => `Groq API error ${status}: ${body}`,
1842
- 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
+ };
1843
2025
  }
1844
- };
2026
+ });
1845
2027
 
1846
2028
  // src/ai/router.ts
1847
2029
  function getProvider(config) {
@@ -1874,12 +2056,13 @@ Run: workscribe config set ai.apiKey <your-key>`);
1874
2056
  config.summary?.includeCategories ?? []
1875
2057
  );
1876
2058
  }
1877
-
1878
- // src/capture/shell-hook.ts
1879
- var import_fs12 = __toESM(require("fs"));
1880
- var import_path8 = __toESM(require("path"));
1881
- var import_os3 = __toESM(require("os"));
1882
- 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
+ });
1883
2066
 
1884
2067
  // src/utils/completion-scripts.ts
1885
2068
  function bashCompletion(commands) {
@@ -1934,12 +2117,13 @@ function fishCompletion(commands) {
1934
2117
  ];
1935
2118
  return lines.join("\n");
1936
2119
  }
2120
+ var init_completion_scripts = __esm({
2121
+ "src/utils/completion-scripts.ts"() {
2122
+ "use strict";
2123
+ }
2124
+ });
1937
2125
 
1938
2126
  // src/capture/shell-hook.ts
1939
- var FISH_COMPLETIONS_DIR = import_path8.default.join(import_os3.default.homedir(), ".config", "fish", "completions");
1940
- var HOOK_START = "# workscribe:hook:start";
1941
- var HOOK_END = "# workscribe:hook:end";
1942
- var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
1943
2127
  function getInstalledHookBlock(rcFile) {
1944
2128
  if (!import_fs12.default.existsSync(rcFile)) return null;
1945
2129
  const content = import_fs12.default.readFileSync(rcFile, "utf-8");
@@ -1994,9 +2178,10 @@ function resolveWorkscribeBinary() {
1994
2178
  return null;
1995
2179
  }
1996
2180
  }
1997
- function injectBinaryPath(hookContent, binaryPath) {
2181
+ function injectBinaryPath(hookContent, binaryPath, nodePath) {
2182
+ const pinnedInvocation = `"${nodePath}" "${binaryPath}"`;
1998
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}"
1999
- 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`);
2000
2185
  }
2001
2186
  function isCompletionsInstalled(shell) {
2002
2187
  if (shell.name === "fish") {
@@ -2029,14 +2214,25 @@ source "${cacheFile}"
2029
2214
  import_fs12.default.appendFileSync(shell.rcFile, sourceLine);
2030
2215
  }
2031
2216
  }
2032
-
2033
- // src/capture/hooks/claude-code.ts
2034
- var import_fs13 = __toESM(require("fs"));
2035
- var import_path9 = __toESM(require("path"));
2036
- 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
+ });
2037
2234
 
2038
2235
  // src/capture/hooks/utils.ts
2039
- var import_child_process4 = require("child_process");
2040
2236
  function isCommandInstalled(cmd) {
2041
2237
  try {
2042
2238
  (0, import_child_process4.execSync)(`which ${cmd}`, { stdio: "ignore" });
@@ -2045,9 +2241,15 @@ function isCommandInstalled(cmd) {
2045
2241
  return false;
2046
2242
  }
2047
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
+ });
2048
2251
 
2049
2252
  // src/capture/hooks/claude-code.ts
2050
- var CLAUDE_SETTINGS_PATH = import_path9.default.join(import_os4.default.homedir(), ".claude", "settings.json");
2051
2253
  function readSettings() {
2052
2254
  if (!import_fs13.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
2053
2255
  try {
@@ -2056,86 +2258,155 @@ function readSettings() {
2056
2258
  return {};
2057
2259
  }
2058
2260
  }
2059
- var claudeCodeIntegration = {
2060
- displayName: "Claude Code",
2061
- isInstalled() {
2062
- return isCommandInstalled("claude");
2063
- },
2064
- isHookInstalled() {
2065
- const settings = readSettings();
2066
- const hooks = settings.hooks ?? {};
2067
- const postToolUse = hooks.PostToolUse ?? [];
2068
- const hasPostToolUse = postToolUse.some((entry) => {
2069
- const entryHooks = entry.hooks ?? [];
2070
- return entryHooks.some((hookItem) => {
2071
- const hook = hookItem;
2072
- return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
2073
- });
2074
- });
2075
- const userPromptSubmit = hooks.UserPromptSubmit ?? [];
2076
- const hasUserPromptSubmit = userPromptSubmit.some((entry) => {
2077
- const entryHooks = entry.hooks ?? [];
2078
- return entryHooks.some((hookItem) => {
2079
- const hook = hookItem;
2080
- return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
2081
- });
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));
2082
2293
  });
2083
- return hasPostToolUse && hasUserPromptSubmit;
2084
- },
2085
- install() {
2086
- const settings = readSettings();
2087
- const existingHooks = settings.hooks ?? {};
2088
- const existingPostToolUse = existingHooks.PostToolUse ?? [];
2089
- const existingUserPromptSubmit = existingHooks.UserPromptSubmit ?? [];
2090
- settings.hooks = {
2091
- ...existingHooks,
2092
- PostToolUse: [
2093
- ...existingPostToolUse,
2094
- {
2095
- matcher: "Bash",
2096
- hooks: [{ type: "command", command: "workscribe _hook --source claude-code", async: true }]
2097
- }
2098
- ],
2099
- UserPromptSubmit: [
2100
- ...existingUserPromptSubmit,
2101
- {
2102
- 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,
2103
2348
  {
2104
- type: "command",
2105
- command: "workscribe _hook --source claude-code --event UserPromptSubmit"
2349
+ matcher: "Bash",
2350
+ hooks: [{ type: "command", command: `${baseCommand} ${HOOK_MARKER}`, async: true }]
2351
+ }
2352
+ ],
2353
+ UserPromptSubmit: [
2354
+ ...existingUserPromptSubmit,
2355
+ {
2356
+ hooks: [
2357
+ {
2358
+ type: "command",
2359
+ command: `${baseCommand} ${HOOK_MARKER} --event UserPromptSubmit`
2360
+ }
2361
+ ]
2106
2362
  }
2107
2363
  ]
2108
- }
2109
- ]
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
+ }
2110
2368
  };
2111
- import_fs13.default.mkdirSync(import_path9.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
2112
- import_fs13.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
2113
2369
  }
2114
- };
2370
+ });
2115
2371
 
2116
2372
  // src/capture/hooks/index.ts
2117
- var AI_HOOK_INTEGRATIONS = {
2118
- "claude-code": claudeCodeIntegration
2119
- };
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
+ });
2120
2383
 
2121
2384
  // src/cli/completions.ts
2122
- var HIDDEN_COMMANDS = /* @__PURE__ */ new Set(["_capture", "_hook"]);
2123
- function getPublicCommands(program2) {
2124
- 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() }));
2125
2387
  }
2126
- function registerCompletions(program2) {
2127
- 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) => {
2128
2390
  const detected = shell ?? (process.env.SHELL ?? "").split("/").at(-1);
2129
2391
  if (!detected || !["bash", "zsh", "fish"].includes(detected)) {
2130
2392
  log.error(`Unknown shell: "${detected ?? ""}". Supported: bash, zsh, fish`);
2131
2393
  log.plain(` Usage: workscribe completions <bash|zsh|fish>`);
2132
2394
  process.exit(1);
2133
2395
  }
2134
- const commands = getPublicCommands(program2);
2396
+ const commands = getPublicCommands(program);
2135
2397
  const script = detected === "bash" ? bashCompletion(commands) : detected === "zsh" ? zshCompletion(commands) : fishCompletion(commands);
2136
2398
  process.stdout.write(script + "\n");
2137
2399
  });
2138
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
+ });
2139
2410
 
2140
2411
  // src/cli/init.ts
2141
2412
  function detectShell() {
@@ -2152,8 +2423,8 @@ function step(current, total, label) {
2152
2423
  log.plain(import_chalk2.default.bold(`
2153
2424
  [${current}/${total}] ${label}`));
2154
2425
  }
2155
- function registerInit(program2) {
2156
- 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 () => {
2157
2428
  const TOTAL_STEPS = 5;
2158
2429
  log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
2159
2430
  step(1, TOTAL_STEPS, "Creating data directories");
@@ -2189,7 +2460,7 @@ function registerInit(program2) {
2189
2460
  } else {
2190
2461
  const hookContent = import_fs14.default.readFileSync(hookFile, "utf-8");
2191
2462
  const binaryPath = resolveWorkscribeBinary();
2192
- const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath) : hookContent;
2463
+ const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath, process.execPath) : hookContent;
2193
2464
  if (shell.name === "fish") {
2194
2465
  const confDir = import_path10.default.dirname(shell.rcFile);
2195
2466
  if (!import_fs14.default.existsSync(confDir)) import_fs14.default.mkdirSync(confDir, { recursive: true });
@@ -2231,7 +2502,7 @@ function registerInit(program2) {
2231
2502
  });
2232
2503
  if (installComps) {
2233
2504
  try {
2234
- installCompletions(shell, getPublicCommands(program2));
2505
+ installCompletions(shell, getPublicCommands(program));
2235
2506
  log.success("Shell completions installed");
2236
2507
  } catch (error) {
2237
2508
  log.warn(`Completions install failed: ${error.message}`);
@@ -2402,12 +2673,32 @@ function registerInit(program2) {
2402
2673
  log.plain("");
2403
2674
  });
2404
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
+ });
2405
2698
 
2406
2699
  // src/cli/status.ts
2407
- var import_fs15 = __toESM(require("fs"));
2408
- var import_chalk3 = __toESM(require("chalk"));
2409
- function registerStatus(program2) {
2410
- 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(() => {
2411
2702
  const config = readConfig();
2412
2703
  const total = getEventCount();
2413
2704
  const todayEvents = getEventsForDate(todayDate());
@@ -2442,6 +2733,20 @@ function registerStatus(program2) {
2442
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)")
2443
2734
  );
2444
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
+ }
2445
2750
  const { total: weekTotal, otherCount } = getRecentOtherRate(7);
2446
2751
  if (weekTotal > 0) {
2447
2752
  const otherPct = Math.round(otherCount / weekTotal * 100);
@@ -2460,30 +2765,26 @@ function registerStatus(program2) {
2460
2765
  log.plain("");
2461
2766
  });
2462
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
+ });
2463
2784
 
2464
2785
  // src/cli/events.ts
2465
- var import_chalk4 = __toESM(require("chalk"));
2466
- var CATEGORY_COLORS = {
2467
- shipping: import_chalk4.default.green,
2468
- versioning: import_chalk4.default.blue,
2469
- building: import_chalk4.default.cyan,
2470
- running: import_chalk4.default.white,
2471
- testing: import_chalk4.default.magenta,
2472
- linting: import_chalk4.default.yellow,
2473
- dependencies: import_chalk4.default.yellow,
2474
- infrastructure: import_chalk4.default.red,
2475
- releasing: import_chalk4.default.bold.green,
2476
- database: import_chalk4.default.bold.blue,
2477
- files: import_chalk4.default.dim.yellow,
2478
- exploring: import_chalk4.default.dim.cyan,
2479
- ai_session: import_chalk4.default.bold.magenta,
2480
- other: import_chalk4.default.gray
2481
- };
2482
- var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
2483
- loadAIToolRegistry().map((entry) => [entry.tool, entry.displayName])
2484
- );
2485
- function registerEvents(program2) {
2486
- 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) => {
2487
2788
  let date = todayDate();
2488
2789
  if (opts.date) {
2489
2790
  try {
@@ -2530,12 +2831,38 @@ function registerEvents(program2) {
2530
2831
  log.plain("");
2531
2832
  });
2532
2833
  }
2533
-
2534
- // src/cli/sessions.ts
2535
- 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
+ });
2536
2864
 
2537
2865
  // src/sessions/grouper.ts
2538
- var LONG_RUNNING_THRESHOLD_MS = 5 * 60 * 1e3;
2539
2866
  function splitByIdleGap(events, idleMs) {
2540
2867
  if (events.length === 0) return [];
2541
2868
  const groups = [[events[0]]];
@@ -2552,7 +2879,6 @@ function splitByIdleGap(events, idleMs) {
2552
2879
  }
2553
2880
  return groups;
2554
2881
  }
2555
- var REPO_SWITCH_GAP_MS = 10 * 60 * 1e3;
2556
2882
  function splitByRepo(events, gapMs = 0) {
2557
2883
  if (events.length === 0) return [];
2558
2884
  const groups = [[events[0]]];
@@ -2573,6 +2899,14 @@ function splitByRepo(events, gapMs = 0) {
2573
2899
  function minutesBetween(start, end) {
2574
2900
  return (new Date(end).getTime() - new Date(start).getTime()) / 6e4;
2575
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
+ });
2576
2910
 
2577
2911
  // src/sessions/detector.ts
2578
2912
  function getOrBuildSessions(date) {
@@ -2640,10 +2974,18 @@ function buildSessions(date, events, savedSummaries = /* @__PURE__ */ new Map())
2640
2974
  }
2641
2975
  return sessions;
2642
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
+ });
2643
2985
 
2644
2986
  // src/cli/sessions.ts
2645
- function registerSessions(program2) {
2646
- 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) => {
2647
2989
  let date = todayDate();
2648
2990
  if (opts.date) {
2649
2991
  try {
@@ -2683,12 +3025,16 @@ function registerSessions(program2) {
2683
3025
  log.plain("");
2684
3026
  });
2685
3027
  }
2686
-
2687
- // src/cli/summary.ts
2688
- var import_chalk6 = __toESM(require("chalk"));
2689
- var import_fs16 = __toESM(require("fs"));
2690
- var import_os6 = __toESM(require("os"));
2691
- 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
+ });
2692
3038
 
2693
3039
  // src/integrations/webhook.ts
2694
3040
  async function postToWebhook(url, text) {
@@ -2706,6 +3052,12 @@ async function postToWebhook(url, text) {
2706
3052
  throw new Error(`Webhook POST failed ${response.status}: ${body}`);
2707
3053
  }
2708
3054
  }
3055
+ var init_webhook = __esm({
3056
+ "src/integrations/webhook.ts"() {
3057
+ "use strict";
3058
+ init_fetch();
3059
+ }
3060
+ });
2709
3061
 
2710
3062
  // src/integrations/slack.ts
2711
3063
  function formatForSlack(markdown, username) {
@@ -2716,6 +3068,11 @@ ${body}` : body;
2716
3068
  function isSlackWebhookUrl(url) {
2717
3069
  return url.startsWith("https://hooks.slack.com/");
2718
3070
  }
3071
+ var init_slack = __esm({
3072
+ "src/integrations/slack.ts"() {
3073
+ "use strict";
3074
+ }
3075
+ });
2719
3076
 
2720
3077
  // src/cli/summary.ts
2721
3078
  function parseBullets(text) {
@@ -2767,8 +3124,8 @@ async function handleWebhook(opts, config, summary) {
2767
3124
  log.warn("--slack requires integrations.slackWebhook to be set in config");
2768
3125
  }
2769
3126
  }
2770
- function registerSummary(program2) {
2771
- 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) => {
2772
3129
  const config = readConfig();
2773
3130
  let provider;
2774
3131
  try {
@@ -2928,10 +3285,28 @@ ${bullets.trim()}`;
2928
3285
  await handleWebhook(opts, config, fullSummary);
2929
3286
  });
2930
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
+ });
2931
3308
 
2932
3309
  // src/cli/export.ts
2933
- var import_fs17 = __toESM(require("fs"));
2934
- var import_path12 = __toESM(require("path"));
2935
3310
  function eventRowToPayload2(row) {
2936
3311
  let metadata = {};
2937
3312
  try {
@@ -2951,8 +3326,8 @@ function eventRowToPayload2(row) {
2951
3326
  }
2952
3327
  return payload;
2953
3328
  }
2954
- function registerExport(program2) {
2955
- 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) => {
2956
3331
  let date = todayDate();
2957
3332
  if (opts.date) {
2958
3333
  try {
@@ -2996,11 +3371,24 @@ ${summary}
2996
3371
  log.success(`Saved: ${outputPath}`);
2997
3372
  });
2998
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
+ });
2999
3388
 
3000
3389
  // src/cli/config.ts
3001
- var import_chalk7 = __toESM(require("chalk"));
3002
- function registerConfig(program2) {
3003
- 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(() => {
3004
3392
  const config = readConfig();
3005
3393
  log.plain("");
3006
3394
  log.plain(import_chalk7.default.bold(" Workscribe Config"));
@@ -3031,16 +3419,6 @@ function registerConfig(program2) {
3031
3419
  }
3032
3420
  });
3033
3421
  }
3034
- var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
3035
- "apiKey",
3036
- "api_key",
3037
- "secret",
3038
- "token",
3039
- "password",
3040
- "passwd",
3041
- "webhook",
3042
- "slackWebhook"
3043
- ]);
3044
3422
  function maskValue(key, value) {
3045
3423
  if (SENSITIVE_KEYS.has(key) && typeof value === "string" && value.length > 0) {
3046
3424
  return import_chalk7.default.dim(`${value.slice(0, 4)}${"*".repeat(8)} (${value.length} chars)`);
@@ -3059,11 +3437,29 @@ function printConfig(obj, indent) {
3059
3437
  }
3060
3438
  }
3061
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
+ });
3062
3459
 
3063
3460
  // src/cli/pause.ts
3064
- var import_fs18 = __toESM(require("fs"));
3065
- function registerPause(program2) {
3066
- program2.command("pause").description("Pause capture temporarily").action(() => {
3461
+ function registerPause(program) {
3462
+ program.command("pause").description("Pause capture temporarily").action(() => {
3067
3463
  if (import_fs18.default.existsSync(PAUSE_FLAG_PATH)) {
3068
3464
  log.info("Capture is already paused. Run workscribe resume to restart.");
3069
3465
  return;
@@ -3071,7 +3467,7 @@ function registerPause(program2) {
3071
3467
  import_fs18.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
3072
3468
  log.success("Capture paused. Run workscribe resume to restart.");
3073
3469
  });
3074
- program2.command("resume").description("Resume capture after a pause").action(() => {
3470
+ program.command("resume").description("Resume capture after a pause").action(() => {
3075
3471
  if (!import_fs18.default.existsSync(PAUSE_FLAG_PATH)) {
3076
3472
  log.info("Capture is not paused.");
3077
3473
  return;
@@ -3080,10 +3476,19 @@ function registerPause(program2) {
3080
3476
  log.success("Capture resumed.");
3081
3477
  });
3082
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
+ });
3083
3488
 
3084
3489
  // src/cli/undo.ts
3085
- function registerUndo(program2) {
3086
- 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) => {
3087
3492
  const count = parseInt(opts.count, 10);
3088
3493
  if (isNaN(count) || count < 1) {
3089
3494
  log.error("--count must be a positive integer");
@@ -3109,12 +3514,17 @@ function registerUndo(program2) {
3109
3514
  log.success(`Removed ${ids.length} event${ids.length > 1 ? "s" : ""}.`);
3110
3515
  });
3111
3516
  }
3517
+ var init_undo = __esm({
3518
+ "src/cli/undo.ts"() {
3519
+ "use strict";
3520
+ init_db();
3521
+ init_logger();
3522
+ }
3523
+ });
3112
3524
 
3113
3525
  // src/cli/clear.ts
3114
- var import_chalk8 = __toESM(require("chalk"));
3115
- var import_prompts2 = __toESM(require("prompts"));
3116
- function registerClear(program2) {
3117
- 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) => {
3118
3528
  let date = todayDate();
3119
3529
  if (opts.date) {
3120
3530
  try {
@@ -3159,13 +3569,19 @@ function registerClear(program2) {
3159
3569
  log.success(`Cleared ${deleted} event${deleted !== 1 ? "s" : ""} for ${date}.`);
3160
3570
  });
3161
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
+ });
3162
3583
 
3163
3584
  // src/cli/uninstall.ts
3164
- var import_fs19 = __toESM(require("fs"));
3165
- var import_path13 = __toESM(require("path"));
3166
- var import_os7 = __toESM(require("os"));
3167
- var import_prompts3 = __toESM(require("prompts"));
3168
- var import_chalk9 = __toESM(require("chalk"));
3169
3585
  function detectShellRcFiles() {
3170
3586
  const candidates = [
3171
3587
  { name: "zsh", rcFile: import_path13.default.join(import_os7.default.homedir(), ".zshrc") },
@@ -3175,8 +3591,8 @@ function detectShellRcFiles() {
3175
3591
  ];
3176
3592
  return candidates.filter((candidate) => import_fs19.default.existsSync(candidate.rcFile) && isHookInstalled(candidate.rcFile));
3177
3593
  }
3178
- function registerUninstall(program2) {
3179
- 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) => {
3180
3596
  log.plain(import_chalk9.default.bold("\n Workscribe \u2014 Uninstall\n"));
3181
3597
  const hooked = detectShellRcFiles();
3182
3598
  if (hooked.length === 0) {
@@ -3221,14 +3637,24 @@ function registerUninstall(program2) {
3221
3637
  log.plain("");
3222
3638
  });
3223
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
+ });
3224
3654
 
3225
3655
  // src/cli/profile.ts
3226
- var import_fs20 = __toESM(require("fs"));
3227
- var import_path14 = __toESM(require("path"));
3228
- var import_chalk10 = __toESM(require("chalk"));
3229
- var MIN_SUGGEST_COUNT = 5;
3230
- function registerProfile(program2) {
3231
- 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");
3232
3658
  profile.command("list").description("List available profiles from the registry").action(async () => {
3233
3659
  const config = readConfig();
3234
3660
  const activeNames = new Set(config.capture.profiles);
@@ -3434,10 +3860,26 @@ function registerProfile(program2) {
3434
3860
  }
3435
3861
  });
3436
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
+ });
3437
3879
 
3438
3880
  // src/cli/note.ts
3439
- function registerNote(program2) {
3440
- 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) => {
3441
3883
  const { repoName, branch } = getGitContext(process.cwd());
3442
3884
  insertEvent({
3443
3885
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3455,12 +3897,16 @@ function registerNote(program2) {
3455
3897
  log.success(`Note saved${repoName ? ` to ${repoName}` : ""}`);
3456
3898
  });
3457
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
+ });
3458
3908
 
3459
3909
  // src/cli/week.ts
3460
- var import_chalk11 = __toESM(require("chalk"));
3461
- var import_fs21 = __toESM(require("fs"));
3462
- var import_path15 = __toESM(require("path"));
3463
- var import_os8 = __toESM(require("os"));
3464
3910
  function eventRowToPayload3(row) {
3465
3911
  let metadata = {};
3466
3912
  try {
@@ -3507,8 +3953,8 @@ async function postSummary(opts, config, summary) {
3507
3953
  log.error(`Webhook failed: ${error.message}`);
3508
3954
  }
3509
3955
  }
3510
- function registerWeek(program2) {
3511
- 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) => {
3512
3958
  const config = readConfig();
3513
3959
  let provider;
3514
3960
  try {
@@ -3559,14 +4005,26 @@ function registerWeek(program2) {
3559
4005
  }
3560
4006
  });
3561
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
+ });
3562
4026
 
3563
4027
  // src/cli/bug-report.ts
3564
- var import_fs22 = __toESM(require("fs"));
3565
- var import_os9 = __toESM(require("os"));
3566
- var import_path16 = __toESM(require("path"));
3567
- var import_chalk12 = __toESM(require("chalk"));
3568
- var GITHUB_ISSUES_URL = "https://github.com/workscribe/workscribe/issues/new";
3569
- var DEBUG_LOG_TAIL_LINES = 100;
3570
4028
  function tailFile(filePath, lines) {
3571
4029
  if (!import_fs22.default.existsSync(filePath)) return "(no debug log found \u2014 set WORKSCRIBE_DEBUG=1 to enable)";
3572
4030
  const content = import_fs22.default.readFileSync(filePath, "utf-8");
@@ -3584,12 +4042,12 @@ function redactConfig() {
3584
4042
  return "(could not read config)";
3585
4043
  }
3586
4044
  }
3587
- function registerBugReport(program2) {
3588
- 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(() => {
3589
4047
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3590
4048
  const reportPath = import_path16.default.join(WORKSCRIBE_DIR, `bug-report-${timestamp}.txt`);
3591
4049
  const systemInfo = [
3592
- `Workscribe version : ${"1.14.0"}`,
4050
+ `Workscribe version : ${"1.14.1"}`,
3593
4051
  `Node.js version : ${process.version}`,
3594
4052
  `Platform : ${process.platform}`,
3595
4053
  `Architecture : ${process.arch}`,
@@ -3620,7 +4078,7 @@ function registerBugReport(program2) {
3620
4078
  process.exit(1);
3621
4079
  }
3622
4080
  const params = new URLSearchParams({
3623
- title: `Bug report \u2014 workscribe ${"1.14.0"}`,
4081
+ title: `Bug report \u2014 workscribe ${"1.14.1"}`,
3624
4082
  body: `<!-- Attach ${import_path16.default.basename(reportPath)} or paste the relevant sections below -->
3625
4083
 
3626
4084
  **Describe the issue:**
@@ -3638,9 +4096,23 @@ function registerBugReport(program2) {
3638
4096
  log.plain("");
3639
4097
  });
3640
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
+ });
3641
4114
 
3642
4115
  // src/cli/alerts.ts
3643
- var import_chalk13 = __toESM(require("chalk"));
3644
4116
  function formatTimestamp(ms) {
3645
4117
  return new Date(ms).toLocaleString([], {
3646
4118
  month: "short",
@@ -3649,8 +4121,8 @@ function formatTimestamp(ms) {
3649
4121
  minute: "2-digit"
3650
4122
  });
3651
4123
  }
3652
- function registerAlerts(program2) {
3653
- 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) => {
3654
4126
  if (opts.check) {
3655
4127
  if (hasPendingAlerts()) {
3656
4128
  log.plain(
@@ -3687,10 +4159,17 @@ function registerAlerts(program2) {
3687
4159
  }
3688
4160
  });
3689
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
+ });
3690
4171
 
3691
4172
  // src/cli/context.ts
3692
- var import_path17 = __toESM(require("path"));
3693
- var import_chalk14 = __toESM(require("chalk"));
3694
4173
  function toContextJson(session) {
3695
4174
  return {
3696
4175
  date: session.date,
@@ -3725,8 +4204,8 @@ function printSessionContext(session, heading) {
3725
4204
  }
3726
4205
  log.plain("");
3727
4206
  }
3728
- function registerContext(program2) {
3729
- program2.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) => {
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) => {
3730
4209
  if (opts.file) {
3731
4210
  const normalizedFile = import_path17.default.normalize(opts.file);
3732
4211
  const session = getLastSessionTouchingFile(normalizedFile);
@@ -3761,10 +4240,21 @@ function registerContext(program2) {
3761
4240
  printSessionContext(sessions[0], `Last session on ${repoName}`);
3762
4241
  });
3763
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
+ });
3764
4256
 
3765
4257
  // src/utils/update-check.ts
3766
- var import_fs23 = __toESM(require("fs"));
3767
- var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3768
4258
  function readCache() {
3769
4259
  try {
3770
4260
  return JSON.parse(import_fs23.default.readFileSync(UPDATE_CHECK_PATH, "utf-8"));
@@ -3787,7 +4277,7 @@ function isNewer(latest, current) {
3787
4277
  return latestPatch > currentPatch;
3788
4278
  }
3789
4279
  function checkForUpdate() {
3790
- const current = "1.14.0";
4280
+ const current = "1.14.1";
3791
4281
  const cache = readCache();
3792
4282
  if (cache && isNewer(cache.latestVersion, current)) {
3793
4283
  process.stderr.write(
@@ -3807,31 +4297,102 @@ function checkForUpdate() {
3807
4297
  });
3808
4298
  }
3809
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
+ }
3810
4394
 
3811
4395
  // src/index.ts
3812
4396
  assertNodeVersion(process.versions.node);
3813
- checkForUpdate();
3814
- var program = new import_commander.Command();
3815
- program.name("workscribe").description("Automatic developer work journal").version("1.14.0");
3816
- registerCapture(program);
3817
- registerHook(program);
3818
- registerInit(program);
3819
- registerStatus(program);
3820
- registerEvents(program);
3821
- registerSessions(program);
3822
- registerSummary(program);
3823
- registerExport(program);
3824
- registerConfig(program);
3825
- registerPause(program);
3826
- registerUndo(program);
3827
- registerClear(program);
3828
- registerUninstall(program);
3829
- registerProfile(program);
3830
- registerNote(program);
3831
- registerWeek(program);
3832
- registerBugReport(program);
3833
- registerCompletions(program);
3834
- registerAlerts(program);
3835
- registerContext(program);
3836
- program.parse();
4397
+ require_main();
3837
4398
  //# sourceMappingURL=index.js.map