@workscribe/cli 1.7.2 → 1.8.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/README.md CHANGED
@@ -148,6 +148,8 @@ workscribe summary --webhook https://custom.endpoint/hook # one-
148
148
  | `ai.baseUrl` | — | Base URL for `openai-compatible` providers |
149
149
  | `session.idleTimeout` | `30` | Minutes of inactivity before a new session starts |
150
150
  | `capture.ignore` | common shell builtins | Commands to exclude from capture |
151
+ | `capture.aiTools` | `true` | Set to `false` to disable all AI session capture |
152
+ | `capture.includeAiderPrompt` | `true` | Set to `false` to skip reading Aider's history file for prompt context |
151
153
  | `redact.extra` | `[]` | Additional regex patterns to redact from captured commands |
152
154
  | `summary.format` | `markdown` | Output format: `markdown` or `json` |
153
155
  | `integrations.slackWebhook` | — | Slack incoming webhook URL |
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");
@@ -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 {
@@ -596,7 +666,14 @@ var RULES = [
596
666
  makeRule(/^(mkdir|touch|ln)\b/, "file_ops", (m) => ({ tool: m[1] })),
597
667
  makeRule(/^(chmod|chown|chgrp)\b/, "file_ops", (m) => ({ tool: m[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,20 @@ 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;
723
803
  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;
804
+ if (category === "ai_session") {
805
+ if (opts.gitBefore) {
806
+ const commits = getCommitsSince(opts.gitBefore, cwd);
807
+ if (commits.length > 0) metadata.commits = commits;
808
+ }
809
+ if (config.capture.includeAiderPrompt !== false) {
810
+ const toolEntry = loadAIToolRegistry().find((entry) => entry.tool === metadata.tool);
811
+ if (toolEntry?.contextSource === "history-file") {
812
+ const prompt = readLastAiderPrompt(cwd);
813
+ if (prompt) metadata.prompt = prompt;
814
+ }
815
+ }
727
816
  }
728
817
  insertEvent({
729
818
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -738,27 +827,6 @@ function captureCommand(opts) {
738
827
  });
739
828
  }
740
829
 
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
830
  // src/cli/capture.ts
763
831
  function registerCapture(program2) {
764
832
  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 +864,7 @@ function registerHook(program2) {
796
864
  });
797
865
  process.stdin.on("end", () => {
798
866
  try {
867
+ if (readConfig().capture.aiTools === false) return;
799
868
  const parsed = parseHookPayload(raw);
800
869
  if (!parsed) return;
801
870
  captureCommand({
@@ -813,21 +882,21 @@ function registerHook(program2) {
813
882
  }
814
883
 
815
884
  // 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"));
885
+ var import_fs10 = __toESM(require("fs"));
886
+ var import_path7 = __toESM(require("path"));
887
+ var import_os4 = __toESM(require("os"));
819
888
  var import_prompts = __toESM(require("prompts"));
820
889
  var import_chalk2 = __toESM(require("chalk"));
821
890
 
822
891
  // src/capture/shell-hook.ts
823
- var import_fs6 = __toESM(require("fs"));
824
- var import_path3 = __toESM(require("path"));
892
+ var import_fs8 = __toESM(require("fs"));
893
+ var import_path5 = __toESM(require("path"));
825
894
  var HOOK_START = "# workscribe:hook:start";
826
895
  var HOOK_END = "# workscribe:hook:end";
827
896
  var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
828
897
  function getInstalledHookBlock(rcFile) {
829
- if (!import_fs6.default.existsSync(rcFile)) return null;
830
- const content = import_fs6.default.readFileSync(rcFile, "utf-8");
898
+ if (!import_fs8.default.existsSync(rcFile)) return null;
899
+ const content = import_fs8.default.readFileSync(rcFile, "utf-8");
831
900
  const startIdx = content.indexOf(HOOK_START);
832
901
  const endIdx = content.indexOf(HOOK_END);
833
902
  if (startIdx === -1 || endIdx === -1) return null;
@@ -843,39 +912,39 @@ function isHookOutdated(rcFile, hookContent) {
843
912
  }
844
913
  function installHook(rcFile, hookContent) {
845
914
  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, `
915
+ import_fs8.default.copyFileSync(rcFile, backup);
916
+ log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
917
+ import_fs8.default.appendFileSync(rcFile, `
849
918
  ${hookContent}
850
919
  `);
851
920
  }
852
921
  function updateHook(rcFile, hookContent) {
853
922
  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");
923
+ import_fs8.default.copyFileSync(rcFile, backup);
924
+ log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
925
+ const content = import_fs8.default.readFileSync(rcFile, "utf-8");
857
926
  const startIdx = content.indexOf(HOOK_START);
858
927
  const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
859
- import_fs6.default.writeFileSync(rcFile, content.slice(0, startIdx) + hookContent + content.slice(endIdx));
928
+ import_fs8.default.writeFileSync(rcFile, content.slice(0, startIdx) + hookContent + content.slice(endIdx));
860
929
  }
861
930
  function removeHook(rcFile) {
862
931
  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");
932
+ import_fs8.default.copyFileSync(rcFile, backup);
933
+ log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
934
+ const content = import_fs8.default.readFileSync(rcFile, "utf-8");
866
935
  const startIdx = content.indexOf(HOOK_START);
867
936
  const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
868
937
  const preambleIdx = content.lastIndexOf(HOOK_PREAMBLE, startIdx);
869
938
  const blockStart = preambleIdx !== -1 ? preambleIdx : startIdx;
870
939
  const before = content.slice(0, blockStart).replace(/\n+$/, "\n");
871
940
  const after = content.slice(endIdx).replace(/^\n/, "");
872
- import_fs6.default.writeFileSync(rcFile, before + after);
941
+ import_fs8.default.writeFileSync(rcFile, before + after);
873
942
  }
874
943
 
875
944
  // 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"));
945
+ var import_fs9 = __toESM(require("fs"));
946
+ var import_path6 = __toESM(require("path"));
947
+ var import_os3 = __toESM(require("os"));
879
948
 
880
949
  // src/capture/hooks/utils.ts
881
950
  var import_child_process3 = require("child_process");
@@ -889,11 +958,11 @@ function isCommandInstalled(cmd) {
889
958
  }
890
959
 
891
960
  // src/capture/hooks/claude-code.ts
892
- var CLAUDE_SETTINGS_PATH = import_path4.default.join(import_os2.default.homedir(), ".claude", "settings.json");
961
+ var CLAUDE_SETTINGS_PATH = import_path6.default.join(import_os3.default.homedir(), ".claude", "settings.json");
893
962
  function readSettings() {
894
- if (!import_fs7.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
963
+ if (!import_fs9.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
895
964
  try {
896
- return JSON.parse(import_fs7.default.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
965
+ return JSON.parse(import_fs9.default.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
897
966
  } catch {
898
967
  return {};
899
968
  }
@@ -928,8 +997,8 @@ var claudeCodeIntegration = {
928
997
  }
929
998
  ]
930
999
  };
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));
1000
+ import_fs9.default.mkdirSync(import_path6.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
1001
+ import_fs9.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
933
1002
  }
934
1003
  };
935
1004
 
@@ -941,19 +1010,19 @@ var AI_HOOK_INTEGRATIONS = {
941
1010
  // src/cli/init.ts
942
1011
  function detectShell() {
943
1012
  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") };
1013
+ if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path7.default.join(import_os4.default.homedir(), ".zshrc") };
1014
+ if (shell.includes("bash")) return { name: "bash", rcFile: import_path7.default.join(import_os4.default.homedir(), ".bashrc") };
946
1015
  return null;
947
1016
  }
948
1017
  function registerInit(program2) {
949
1018
  program2.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
950
1019
  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 });
1020
+ if (!import_fs10.default.existsSync(WORKSCRIBE_DIR)) {
1021
+ import_fs10.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true, mode: 448 });
953
1022
  log.success(`Created ${WORKSCRIBE_DIR}`);
954
1023
  } else {
955
1024
  try {
956
- import_fs8.default.chmodSync(WORKSCRIBE_DIR, 448);
1025
+ import_fs10.default.chmodSync(WORKSCRIBE_DIR, 448);
957
1026
  } catch {
958
1027
  }
959
1028
  }
@@ -964,12 +1033,12 @@ function registerInit(program2) {
964
1033
  log.warn("Could not detect shell. Supported: zsh, bash");
965
1034
  log.plain(`Manually add the hook from: ${import_chalk2.default.cyan("shell/hook.zsh")} or ${import_chalk2.default.cyan("shell/hook.bash")}`);
966
1035
  } else {
967
- const hookFile = import_path5.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
968
- if (!import_fs8.default.existsSync(hookFile)) {
1036
+ const hookFile = import_path7.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
1037
+ if (!import_fs10.default.existsSync(hookFile)) {
969
1038
  log.warn(`Hook template not found at ${hookFile}`);
970
1039
  } 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, "");
1040
+ const hookContent = import_fs10.default.readFileSync(hookFile, "utf-8");
1041
+ if (!import_fs10.default.existsSync(shell.rcFile)) import_fs10.default.writeFileSync(shell.rcFile, "");
973
1042
  if (!isHookInstalled(shell.rcFile)) {
974
1043
  installHook(shell.rcFile, hookContent);
975
1044
  log.success(`Shell hook installed in ${shell.rcFile}`);
@@ -991,21 +1060,29 @@ function registerInit(program2) {
991
1060
  }
992
1061
  }
993
1062
  }
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`);
1063
+ const installedTools = loadAIToolRegistry().filter(
1064
+ (t) => t.hook !== null && AI_HOOK_INTEGRATIONS[t.hook]?.isInstalled()
1065
+ );
1066
+ if (installedTools.length === 0) {
1067
+ log.info("No AI coding tools detected \u2014 re-run workscribe init after installing one");
1068
+ } else {
1069
+ for (const tool of installedTools) {
1070
+ const integration = AI_HOOK_INTEGRATIONS[tool.hook];
1071
+ if (integration.isHookInstalled()) {
1072
+ log.info(`${integration.displayName} session hook already installed`);
1073
+ } else {
1074
+ const { install } = await (0, import_prompts.default)({
1075
+ type: "confirm",
1076
+ name: "install",
1077
+ message: `${integration.displayName} detected. Install session capture hook?`,
1078
+ initial: false
1079
+ });
1080
+ if (install) {
1081
+ integration.install();
1082
+ log.success(`${integration.displayName} session hook installed`);
1083
+ } else {
1084
+ log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
1085
+ }
1009
1086
  }
1010
1087
  }
1011
1088
  }
@@ -1057,7 +1134,7 @@ function registerInit(program2) {
1057
1134
  log.info("Skipped \u2014 run later: workscribe config set ai.provider <provider>");
1058
1135
  }
1059
1136
  }
1060
- if (!import_fs8.default.existsSync(CONFIG_PATH)) {
1137
+ if (!import_fs10.default.existsSync(CONFIG_PATH)) {
1061
1138
  writeConfig(config);
1062
1139
  log.success("Config file created");
1063
1140
  }
@@ -1076,7 +1153,7 @@ function registerInit(program2) {
1076
1153
  }
1077
1154
 
1078
1155
  // src/cli/status.ts
1079
- var import_fs9 = __toESM(require("fs"));
1156
+ var import_fs11 = __toESM(require("fs"));
1080
1157
  var import_chalk3 = __toESM(require("chalk"));
1081
1158
 
1082
1159
  // src/utils/format.ts
@@ -1134,7 +1211,7 @@ function registerStatus(program2) {
1134
1211
  const total = getEventCount();
1135
1212
  const todayEvents = getEventsForDate(todayDate());
1136
1213
  const last = getLastEvent();
1137
- const isPaused = import_fs9.default.existsSync(PAUSE_FLAG_PATH);
1214
+ const isPaused = import_fs11.default.existsSync(PAUSE_FLAG_PATH);
1138
1215
  log.plain("");
1139
1216
  log.plain(import_chalk3.default.bold(" Workscribe Status"));
1140
1217
  log.plain("");
@@ -1182,7 +1259,9 @@ var CATEGORY_COLORS = {
1182
1259
  file_ops: import_chalk4.default.yellow,
1183
1260
  other: import_chalk4.default.gray
1184
1261
  };
1185
- var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(ai_tools_default.map((t) => [t.tool, t.displayName]));
1262
+ var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
1263
+ loadAIToolRegistry().map((t) => [t.tool, t.displayName])
1264
+ );
1186
1265
  function registerEvents(program2) {
1187
1266
  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
1267
  let date = todayDate();
@@ -1370,9 +1449,9 @@ function registerSessions(program2) {
1370
1449
 
1371
1450
  // src/cli/summary.ts
1372
1451
  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"));
1452
+ var import_fs12 = __toESM(require("fs"));
1453
+ var import_os5 = __toESM(require("os"));
1454
+ var import_path8 = __toESM(require("path"));
1376
1455
 
1377
1456
  // src/ai/prompt.ts
1378
1457
  var SYSTEM_PROMPT = `You write short developer work summaries. Here is an example:
@@ -1767,7 +1846,7 @@ async function handleWebhook(opts, config, summary) {
1767
1846
  if (webhookUrl) {
1768
1847
  try {
1769
1848
  log.info("Posting to webhook\u2026");
1770
- const username = import_os4.default.userInfo().username;
1849
+ const username = import_os5.default.userInfo().username;
1771
1850
  const body = isSlackWebhookUrl(webhookUrl) ? formatForSlack(summary, username) : summary;
1772
1851
  await postToWebhook(webhookUrl, body);
1773
1852
  log.success("Posted.");
@@ -1804,9 +1883,9 @@ function registerSummary(program2) {
1804
1883
  return;
1805
1884
  }
1806
1885
  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");
1886
+ const cacheFile = import_path8.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
1887
+ if (!opts.fresh && import_fs12.default.existsSync(cacheFile)) {
1888
+ const cached = import_fs12.default.readFileSync(cacheFile, "utf-8");
1810
1889
  log.plain("");
1811
1890
  log.plain(cached);
1812
1891
  log.plain("");
@@ -1822,8 +1901,8 @@ function registerSummary(program2) {
1822
1901
  log.plain("");
1823
1902
  log.plain(summary);
1824
1903
  log.plain("");
1825
- import_fs10.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
1826
- import_fs10.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
1904
+ import_fs12.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
1905
+ import_fs12.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
1827
1906
  await handleWebhook(opts, config, summary);
1828
1907
  } catch (e) {
1829
1908
  log.error(`Weekly summary failed [${config.ai.provider}]: ${e.message}`);
@@ -1910,8 +1989,8 @@ ${bullets.trim()}`;
1910
1989
  }
1911
1990
 
1912
1991
  // src/cli/export.ts
1913
- var import_fs11 = __toESM(require("fs"));
1914
- var import_path7 = __toESM(require("path"));
1992
+ var import_fs13 = __toESM(require("fs"));
1993
+ var import_path9 = __toESM(require("path"));
1915
1994
  function eventRowToPayload2(row) {
1916
1995
  let metadata = {};
1917
1996
  try {
@@ -1956,9 +2035,9 @@ function registerExport(program2) {
1956
2035
  const payload = { date, sessions: sessionPayloads };
1957
2036
  log.info(`Generating summary via ${config.ai.provider}\u2026`);
1958
2037
  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}
2038
+ const outputPath = opts.output ?? import_path9.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
2039
+ if (!import_fs13.default.existsSync(EXPORTS_DIR)) import_fs13.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
2040
+ import_fs13.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
1962
2041
 
1963
2042
  ${summary}
1964
2043
  `, { encoding: "utf-8", mode: 384 });
@@ -2030,22 +2109,22 @@ function printConfig(obj, indent) {
2030
2109
  }
2031
2110
 
2032
2111
  // src/cli/pause.ts
2033
- var import_fs12 = __toESM(require("fs"));
2112
+ var import_fs14 = __toESM(require("fs"));
2034
2113
  function registerPause(program2) {
2035
2114
  program2.command("pause").description("Pause capture temporarily").action(() => {
2036
- if (import_fs12.default.existsSync(PAUSE_FLAG_PATH)) {
2115
+ if (import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
2037
2116
  log.info("Capture is already paused. Run workscribe resume to restart.");
2038
2117
  return;
2039
2118
  }
2040
- import_fs12.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
2119
+ import_fs14.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
2041
2120
  log.success("Capture paused. Run workscribe resume to restart.");
2042
2121
  });
2043
2122
  program2.command("resume").description("Resume capture after a pause").action(() => {
2044
- if (!import_fs12.default.existsSync(PAUSE_FLAG_PATH)) {
2123
+ if (!import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
2045
2124
  log.info("Capture is not paused.");
2046
2125
  return;
2047
2126
  }
2048
- import_fs12.default.rmSync(PAUSE_FLAG_PATH);
2127
+ import_fs14.default.rmSync(PAUSE_FLAG_PATH);
2049
2128
  log.success("Capture resumed.");
2050
2129
  });
2051
2130
  }
@@ -2130,18 +2209,18 @@ function registerClear(program2) {
2130
2209
  }
2131
2210
 
2132
2211
  // 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"));
2212
+ var import_fs15 = __toESM(require("fs"));
2213
+ var import_path10 = __toESM(require("path"));
2214
+ var import_os6 = __toESM(require("os"));
2136
2215
  var import_prompts3 = __toESM(require("prompts"));
2137
2216
  var import_chalk9 = __toESM(require("chalk"));
2138
2217
  function detectShellRcFiles() {
2139
2218
  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") }
2219
+ { name: "zsh", rcFile: import_path10.default.join(import_os6.default.homedir(), ".zshrc") },
2220
+ { name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bashrc") },
2221
+ { name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bash_profile") }
2143
2222
  ];
2144
- return candidates.filter((c) => import_fs13.default.existsSync(c.rcFile) && isHookInstalled(c.rcFile));
2223
+ return candidates.filter((c) => import_fs15.default.existsSync(c.rcFile) && isHookInstalled(c.rcFile));
2145
2224
  }
2146
2225
  function registerUninstall(program2) {
2147
2226
  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 +2239,7 @@ function registerUninstall(program2) {
2160
2239
  }
2161
2240
  }
2162
2241
  if (opts.hooksOnly) return;
2163
- if (import_fs13.default.existsSync(WORKSCRIBE_DIR)) {
2242
+ if (import_fs15.default.existsSync(WORKSCRIBE_DIR)) {
2164
2243
  log.plain("");
2165
2244
  const { deleteData } = await (0, import_prompts3.default)({
2166
2245
  type: "confirm",
@@ -2169,7 +2248,7 @@ function registerUninstall(program2) {
2169
2248
  initial: false
2170
2249
  });
2171
2250
  if (deleteData) {
2172
- import_fs13.default.rmSync(WORKSCRIBE_DIR, { recursive: true, force: true });
2251
+ import_fs15.default.rmSync(WORKSCRIBE_DIR, { recursive: true, force: true });
2173
2252
  log.success(`Deleted ${WORKSCRIBE_DIR}`);
2174
2253
  } else {
2175
2254
  log.info(`Data kept at ${WORKSCRIBE_DIR}`);
@@ -2183,7 +2262,7 @@ function registerUninstall(program2) {
2183
2262
 
2184
2263
  // src/index.ts
2185
2264
  var program = new import_commander.Command();
2186
- program.name("workscribe").description("Automatic developer work journal").version("1.7.2");
2265
+ program.name("workscribe").description("Automatic developer work journal").version("1.8.0");
2187
2266
  registerCapture(program);
2188
2267
  registerHook(program);
2189
2268
  registerInit(program);