@workscribe/cli 1.7.2 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_commander = require("commander");
28
28
 
29
29
  // src/capture/index.ts
30
- var import_fs4 = __toESM(require("fs"));
30
+ var import_fs7 = __toESM(require("fs"));
31
31
 
32
32
  // src/db/index.ts
33
33
  var import_node_sqlite = require("node:sqlite");
@@ -121,7 +121,7 @@ function applyMigrations(db) {
121
121
  });
122
122
  }
123
123
  const applied = new Set(
124
- db.prepare("SELECT version FROM schema_migrations").all().map((r) => r.version)
124
+ db.prepare("SELECT version FROM schema_migrations").all().map((row) => row.version)
125
125
  );
126
126
  for (const migration of MIGRATIONS) {
127
127
  if (applied.has(migration.version)) continue;
@@ -208,7 +208,7 @@ function updateSessionSummary(sessionId, summary) {
208
208
  function getLastEventIds(n) {
209
209
  const db = getDb();
210
210
  const rows = db.prepare("SELECT id FROM events ORDER BY id DESC LIMIT $n").all({ n });
211
- return rows.map((r) => r.id);
211
+ return rows.map((row) => row.id);
212
212
  }
213
213
  function getEventsByIds(ids) {
214
214
  if (ids.length === 0) return [];
@@ -345,6 +345,28 @@ function getCommitsSince(sha, cwd) {
345
345
  }
346
346
  }
347
347
 
348
+ // src/capture/aider.ts
349
+ var import_fs3 = __toESM(require("fs"));
350
+ var import_path3 = __toESM(require("path"));
351
+ var HISTORY_FILE = ".aider.chat.history.md";
352
+ var PROMPT_MAX_LENGTH = 200;
353
+ function readLastAiderPrompt(cwd) {
354
+ try {
355
+ const histPath = import_path3.default.join(cwd, HISTORY_FILE);
356
+ const content = import_fs3.default.readFileSync(histPath, "utf-8");
357
+ const matches = [...content.matchAll(/^> (.+)$/gm)];
358
+ if (!matches.length) return null;
359
+ return matches[matches.length - 1][1].trim().slice(0, PROMPT_MAX_LENGTH);
360
+ } catch {
361
+ return null;
362
+ }
363
+ }
364
+
365
+ // src/config/registry.ts
366
+ var import_fs5 = __toESM(require("fs"));
367
+ var import_os2 = __toESM(require("os"));
368
+ var import_path4 = __toESM(require("path"));
369
+
348
370
  // src/config/ai-tools.json
349
371
  var ai_tools_default = [
350
372
  {
@@ -405,6 +427,54 @@ var ai_tools_default = [
405
427
  }
406
428
  ];
407
429
 
430
+ // src/utils/logger.ts
431
+ var import_chalk = __toESM(require("chalk"));
432
+ var import_fs4 = __toESM(require("fs"));
433
+ var isDebug = process.env.WORKSCRIBE_DEBUG === "1";
434
+ var log = {
435
+ info: (msg) => console.log(import_chalk.default.cyan("\u2192"), msg),
436
+ success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
437
+ warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
438
+ error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
439
+ plain: (msg) => console.log(msg)
440
+ };
441
+ function debugLog(context, error) {
442
+ if (!isDebug) return;
443
+ const entry = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${context}] ${error instanceof Error ? error.stack : String(error)}
444
+ `;
445
+ try {
446
+ import_fs4.default.appendFileSync(DEBUG_LOG_PATH, entry, { mode: 384 });
447
+ } catch {
448
+ }
449
+ }
450
+
451
+ // src/config/registry.ts
452
+ var USER_REGISTRY_PATH = import_path4.default.join(import_os2.default.homedir(), ".workscribe", "ai-tools.json");
453
+ function loadAIToolRegistry() {
454
+ let userEntries = [];
455
+ try {
456
+ if (import_fs5.default.existsSync(USER_REGISTRY_PATH)) {
457
+ const raw = import_fs5.default.readFileSync(USER_REGISTRY_PATH, "utf-8");
458
+ const parsed = JSON.parse(raw);
459
+ if (Array.isArray(parsed)) {
460
+ userEntries = parsed.filter((entry) => typeof entry.tool === "string" && typeof entry.pattern === "string").map((entry) => ({
461
+ tool: entry.tool,
462
+ displayName: entry.displayName ?? entry.tool,
463
+ pattern: entry.pattern,
464
+ hook: entry.hook ?? null,
465
+ contextSource: entry.contextSource ?? null
466
+ }));
467
+ }
468
+ }
469
+ } catch {
470
+ debugLog("registry", "Failed to load user AI tool registry \u2014 using built-ins only");
471
+ }
472
+ if (userEntries.length === 0) return ai_tools_default;
473
+ const userToolSlugs = new Set(userEntries.map((entry) => entry.tool));
474
+ const filteredBuiltins = ai_tools_default.filter((entry) => !userToolSlugs.has(entry.tool));
475
+ return [...filteredBuiltins, ...userEntries];
476
+ }
477
+
408
478
  // src/normalize/patterns.ts
409
479
  function makeRule(pattern, category, extract = () => ({})) {
410
480
  return {
@@ -418,7 +488,7 @@ function makeRule(pattern, category, extract = () => ({})) {
418
488
  function extractPackages(cmd, afterKeyword) {
419
489
  const idx = cmd.indexOf(afterKeyword);
420
490
  if (idx === -1) return [];
421
- return cmd.slice(idx + afterKeyword.length).trim().split(/\s+/).filter((p) => p && !p.startsWith("-"));
491
+ return cmd.slice(idx + afterKeyword.length).trim().split(/\s+/).filter((part) => part && !part.startsWith("-"));
422
492
  }
423
493
  var RULES = [
424
494
  // ─── Git operations ───────────────────────────────────────────────────────
@@ -436,7 +506,7 @@ var RULES = [
436
506
  }),
437
507
  makeRule(/^git\s+(checkout|switch)\b/, "branch_switch", (_m, cmd) => {
438
508
  const parts = cmd.split(/\s+/);
439
- const branch = parts.find((p) => !p.startsWith("-") && p !== "git" && p !== parts[1]);
509
+ const branch = parts.find((part) => !part.startsWith("-") && part !== "git" && part !== parts[1]);
440
510
  return { branch: branch ?? null };
441
511
  }),
442
512
  makeRule(/^git\s+(merge|rebase)\b/, "code_merge", (_m, cmd) => {
@@ -502,7 +572,7 @@ var RULES = [
502
572
  makeRule(/^yarn\s+test\b/, "test_run", () => ({ tool: "yarn" })),
503
573
  makeRule(/^pnpm\s+test\b/, "test_run", () => ({ tool: "pnpm" })),
504
574
  makeRule(/^bun\s+test\b/, "test_run", () => ({ tool: "bun" })),
505
- makeRule(/^(jest|vitest)\b/, "test_run", (m) => ({ tool: m[1] })),
575
+ makeRule(/^(jest|vitest)\b/, "test_run", (match) => ({ tool: match[1] })),
506
576
  makeRule(/^pytest\b/, "test_run", () => ({ tool: "pytest" })),
507
577
  makeRule(/^go\s+test\b/, "test_run", () => ({ tool: "go" })),
508
578
  makeRule(/^cargo\s+test\b/, "test_run", () => ({ tool: "cargo" })),
@@ -510,19 +580,19 @@ var RULES = [
510
580
  makeRule(/^(php\s+)?phpunit\b/, "test_run", () => ({ tool: "phpunit" })),
511
581
  // ─── Build ────────────────────────────────────────────────────────────────
512
582
  makeRule(/^npm\s+run\s+build\b/, "build", () => ({ tool: "npm" })),
513
- makeRule(/^(yarn|pnpm)\s+build\b/, "build", (m) => ({ tool: m[1] })),
583
+ makeRule(/^(yarn|pnpm)\s+build\b/, "build", (match) => ({ tool: match[1] })),
514
584
  makeRule(/^bun\s+run\s+build\b/, "build", () => ({ tool: "bun" })),
515
- makeRule(/^(make|gmake)\b/, "build", (m) => ({ tool: m[1] })),
585
+ makeRule(/^(make|gmake)\b/, "build", (match) => ({ tool: match[1] })),
516
586
  makeRule(/^cargo\s+build\b/, "build", () => ({ tool: "cargo" })),
517
587
  makeRule(/^go\s+build\b/, "build", () => ({ tool: "go" })),
518
588
  makeRule(/^docker\s+build\b/, "build", () => ({ tool: "docker" })),
519
- makeRule(/^(mvn|gradle)\b/, "build", (m) => ({ tool: m[1] })),
589
+ makeRule(/^(mvn|gradle)\b/, "build", (match) => ({ tool: match[1] })),
520
590
  // ─── Dev server ───────────────────────────────────────────────────────────
521
591
  makeRule(/^npm\s+run\s+(dev|start|serve)\b/, "server_run", (_m, cmd) => ({
522
592
  tool: "npm",
523
593
  script: cmd.split(/\s+/)[2]
524
594
  })),
525
- makeRule(/^(yarn|pnpm)\s+(dev|start)\b/, "server_run", (m) => ({ tool: m[1], script: m[2] })),
595
+ makeRule(/^(yarn|pnpm)\s+(dev|start)\b/, "server_run", (match) => ({ tool: match[1], script: match[2] })),
526
596
  makeRule(/^bun\s+run\s+(dev|start|serve)\b/, "server_run", (_m, cmd) => ({
527
597
  tool: "bun",
528
598
  script: cmd.split(/\s+/)[2]
@@ -564,7 +634,7 @@ var RULES = [
564
634
  tool: cmd.split(/\s+/)[1]
565
635
  })),
566
636
  // ─── Scripts ──────────────────────────────────────────────────────────────
567
- makeRule(/^(bash|sh|zsh|fish)\s+\S+/, "script_run", (m) => ({ shell: m[1] })),
637
+ makeRule(/^(bash|sh|zsh|fish)\s+\S+/, "script_run", (match) => ({ shell: match[1] })),
568
638
  makeRule(/^node\s+\S+\.(?:js|mjs|cjs)\b/, "script_run", () => ({ runtime: "node" })),
569
639
  makeRule(/^python3?\s+\S+\.py\b/, "script_run", () => ({ runtime: "python" })),
570
640
  // ─── Environment ──────────────────────────────────────────────────────────
@@ -583,20 +653,27 @@ var RULES = [
583
653
  script: cmd.split(/\s+/)[2]
584
654
  })),
585
655
  // ─── Code search ─────────────────────────────────────────────────────────
586
- makeRule(/^(grep|egrep|fgrep)\b/, "code_search", (m) => ({ tool: m[1] })),
587
- makeRule(/^(rg|ag|ack|ripgrep)\b/, "code_search", (m) => ({ tool: m[1] })),
656
+ makeRule(/^(grep|egrep|fgrep)\b/, "code_search", (match) => ({ tool: match[1] })),
657
+ makeRule(/^(rg|ag|ack|ripgrep)\b/, "code_search", (match) => ({ tool: match[1] })),
588
658
  makeRule(/^find\s/, "code_search", () => ({ tool: "find" })),
589
659
  // ─── Code read ────────────────────────────────────────────────────────────
590
- makeRule(/^(cat|bat|less|more)\s+\S+/, "code_read", (m) => ({ tool: m[1] })),
591
- makeRule(/^(head|tail)\b/, "code_read", (m) => ({ tool: m[1] })),
660
+ makeRule(/^(cat|bat|less|more)\s+\S+/, "code_read", (match) => ({ tool: match[1] })),
661
+ makeRule(/^(head|tail)\b/, "code_read", (match) => ({ tool: match[1] })),
592
662
  // ─── File operations ──────────────────────────────────────────────────────
593
- makeRule(/^(cp|rsync)\b/, "file_ops", (m) => ({ tool: m[1] })),
663
+ makeRule(/^(cp|rsync)\b/, "file_ops", (match) => ({ tool: match[1] })),
594
664
  makeRule(/^(mv)\b/, "file_ops", () => ({ tool: "mv" })),
595
- makeRule(/^(rm|rmdir)\b/, "file_ops", (m) => ({ tool: m[1] })),
596
- makeRule(/^(mkdir|touch|ln)\b/, "file_ops", (m) => ({ tool: m[1] })),
597
- makeRule(/^(chmod|chown|chgrp)\b/, "file_ops", (m) => ({ tool: m[1] })),
665
+ makeRule(/^(rm|rmdir)\b/, "file_ops", (match) => ({ tool: match[1] })),
666
+ makeRule(/^(mkdir|touch|ln)\b/, "file_ops", (match) => ({ tool: match[1] })),
667
+ makeRule(/^(chmod|chown|chgrp)\b/, "file_ops", (match) => ({ tool: match[1] })),
598
668
  // ─── AI terminal tools (registry-driven) ─────────────────────────────────
599
- ...ai_tools_default.map((entry) => makeRule(new RegExp(entry.pattern), "ai_session", () => ({ tool: entry.tool })))
669
+ ...loadAIToolRegistry().flatMap((entry) => {
670
+ try {
671
+ return [makeRule(new RegExp(entry.pattern), "ai_session", () => ({ tool: entry.tool }))];
672
+ } catch {
673
+ debugLog("registry", `Skipping tool "${entry.tool}" \u2014 invalid pattern: ${entry.pattern}`);
674
+ return [];
675
+ }
676
+ })
600
677
  ];
601
678
  function normalizeCommand(command) {
602
679
  let trimmed = command.trim().replace(/\s+/g, " ");
@@ -609,7 +686,7 @@ function normalizeCommand(command) {
609
686
  }
610
687
 
611
688
  // src/config/index.ts
612
- var import_fs3 = __toESM(require("fs"));
689
+ var import_fs6 = __toESM(require("fs"));
613
690
  var DEFAULTS = {
614
691
  ai: {
615
692
  provider: "",
@@ -621,7 +698,9 @@ var DEFAULTS = {
621
698
  idleTimeout: 30
622
699
  },
623
700
  capture: {
624
- ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"]
701
+ ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"],
702
+ aiTools: true,
703
+ includeAiderPrompt: true
625
704
  },
626
705
  redact: {
627
706
  extra: []
@@ -631,9 +710,9 @@ var DEFAULTS = {
631
710
  }
632
711
  };
633
712
  function readConfig() {
634
- if (!import_fs3.default.existsSync(CONFIG_PATH)) return { ...DEFAULTS };
713
+ if (!import_fs6.default.existsSync(CONFIG_PATH)) return { ...DEFAULTS };
635
714
  try {
636
- const raw = import_fs3.default.readFileSync(CONFIG_PATH, "utf-8");
715
+ const raw = import_fs6.default.readFileSync(CONFIG_PATH, "utf-8");
637
716
  const parsed = JSON.parse(raw);
638
717
  return deepMerge(DEFAULTS, parsed);
639
718
  } catch {
@@ -641,9 +720,9 @@ function readConfig() {
641
720
  }
642
721
  }
643
722
  function writeConfig(config) {
644
- import_fs3.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
723
+ import_fs6.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
645
724
  try {
646
- import_fs3.default.chmodSync(CONFIG_PATH, 384);
725
+ import_fs6.default.chmodSync(CONFIG_PATH, 384);
647
726
  } catch {
648
727
  }
649
728
  }
@@ -710,7 +789,7 @@ function isDuplicate(command, cwd, lastEvent, dedupWindowMs = 5e3, now = Date.no
710
789
 
711
790
  // src/capture/index.ts
712
791
  function captureCommand(opts) {
713
- if (import_fs4.default.existsSync(PAUSE_FLAG_PATH)) return;
792
+ if (import_fs7.default.existsSync(PAUSE_FLAG_PATH)) return;
714
793
  const config = readConfig();
715
794
  const { command, cwd, exitCode, durationMs } = opts;
716
795
  if (shouldFilter(command, config.capture.ignore)) return;
@@ -720,10 +799,25 @@ function captureCommand(opts) {
720
799
  const nameOverride = config.projects?.[cwd] ?? null;
721
800
  const { repoName, branch } = getGitContext(cwd, nameOverride);
722
801
  const { category, metadata } = normalizeCommand(safeCommand);
802
+ if (category === "ai_session" && config.capture.aiTools === false) return;
803
+ if (category === "other") {
804
+ const firstToken = safeCommand.trim().split(/\s+/)[0];
805
+ if (firstToken) metadata.tool = firstToken;
806
+ metadata.hint = safeCommand.trim().slice(0, 120);
807
+ }
723
808
  if (opts.source) metadata.source = opts.source;
724
- if (category === "ai_session" && opts.gitBefore) {
725
- const commits = getCommitsSince(opts.gitBefore, cwd);
726
- if (commits.length > 0) metadata.commits = commits;
809
+ if (category === "ai_session") {
810
+ if (opts.gitBefore) {
811
+ const commits = getCommitsSince(opts.gitBefore, cwd);
812
+ if (commits.length > 0) metadata.commits = commits;
813
+ }
814
+ if (config.capture.includeAiderPrompt !== false) {
815
+ const toolEntry = loadAIToolRegistry().find((entry) => entry.tool === metadata.tool);
816
+ if (toolEntry?.contextSource === "history-file") {
817
+ const prompt = readLastAiderPrompt(cwd);
818
+ if (prompt) metadata.prompt = prompt;
819
+ }
820
+ }
727
821
  }
728
822
  insertEvent({
729
823
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -738,27 +832,6 @@ function captureCommand(opts) {
738
832
  });
739
833
  }
740
834
 
741
- // src/utils/logger.ts
742
- var import_chalk = __toESM(require("chalk"));
743
- var import_fs5 = __toESM(require("fs"));
744
- var isDebug = process.env.WORKSCRIBE_DEBUG === "1";
745
- var log = {
746
- info: (msg) => console.log(import_chalk.default.cyan("\u2192"), msg),
747
- success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
748
- warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
749
- error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
750
- plain: (msg) => console.log(msg)
751
- };
752
- function debugLog(context, error) {
753
- if (!isDebug) return;
754
- const entry = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${context}] ${error instanceof Error ? error.stack : String(error)}
755
- `;
756
- try {
757
- import_fs5.default.appendFileSync(DEBUG_LOG_PATH, entry, { mode: 384 });
758
- } catch {
759
- }
760
- }
761
-
762
835
  // src/cli/capture.ts
763
836
  function registerCapture(program2) {
764
837
  program2.command("_capture").description("Internal: capture a shell command event (called by shell hook)").option("--cmd <command>", "command that was executed").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").action((opts) => {
@@ -796,6 +869,7 @@ function registerHook(program2) {
796
869
  });
797
870
  process.stdin.on("end", () => {
798
871
  try {
872
+ if (readConfig().capture.aiTools === false) return;
799
873
  const parsed = parseHookPayload(raw);
800
874
  if (!parsed) return;
801
875
  captureCommand({
@@ -813,21 +887,21 @@ function registerHook(program2) {
813
887
  }
814
888
 
815
889
  // src/cli/init.ts
816
- var import_fs8 = __toESM(require("fs"));
817
- var import_path5 = __toESM(require("path"));
818
- var import_os3 = __toESM(require("os"));
890
+ var import_fs10 = __toESM(require("fs"));
891
+ var import_path7 = __toESM(require("path"));
892
+ var import_os4 = __toESM(require("os"));
819
893
  var import_prompts = __toESM(require("prompts"));
820
894
  var import_chalk2 = __toESM(require("chalk"));
821
895
 
822
896
  // src/capture/shell-hook.ts
823
- var import_fs6 = __toESM(require("fs"));
824
- var import_path3 = __toESM(require("path"));
897
+ var import_fs8 = __toESM(require("fs"));
898
+ var import_path5 = __toESM(require("path"));
825
899
  var HOOK_START = "# workscribe:hook:start";
826
900
  var HOOK_END = "# workscribe:hook:end";
827
901
  var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
828
902
  function getInstalledHookBlock(rcFile) {
829
- if (!import_fs6.default.existsSync(rcFile)) return null;
830
- const content = import_fs6.default.readFileSync(rcFile, "utf-8");
903
+ if (!import_fs8.default.existsSync(rcFile)) return null;
904
+ const content = import_fs8.default.readFileSync(rcFile, "utf-8");
831
905
  const startIdx = content.indexOf(HOOK_START);
832
906
  const endIdx = content.indexOf(HOOK_END);
833
907
  if (startIdx === -1 || endIdx === -1) return null;
@@ -843,39 +917,39 @@ function isHookOutdated(rcFile, hookContent) {
843
917
  }
844
918
  function installHook(rcFile, hookContent) {
845
919
  const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
846
- import_fs6.default.copyFileSync(rcFile, backup);
847
- log.info(`Backed up ${rcFile} \u2192 ${import_path3.default.basename(backup)}`);
848
- import_fs6.default.appendFileSync(rcFile, `
920
+ import_fs8.default.copyFileSync(rcFile, backup);
921
+ log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
922
+ import_fs8.default.appendFileSync(rcFile, `
849
923
  ${hookContent}
850
924
  `);
851
925
  }
852
926
  function updateHook(rcFile, hookContent) {
853
927
  const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
854
- import_fs6.default.copyFileSync(rcFile, backup);
855
- log.info(`Backed up ${rcFile} \u2192 ${import_path3.default.basename(backup)}`);
856
- const content = import_fs6.default.readFileSync(rcFile, "utf-8");
928
+ import_fs8.default.copyFileSync(rcFile, backup);
929
+ log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
930
+ const content = import_fs8.default.readFileSync(rcFile, "utf-8");
857
931
  const startIdx = content.indexOf(HOOK_START);
858
932
  const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
859
- import_fs6.default.writeFileSync(rcFile, content.slice(0, startIdx) + hookContent + content.slice(endIdx));
933
+ import_fs8.default.writeFileSync(rcFile, content.slice(0, startIdx) + hookContent + content.slice(endIdx));
860
934
  }
861
935
  function removeHook(rcFile) {
862
936
  const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
863
- import_fs6.default.copyFileSync(rcFile, backup);
864
- log.info(`Backed up ${rcFile} \u2192 ${import_path3.default.basename(backup)}`);
865
- const content = import_fs6.default.readFileSync(rcFile, "utf-8");
937
+ import_fs8.default.copyFileSync(rcFile, backup);
938
+ log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
939
+ const content = import_fs8.default.readFileSync(rcFile, "utf-8");
866
940
  const startIdx = content.indexOf(HOOK_START);
867
941
  const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
868
942
  const preambleIdx = content.lastIndexOf(HOOK_PREAMBLE, startIdx);
869
943
  const blockStart = preambleIdx !== -1 ? preambleIdx : startIdx;
870
944
  const before = content.slice(0, blockStart).replace(/\n+$/, "\n");
871
945
  const after = content.slice(endIdx).replace(/^\n/, "");
872
- import_fs6.default.writeFileSync(rcFile, before + after);
946
+ import_fs8.default.writeFileSync(rcFile, before + after);
873
947
  }
874
948
 
875
949
  // src/capture/hooks/claude-code.ts
876
- var import_fs7 = __toESM(require("fs"));
877
- var import_path4 = __toESM(require("path"));
878
- var import_os2 = __toESM(require("os"));
950
+ var import_fs9 = __toESM(require("fs"));
951
+ var import_path6 = __toESM(require("path"));
952
+ var import_os3 = __toESM(require("os"));
879
953
 
880
954
  // src/capture/hooks/utils.ts
881
955
  var import_child_process3 = require("child_process");
@@ -889,11 +963,11 @@ function isCommandInstalled(cmd) {
889
963
  }
890
964
 
891
965
  // src/capture/hooks/claude-code.ts
892
- var CLAUDE_SETTINGS_PATH = import_path4.default.join(import_os2.default.homedir(), ".claude", "settings.json");
966
+ var CLAUDE_SETTINGS_PATH = import_path6.default.join(import_os3.default.homedir(), ".claude", "settings.json");
893
967
  function readSettings() {
894
- if (!import_fs7.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
968
+ if (!import_fs9.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
895
969
  try {
896
- return JSON.parse(import_fs7.default.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
970
+ return JSON.parse(import_fs9.default.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
897
971
  } catch {
898
972
  return {};
899
973
  }
@@ -908,8 +982,8 @@ var claudeCodeIntegration = {
908
982
  const postToolUse = settings.hooks?.PostToolUse ?? [];
909
983
  return postToolUse.some((entry) => {
910
984
  const hooks = entry.hooks ?? [];
911
- return hooks.some((h) => {
912
- const hook = h;
985
+ return hooks.some((hookItem) => {
986
+ const hook = hookItem;
913
987
  return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
914
988
  });
915
989
  });
@@ -928,8 +1002,8 @@ var claudeCodeIntegration = {
928
1002
  }
929
1003
  ]
930
1004
  };
931
- import_fs7.default.mkdirSync(import_path4.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
932
- import_fs7.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
1005
+ import_fs9.default.mkdirSync(import_path6.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
1006
+ import_fs9.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
933
1007
  }
934
1008
  };
935
1009
 
@@ -941,19 +1015,19 @@ var AI_HOOK_INTEGRATIONS = {
941
1015
  // src/cli/init.ts
942
1016
  function detectShell() {
943
1017
  const shell = process.env.SHELL ?? "";
944
- if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path5.default.join(import_os3.default.homedir(), ".zshrc") };
945
- if (shell.includes("bash")) return { name: "bash", rcFile: import_path5.default.join(import_os3.default.homedir(), ".bashrc") };
1018
+ if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path7.default.join(import_os4.default.homedir(), ".zshrc") };
1019
+ if (shell.includes("bash")) return { name: "bash", rcFile: import_path7.default.join(import_os4.default.homedir(), ".bashrc") };
946
1020
  return null;
947
1021
  }
948
1022
  function registerInit(program2) {
949
1023
  program2.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
950
1024
  log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
951
- if (!import_fs8.default.existsSync(WORKSCRIBE_DIR)) {
952
- import_fs8.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true, mode: 448 });
1025
+ if (!import_fs10.default.existsSync(WORKSCRIBE_DIR)) {
1026
+ import_fs10.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true, mode: 448 });
953
1027
  log.success(`Created ${WORKSCRIBE_DIR}`);
954
1028
  } else {
955
1029
  try {
956
- import_fs8.default.chmodSync(WORKSCRIBE_DIR, 448);
1030
+ import_fs10.default.chmodSync(WORKSCRIBE_DIR, 448);
957
1031
  } catch {
958
1032
  }
959
1033
  }
@@ -964,12 +1038,12 @@ function registerInit(program2) {
964
1038
  log.warn("Could not detect shell. Supported: zsh, bash");
965
1039
  log.plain(`Manually add the hook from: ${import_chalk2.default.cyan("shell/hook.zsh")} or ${import_chalk2.default.cyan("shell/hook.bash")}`);
966
1040
  } else {
967
- const hookFile = import_path5.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
968
- if (!import_fs8.default.existsSync(hookFile)) {
1041
+ const hookFile = import_path7.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
1042
+ if (!import_fs10.default.existsSync(hookFile)) {
969
1043
  log.warn(`Hook template not found at ${hookFile}`);
970
1044
  } else {
971
- const hookContent = import_fs8.default.readFileSync(hookFile, "utf-8");
972
- if (!import_fs8.default.existsSync(shell.rcFile)) import_fs8.default.writeFileSync(shell.rcFile, "");
1045
+ const hookContent = import_fs10.default.readFileSync(hookFile, "utf-8");
1046
+ if (!import_fs10.default.existsSync(shell.rcFile)) import_fs10.default.writeFileSync(shell.rcFile, "");
973
1047
  if (!isHookInstalled(shell.rcFile)) {
974
1048
  installHook(shell.rcFile, hookContent);
975
1049
  log.success(`Shell hook installed in ${shell.rcFile}`);
@@ -991,21 +1065,29 @@ function registerInit(program2) {
991
1065
  }
992
1066
  }
993
1067
  }
994
- for (const tool of ai_tools_default.filter((t) => t.hook !== null)) {
995
- const integration = AI_HOOK_INTEGRATIONS[tool.hook];
996
- if (!integration?.isInstalled()) continue;
997
- if (integration.isHookInstalled()) {
998
- log.info(`${integration.displayName} session hook already installed`);
999
- } else {
1000
- const { install } = await (0, import_prompts.default)({
1001
- type: "confirm",
1002
- name: "install",
1003
- message: `${integration.displayName} detected. Install session capture hook?`,
1004
- initial: true
1005
- });
1006
- if (install) {
1007
- integration.install();
1008
- log.success(`${integration.displayName} session hook installed`);
1068
+ const installedTools = loadAIToolRegistry().filter(
1069
+ (entry) => entry.hook !== null && AI_HOOK_INTEGRATIONS[entry.hook]?.isInstalled()
1070
+ );
1071
+ if (installedTools.length === 0) {
1072
+ log.info("No AI coding tools detected \u2014 re-run workscribe init after installing one");
1073
+ } else {
1074
+ for (const tool of installedTools) {
1075
+ const integration = AI_HOOK_INTEGRATIONS[tool.hook];
1076
+ if (integration.isHookInstalled()) {
1077
+ log.info(`${integration.displayName} session hook already installed`);
1078
+ } else {
1079
+ const { install } = await (0, import_prompts.default)({
1080
+ type: "confirm",
1081
+ name: "install",
1082
+ message: `${integration.displayName} detected. Install session capture hook?`,
1083
+ initial: false
1084
+ });
1085
+ if (install) {
1086
+ integration.install();
1087
+ log.success(`${integration.displayName} session hook installed`);
1088
+ } else {
1089
+ log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
1090
+ }
1009
1091
  }
1010
1092
  }
1011
1093
  }
@@ -1057,7 +1139,7 @@ function registerInit(program2) {
1057
1139
  log.info("Skipped \u2014 run later: workscribe config set ai.provider <provider>");
1058
1140
  }
1059
1141
  }
1060
- if (!import_fs8.default.existsSync(CONFIG_PATH)) {
1142
+ if (!import_fs10.default.existsSync(CONFIG_PATH)) {
1061
1143
  writeConfig(config);
1062
1144
  log.success("Config file created");
1063
1145
  }
@@ -1076,7 +1158,7 @@ function registerInit(program2) {
1076
1158
  }
1077
1159
 
1078
1160
  // src/cli/status.ts
1079
- var import_fs9 = __toESM(require("fs"));
1161
+ var import_fs11 = __toESM(require("fs"));
1080
1162
  var import_chalk3 = __toESM(require("chalk"));
1081
1163
 
1082
1164
  // src/utils/format.ts
@@ -1134,7 +1216,7 @@ function registerStatus(program2) {
1134
1216
  const total = getEventCount();
1135
1217
  const todayEvents = getEventsForDate(todayDate());
1136
1218
  const last = getLastEvent();
1137
- const isPaused = import_fs9.default.existsSync(PAUSE_FLAG_PATH);
1219
+ const isPaused = import_fs11.default.existsSync(PAUSE_FLAG_PATH);
1138
1220
  log.plain("");
1139
1221
  log.plain(import_chalk3.default.bold(" Workscribe Status"));
1140
1222
  log.plain("");
@@ -1182,7 +1264,9 @@ var CATEGORY_COLORS = {
1182
1264
  file_ops: import_chalk4.default.yellow,
1183
1265
  other: import_chalk4.default.gray
1184
1266
  };
1185
- var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(ai_tools_default.map((t) => [t.tool, t.displayName]));
1267
+ var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
1268
+ loadAIToolRegistry().map((entry) => [entry.tool, entry.displayName])
1269
+ );
1186
1270
  function registerEvents(program2) {
1187
1271
  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) => {
1188
1272
  let date = todayDate();
@@ -1273,18 +1357,18 @@ function getOrBuildSessions(date) {
1273
1357
  const existing = getSessionsForDate(date);
1274
1358
  const events = getEventsForDate(date);
1275
1359
  if (events.length === 0) return [];
1276
- const cachedEventCount = existing.reduce((sum, s) => sum + s.event_count, 0);
1360
+ const cachedEventCount = existing.reduce((sum, sessionRow) => sum + sessionRow.event_count, 0);
1277
1361
  if (existing.length > 0 && cachedEventCount === events.length) {
1278
- return existing.map((s) => ({
1279
- id: s.id,
1280
- date: s.date,
1281
- startedAt: s.started_at,
1282
- endedAt: s.ended_at,
1283
- repoName: s.repo_name,
1284
- durationMinutes: minutesBetween(s.started_at, s.ended_at),
1285
- eventCount: s.event_count,
1286
- summary: s.summary,
1287
- events: getEventsBySessionId(s.id)
1362
+ return existing.map((sessionRow) => ({
1363
+ id: sessionRow.id,
1364
+ date: sessionRow.date,
1365
+ startedAt: sessionRow.started_at,
1366
+ endedAt: sessionRow.ended_at,
1367
+ repoName: sessionRow.repo_name,
1368
+ durationMinutes: minutesBetween(sessionRow.started_at, sessionRow.ended_at),
1369
+ eventCount: sessionRow.event_count,
1370
+ summary: sessionRow.summary,
1371
+ events: getEventsBySessionId(sessionRow.id)
1288
1372
  }));
1289
1373
  }
1290
1374
  const savedSummaries = /* @__PURE__ */ new Map();
@@ -1370,9 +1454,9 @@ function registerSessions(program2) {
1370
1454
 
1371
1455
  // src/cli/summary.ts
1372
1456
  var import_chalk6 = __toESM(require("chalk"));
1373
- var import_fs10 = __toESM(require("fs"));
1374
- var import_os4 = __toESM(require("os"));
1375
- var import_path6 = __toESM(require("path"));
1457
+ var import_fs12 = __toESM(require("fs"));
1458
+ var import_os5 = __toESM(require("os"));
1459
+ var import_path8 = __toESM(require("path"));
1376
1460
 
1377
1461
  // src/ai/prompt.ts
1378
1462
  var SYSTEM_PROMPT = `You write short developer work summaries. Here is an example:
@@ -1413,9 +1497,14 @@ function buildUserMessage(payload) {
1413
1497
  const counts = /* @__PURE__ */ new Map();
1414
1498
  for (const event of session.events) {
1415
1499
  if (EXCLUDED.has(event.category)) continue;
1416
- const meta = Object.keys(event.metadata).length > 0 ? ` ${JSON.stringify(event.metadata)}` : "";
1417
1500
  const failed = event.exitCode !== null && event.exitCode !== 0 ? " [failed]" : "";
1418
- const key = `${event.category}${meta}${failed}`;
1501
+ let key;
1502
+ if (event.hint) {
1503
+ key = `other [${event.hint}]${failed}`;
1504
+ } else {
1505
+ const meta = Object.keys(event.metadata).length > 0 ? ` ${JSON.stringify(event.metadata)}` : "";
1506
+ key = `${event.category}${meta}${failed}`;
1507
+ }
1419
1508
  counts.set(key, (counts.get(key) ?? 0) + 1);
1420
1509
  }
1421
1510
  for (const [key, count] of counts) {
@@ -1470,9 +1559,14 @@ function buildWeeklyMessage(payload) {
1470
1559
  const counts = /* @__PURE__ */ new Map();
1471
1560
  for (const e of s.events) {
1472
1561
  if (EXCLUDED.has(e.category)) continue;
1473
- const meta = Object.keys(e.metadata).length > 0 ? ` ${JSON.stringify(e.metadata)}` : "";
1474
1562
  const failed = e.exitCode !== null && e.exitCode !== 0 ? " [failed]" : "";
1475
- const key = `${e.category}${meta}${failed}`;
1563
+ let key;
1564
+ if (e.hint) {
1565
+ key = `other [${e.hint}]${failed}`;
1566
+ } else {
1567
+ const meta = Object.keys(e.metadata).length > 0 ? ` ${JSON.stringify(e.metadata)}` : "";
1568
+ key = `${e.category}${meta}${failed}`;
1569
+ }
1476
1570
  counts.set(key, (counts.get(key) ?? 0) + 1);
1477
1571
  }
1478
1572
  const eventLine = [...counts.entries()].map(([k, n]) => `${n > 1 ? `${n}x ` : ""}${k}`).join(", ");
@@ -1528,7 +1622,7 @@ var AnthropicProvider = class {
1528
1622
  throw new Error(`Anthropic API error ${response.status}: ${body}`);
1529
1623
  }
1530
1624
  const data = await response.json();
1531
- return data.content.find((c) => c.type === "text")?.text ?? "";
1625
+ return data.content.find((block) => block.type === "text")?.text ?? "";
1532
1626
  }
1533
1627
  async summarizeWeek(payload) {
1534
1628
  const response = await fetchWithTimeout("https://api.anthropic.com/v1/messages", {
@@ -1550,7 +1644,7 @@ var AnthropicProvider = class {
1550
1644
  throw new Error(`Anthropic API error ${response.status}: ${body}`);
1551
1645
  }
1552
1646
  const data = await response.json();
1553
- return data.content.find((c) => c.type === "text")?.text ?? "";
1647
+ return data.content.find((block) => block.type === "text")?.text ?? "";
1554
1648
  }
1555
1649
  };
1556
1650
 
@@ -1736,7 +1830,7 @@ function isSlackWebhookUrl(url) {
1736
1830
 
1737
1831
  // src/cli/summary.ts
1738
1832
  function parseBullets(text) {
1739
- return text.split("\n").map((l) => l.replace(/^[•\-]\s*/, "").trim()).filter(Boolean);
1833
+ return text.split("\n").map((line) => line.replace(/^[•\-]\s*/, "").trim()).filter(Boolean);
1740
1834
  }
1741
1835
  function eventRowToPayload(row) {
1742
1836
  let metadata = {};
@@ -1744,12 +1838,18 @@ function eventRowToPayload(row) {
1744
1838
  if (row.metadata) metadata = JSON.parse(row.metadata);
1745
1839
  } catch {
1746
1840
  }
1747
- return {
1841
+ const payload = {
1748
1842
  category: row.category,
1749
1843
  metadata,
1750
1844
  exitCode: row.exit_code,
1751
1845
  durationMs: row.duration_ms
1752
1846
  };
1847
+ if (row.category === "other" && typeof metadata.hint === "string") {
1848
+ payload.hint = metadata.hint;
1849
+ const { hint: _h, tool: _t, ...rest } = metadata;
1850
+ payload.metadata = rest;
1851
+ }
1852
+ return payload;
1753
1853
  }
1754
1854
  function sessionToPayload(s, date) {
1755
1855
  return {
@@ -1767,7 +1867,7 @@ async function handleWebhook(opts, config, summary) {
1767
1867
  if (webhookUrl) {
1768
1868
  try {
1769
1869
  log.info("Posting to webhook\u2026");
1770
- const username = import_os4.default.userInfo().username;
1870
+ const username = import_os5.default.userInfo().username;
1771
1871
  const body = isSlackWebhookUrl(webhookUrl) ? formatForSlack(summary, username) : summary;
1772
1872
  await postToWebhook(webhookUrl, body);
1773
1873
  log.success("Posted.");
@@ -1796,7 +1896,7 @@ function registerSummary(program2) {
1796
1896
  for (const day of days) {
1797
1897
  const sessions2 = getOrBuildSessions(day);
1798
1898
  if (sessions2.length > 0) {
1799
- weeklyDays.push({ date: day, sessions: sessions2.map((s) => sessionToPayload(s, day)) });
1899
+ weeklyDays.push({ date: day, sessions: sessions2.map((session) => sessionToPayload(session, day)) });
1800
1900
  }
1801
1901
  }
1802
1902
  if (weeklyDays.length === 0) {
@@ -1804,9 +1904,9 @@ function registerSummary(program2) {
1804
1904
  return;
1805
1905
  }
1806
1906
  const cacheKey = isoWeekKey(today);
1807
- const cacheFile = import_path6.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
1808
- if (!opts.fresh && import_fs10.default.existsSync(cacheFile)) {
1809
- const cached = import_fs10.default.readFileSync(cacheFile, "utf-8");
1907
+ const cacheFile = import_path8.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
1908
+ if (!opts.fresh && import_fs12.default.existsSync(cacheFile)) {
1909
+ const cached = import_fs12.default.readFileSync(cacheFile, "utf-8");
1810
1910
  log.plain("");
1811
1911
  log.plain(cached);
1812
1912
  log.plain("");
@@ -1818,12 +1918,12 @@ function registerSummary(program2) {
1818
1918
  log.plain("");
1819
1919
  log.info(`Generating weekly summary via ${import_chalk6.default.cyan(config.ai.provider)}\u2026`);
1820
1920
  try {
1821
- const summary = provider.summarizeWeek ? await provider.summarizeWeek(payload) : await provider.summarize({ date: today, sessions: weeklyDays.flatMap((d) => d.sessions) });
1921
+ const summary = provider.summarizeWeek ? await provider.summarizeWeek(payload) : await provider.summarize({ date: today, sessions: weeklyDays.flatMap((day) => day.sessions) });
1822
1922
  log.plain("");
1823
1923
  log.plain(summary);
1824
1924
  log.plain("");
1825
- import_fs10.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
1826
- import_fs10.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
1925
+ import_fs12.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
1926
+ import_fs12.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
1827
1927
  await handleWebhook(opts, config, summary);
1828
1928
  } catch (e) {
1829
1929
  log.error(`Weekly summary failed [${config.ai.provider}]: ${e.message}`);
@@ -1847,16 +1947,16 @@ function registerSummary(program2) {
1847
1947
  }
1848
1948
  const jsonMode = config.summary?.format === "json";
1849
1949
  if (!opts.fresh) {
1850
- const allCached = sessions.every((s) => s.summary);
1950
+ const allCached = sessions.every((session) => session.summary);
1851
1951
  if (allCached) {
1852
- const fullSummary2 = sessions.map((s) => s.summary).join("\n\n");
1952
+ const fullSummary2 = sessions.map((session) => session.summary).join("\n\n");
1853
1953
  log.plain("");
1854
1954
  if (jsonMode) {
1855
- const jsonOut = sessions.map((s) => ({
1856
- project: s.repoName,
1857
- branch: s.events[0]?.branch ?? null,
1858
- durationMinutes: s.durationMinutes,
1859
- bullets: parseBullets(s.summary.replace(/^## .+\n/, ""))
1955
+ const jsonOut = sessions.map((session) => ({
1956
+ project: session.repoName,
1957
+ branch: session.events[0]?.branch ?? null,
1958
+ durationMinutes: session.durationMinutes,
1959
+ bullets: parseBullets(session.summary.replace(/^## .+\n/, ""))
1860
1960
  }));
1861
1961
  log.plain(JSON.stringify({ date, sessions: jsonOut }, null, 2));
1862
1962
  } else {
@@ -1910,15 +2010,26 @@ ${bullets.trim()}`;
1910
2010
  }
1911
2011
 
1912
2012
  // src/cli/export.ts
1913
- var import_fs11 = __toESM(require("fs"));
1914
- var import_path7 = __toESM(require("path"));
2013
+ var import_fs13 = __toESM(require("fs"));
2014
+ var import_path9 = __toESM(require("path"));
1915
2015
  function eventRowToPayload2(row) {
1916
2016
  let metadata = {};
1917
2017
  try {
1918
2018
  if (row.metadata) metadata = JSON.parse(row.metadata);
1919
2019
  } catch {
1920
2020
  }
1921
- return { category: row.category, metadata, exitCode: row.exit_code, durationMs: row.duration_ms };
2021
+ const payload = {
2022
+ category: row.category,
2023
+ metadata,
2024
+ exitCode: row.exit_code,
2025
+ durationMs: row.duration_ms
2026
+ };
2027
+ if (row.category === "other" && typeof metadata.hint === "string") {
2028
+ payload.hint = metadata.hint;
2029
+ const { hint: _h, tool: _t, ...rest } = metadata;
2030
+ payload.metadata = rest;
2031
+ }
2032
+ return payload;
1922
2033
  }
1923
2034
  function registerExport(program2) {
1924
2035
  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) => {
@@ -1944,21 +2055,21 @@ function registerExport(program2) {
1944
2055
  log.error(e.message);
1945
2056
  process.exit(1);
1946
2057
  }
1947
- const sessionPayloads = sessions.map((s) => ({
2058
+ const sessionPayloads = sessions.map((session) => ({
1948
2059
  date,
1949
- project: s.repoName,
1950
- branch: s.events[0]?.branch ?? null,
1951
- startTime: formatDate(s.startedAt),
1952
- endTime: formatDate(s.endedAt),
1953
- durationMinutes: s.durationMinutes,
1954
- events: s.events.map(eventRowToPayload2)
2060
+ project: session.repoName,
2061
+ branch: session.events[0]?.branch ?? null,
2062
+ startTime: formatDate(session.startedAt),
2063
+ endTime: formatDate(session.endedAt),
2064
+ durationMinutes: session.durationMinutes,
2065
+ events: session.events.map(eventRowToPayload2)
1955
2066
  }));
1956
2067
  const payload = { date, sessions: sessionPayloads };
1957
2068
  log.info(`Generating summary via ${config.ai.provider}\u2026`);
1958
2069
  const summary = await provider.summarize(payload);
1959
- const outputPath = opts.output ?? import_path7.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
1960
- if (!import_fs11.default.existsSync(EXPORTS_DIR)) import_fs11.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
1961
- import_fs11.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
2070
+ const outputPath = opts.output ?? import_path9.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
2071
+ if (!import_fs13.default.existsSync(EXPORTS_DIR)) import_fs13.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
2072
+ import_fs13.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
1962
2073
 
1963
2074
  ${summary}
1964
2075
  `, { encoding: "utf-8", mode: 384 });
@@ -2030,22 +2141,22 @@ function printConfig(obj, indent) {
2030
2141
  }
2031
2142
 
2032
2143
  // src/cli/pause.ts
2033
- var import_fs12 = __toESM(require("fs"));
2144
+ var import_fs14 = __toESM(require("fs"));
2034
2145
  function registerPause(program2) {
2035
2146
  program2.command("pause").description("Pause capture temporarily").action(() => {
2036
- if (import_fs12.default.existsSync(PAUSE_FLAG_PATH)) {
2147
+ if (import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
2037
2148
  log.info("Capture is already paused. Run workscribe resume to restart.");
2038
2149
  return;
2039
2150
  }
2040
- import_fs12.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
2151
+ import_fs14.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
2041
2152
  log.success("Capture paused. Run workscribe resume to restart.");
2042
2153
  });
2043
2154
  program2.command("resume").description("Resume capture after a pause").action(() => {
2044
- if (!import_fs12.default.existsSync(PAUSE_FLAG_PATH)) {
2155
+ if (!import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
2045
2156
  log.info("Capture is not paused.");
2046
2157
  return;
2047
2158
  }
2048
- import_fs12.default.rmSync(PAUSE_FLAG_PATH);
2159
+ import_fs14.default.rmSync(PAUSE_FLAG_PATH);
2049
2160
  log.success("Capture resumed.");
2050
2161
  });
2051
2162
  }
@@ -2130,18 +2241,18 @@ function registerClear(program2) {
2130
2241
  }
2131
2242
 
2132
2243
  // src/cli/uninstall.ts
2133
- var import_fs13 = __toESM(require("fs"));
2134
- var import_path8 = __toESM(require("path"));
2135
- var import_os5 = __toESM(require("os"));
2244
+ var import_fs15 = __toESM(require("fs"));
2245
+ var import_path10 = __toESM(require("path"));
2246
+ var import_os6 = __toESM(require("os"));
2136
2247
  var import_prompts3 = __toESM(require("prompts"));
2137
2248
  var import_chalk9 = __toESM(require("chalk"));
2138
2249
  function detectShellRcFiles() {
2139
2250
  const candidates = [
2140
- { name: "zsh", rcFile: import_path8.default.join(import_os5.default.homedir(), ".zshrc") },
2141
- { name: "bash", rcFile: import_path8.default.join(import_os5.default.homedir(), ".bashrc") },
2142
- { name: "bash", rcFile: import_path8.default.join(import_os5.default.homedir(), ".bash_profile") }
2251
+ { name: "zsh", rcFile: import_path10.default.join(import_os6.default.homedir(), ".zshrc") },
2252
+ { name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bashrc") },
2253
+ { name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bash_profile") }
2143
2254
  ];
2144
- return candidates.filter((c) => import_fs13.default.existsSync(c.rcFile) && isHookInstalled(c.rcFile));
2255
+ return candidates.filter((candidate) => import_fs15.default.existsSync(candidate.rcFile) && isHookInstalled(candidate.rcFile));
2145
2256
  }
2146
2257
  function registerUninstall(program2) {
2147
2258
  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) => {
@@ -2160,7 +2271,7 @@ function registerUninstall(program2) {
2160
2271
  }
2161
2272
  }
2162
2273
  if (opts.hooksOnly) return;
2163
- if (import_fs13.default.existsSync(WORKSCRIBE_DIR)) {
2274
+ if (import_fs15.default.existsSync(WORKSCRIBE_DIR)) {
2164
2275
  log.plain("");
2165
2276
  const { deleteData } = await (0, import_prompts3.default)({
2166
2277
  type: "confirm",
@@ -2169,7 +2280,7 @@ function registerUninstall(program2) {
2169
2280
  initial: false
2170
2281
  });
2171
2282
  if (deleteData) {
2172
- import_fs13.default.rmSync(WORKSCRIBE_DIR, { recursive: true, force: true });
2283
+ import_fs15.default.rmSync(WORKSCRIBE_DIR, { recursive: true, force: true });
2173
2284
  log.success(`Deleted ${WORKSCRIBE_DIR}`);
2174
2285
  } else {
2175
2286
  log.info(`Data kept at ${WORKSCRIBE_DIR}`);
@@ -2183,7 +2294,7 @@ function registerUninstall(program2) {
2183
2294
 
2184
2295
  // src/index.ts
2185
2296
  var program = new import_commander.Command();
2186
- program.name("workscribe").description("Automatic developer work journal").version("1.7.2");
2297
+ program.name("workscribe").description("Automatic developer work journal").version("1.9.0");
2187
2298
  registerCapture(program);
2188
2299
  registerHook(program);
2189
2300
  registerInit(program);