@workscribe/cli 1.14.2 → 1.15.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
@@ -755,12 +755,28 @@ function readConfig() {
755
755
  }
756
756
  }
757
757
  function writeConfig(config) {
758
+ import_fs7.default.mkdirSync(import_path5.default.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
758
759
  import_fs7.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
759
760
  try {
760
761
  import_fs7.default.chmodSync(CONFIG_PATH, 384);
761
762
  } catch {
762
763
  }
763
764
  }
765
+ function ensureInstallId(config) {
766
+ if (config.telemetry.installId) return config.telemetry.installId;
767
+ const installId = import_crypto.default.randomUUID();
768
+ writeConfig({ ...config, telemetry: { ...config.telemetry, installId } });
769
+ return installId;
770
+ }
771
+ function ensureTelemetryFallback() {
772
+ const config = readConfig();
773
+ if (config.telemetry.installId) return;
774
+ config.telemetry.enabled = true;
775
+ config.telemetry.installId = ensureInstallId(config);
776
+ process.stderr.write(
777
+ "\n Workscribe collects anonymous install stats (install ID, CLI version, platform, Node version).\n Opt out: workscribe config set telemetry.enabled false\n\n"
778
+ );
779
+ }
764
780
  function getConfigValue(config, key) {
765
781
  const parts = key.split(".");
766
782
  let cur = config;
@@ -800,11 +816,13 @@ function deepMerge(defaults, overrides) {
800
816
  }
801
817
  return result;
802
818
  }
803
- var import_fs7, DEFAULTS;
819
+ var import_crypto, import_fs7, import_path5, DEFAULTS;
804
820
  var init_config = __esm({
805
821
  "src/config/index.ts"() {
806
822
  "use strict";
823
+ import_crypto = __toESM(require("crypto"));
807
824
  import_fs7 = __toESM(require("fs"));
825
+ import_path5 = __toESM(require("path"));
808
826
  init_paths();
809
827
  DEFAULTS = {
810
828
  ai: {
@@ -830,6 +848,10 @@ var init_config = __esm({
830
848
  },
831
849
  summary: {
832
850
  format: "markdown"
851
+ },
852
+ telemetry: {
853
+ enabled: true,
854
+ installId: ""
833
855
  }
834
856
  };
835
857
  }
@@ -902,7 +924,7 @@ var init_fetch = __esm({
902
924
 
903
925
  // src/profiles/fetcher.ts
904
926
  function manifestCachePath() {
905
- return import_path5.default.join(PROFILES_DIR, "manifest.json");
927
+ return import_path6.default.join(PROFILES_DIR, "manifest.json");
906
928
  }
907
929
  async function fetchManifest() {
908
930
  const response = await fetchWithTimeout(`${REGISTRY_BASE}/manifest.json`, {});
@@ -922,7 +944,7 @@ async function fetchProfile(name) {
922
944
  async function installProfile(name) {
923
945
  ensureProfilesDir();
924
946
  const profile = await fetchProfile(name);
925
- const dest = import_path5.default.join(PROFILES_DIR, `${name}.json`);
947
+ const dest = import_path6.default.join(PROFILES_DIR, `${name}.json`);
926
948
  import_fs8.default.writeFileSync(dest, JSON.stringify(profile, null, 2), { encoding: "utf-8", mode: 384 });
927
949
  debugLog("profiles", `Installed profile "${name}" to ${dest}`);
928
950
  }
@@ -946,12 +968,12 @@ function ensureProfilesDir() {
946
968
  import_fs8.default.mkdirSync(PROFILES_DIR, { recursive: true, mode: 448 });
947
969
  }
948
970
  }
949
- var import_fs8, import_path5, REGISTRY_BASE;
971
+ var import_fs8, import_path6, REGISTRY_BASE;
950
972
  var init_fetcher = __esm({
951
973
  "src/profiles/fetcher.ts"() {
952
974
  "use strict";
953
975
  import_fs8 = __toESM(require("fs"));
954
- import_path5 = __toESM(require("path"));
976
+ import_path6 = __toESM(require("path"));
955
977
  init_paths();
956
978
  init_schema();
957
979
  init_logger();
@@ -982,7 +1004,7 @@ function compileRule(profileName, rule) {
982
1004
  };
983
1005
  }
984
1006
  function loadProfileFromDisk(name) {
985
- const filePath = import_path6.default.join(PROFILES_DIR, `${name}.json`);
1007
+ const filePath = import_path7.default.join(PROFILES_DIR, `${name}.json`);
986
1008
  if (!import_fs9.default.existsSync(filePath)) {
987
1009
  debugLog("profiles", `Profile "${name}" not found in cache \u2014 run: workscribe profile add ${name}`);
988
1010
  return null;
@@ -1034,15 +1056,15 @@ function detectProfilesForDirectory(dir) {
1034
1056
  return manifest.filter((entry) => {
1035
1057
  if (activeNames.has(entry.name)) return false;
1036
1058
  const indicatorFiles = entry.detect?.files ?? [];
1037
- return indicatorFiles.length > 0 && indicatorFiles.every((file) => import_fs9.default.existsSync(import_path6.default.join(dir, file)));
1059
+ return indicatorFiles.length > 0 && indicatorFiles.every((file) => import_fs9.default.existsSync(import_path7.default.join(dir, file)));
1038
1060
  });
1039
1061
  }
1040
- var import_fs9, import_path6;
1062
+ var import_fs9, import_path7;
1041
1063
  var init_loader = __esm({
1042
1064
  "src/profiles/loader.ts"() {
1043
1065
  "use strict";
1044
1066
  import_fs9 = __toESM(require("fs"));
1045
- import_path6 = __toESM(require("path"));
1067
+ import_path7 = __toESM(require("path"));
1046
1068
  init_paths();
1047
1069
  init_config();
1048
1070
  init_schema();
@@ -1478,7 +1500,7 @@ function insertAlert(tool, sessionId, context) {
1478
1500
  `INSERT INTO alerts (created_at, tool, session_id, context)
1479
1501
  VALUES ($created_at, $tool, $session_id, $context)`
1480
1502
  ).run({ created_at: Date.now(), tool, session_id: sessionId ?? null, context });
1481
- import_fs11.default.mkdirSync(import_path7.default.dirname(ALERTS_PENDING_PATH), { recursive: true });
1503
+ import_fs11.default.mkdirSync(import_path8.default.dirname(ALERTS_PENDING_PATH), { recursive: true });
1482
1504
  import_fs11.default.writeFileSync(ALERTS_PENDING_PATH, "", { flag: "w" });
1483
1505
  }
1484
1506
  function getPendingAlerts() {
@@ -1496,12 +1518,12 @@ function clearAlerts() {
1496
1518
  }
1497
1519
  return Number(result.changes);
1498
1520
  }
1499
- var import_fs11, import_path7;
1521
+ var import_fs11, import_path8;
1500
1522
  var init_alerts2 = __esm({
1501
1523
  "src/db/alerts.ts"() {
1502
1524
  "use strict";
1503
1525
  import_fs11 = __toESM(require("fs"));
1504
- import_path7 = __toESM(require("path"));
1526
+ import_path8 = __toESM(require("path"));
1505
1527
  init_db();
1506
1528
  init_paths();
1507
1529
  }
@@ -2143,7 +2165,7 @@ function isHookOutdated(rcFile, hookContent) {
2143
2165
  function installHook(rcFile, hookContent) {
2144
2166
  const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
2145
2167
  import_fs12.default.copyFileSync(rcFile, backup);
2146
- log.info(`Backed up ${rcFile} \u2192 ${import_path8.default.basename(backup)}`);
2168
+ log.info(`Backed up ${rcFile} \u2192 ${import_path9.default.basename(backup)}`);
2147
2169
  import_fs12.default.appendFileSync(rcFile, `
2148
2170
  ${hookContent}
2149
2171
  `);
@@ -2151,7 +2173,7 @@ ${hookContent}
2151
2173
  function updateHook(rcFile, hookContent) {
2152
2174
  const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
2153
2175
  import_fs12.default.copyFileSync(rcFile, backup);
2154
- log.info(`Backed up ${rcFile} \u2192 ${import_path8.default.basename(backup)}`);
2176
+ log.info(`Backed up ${rcFile} \u2192 ${import_path9.default.basename(backup)}`);
2155
2177
  const content = import_fs12.default.readFileSync(rcFile, "utf-8");
2156
2178
  const startIdx = content.indexOf(HOOK_START);
2157
2179
  const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
@@ -2160,7 +2182,7 @@ function updateHook(rcFile, hookContent) {
2160
2182
  function removeHook(rcFile) {
2161
2183
  const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
2162
2184
  import_fs12.default.copyFileSync(rcFile, backup);
2163
- log.info(`Backed up ${rcFile} \u2192 ${import_path8.default.basename(backup)}`);
2185
+ log.info(`Backed up ${rcFile} \u2192 ${import_path9.default.basename(backup)}`);
2164
2186
  const content = import_fs12.default.readFileSync(rcFile, "utf-8");
2165
2187
  const startIdx = content.indexOf(HOOK_START);
2166
2188
  const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
@@ -2179,13 +2201,13 @@ function resolveWorkscribeBinary() {
2179
2201
  }
2180
2202
  }
2181
2203
  function injectBinaryPath(hookContent, binaryPath, nodePath) {
2182
- const pinnedInvocation = `"${nodePath}" "${binaryPath}"`;
2204
+ const pinnedInvocation = `"${nodePath}" --disable-warning=ExperimentalWarning "${binaryPath}"`;
2183
2205
  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}"
2184
2206
  or return 0`).replaceAll("command workscribe _capture", `${pinnedInvocation} _capture`).replaceAll("command workscribe alerts", `${pinnedInvocation} alerts`);
2185
2207
  }
2186
2208
  function isCompletionsInstalled(shell) {
2187
2209
  if (shell.name === "fish") {
2188
- return import_fs12.default.existsSync(import_path8.default.join(FISH_COMPLETIONS_DIR, "workscribe.fish"));
2210
+ return import_fs12.default.existsSync(import_path9.default.join(FISH_COMPLETIONS_DIR, "workscribe.fish"));
2189
2211
  }
2190
2212
  try {
2191
2213
  return import_fs12.default.readFileSync(shell.rcFile, "utf-8").includes(COMPLETIONS_MARKER);
@@ -2198,10 +2220,10 @@ function installCompletions(shell, commands) {
2198
2220
  if (!import_fs12.default.existsSync(FISH_COMPLETIONS_DIR)) {
2199
2221
  import_fs12.default.mkdirSync(FISH_COMPLETIONS_DIR, { recursive: true });
2200
2222
  }
2201
- import_fs12.default.writeFileSync(import_path8.default.join(FISH_COMPLETIONS_DIR, "workscribe.fish"), fishCompletion(commands), { mode: 420 });
2223
+ import_fs12.default.writeFileSync(import_path9.default.join(FISH_COMPLETIONS_DIR, "workscribe.fish"), fishCompletion(commands), { mode: 420 });
2202
2224
  } else {
2203
2225
  const script = shell.name === "zsh" ? zshCompletion(commands) : bashCompletion(commands);
2204
- const cacheFile = import_path8.default.join(WORKSCRIBE_DIR, `completion.${shell.name}`);
2226
+ const cacheFile = import_path9.default.join(WORKSCRIBE_DIR, `completion.${shell.name}`);
2205
2227
  import_fs12.default.writeFileSync(cacheFile, script, { mode: 420 });
2206
2228
  if (!isCompletionsInstalled(shell)) {
2207
2229
  const sourceLine = shell.name === "zsh" ? `
@@ -2216,18 +2238,18 @@ source "${cacheFile}"
2216
2238
  }
2217
2239
  }
2218
2240
  }
2219
- var import_fs12, import_path8, import_os3, import_child_process3, FISH_COMPLETIONS_DIR, HOOK_START, HOOK_END, HOOK_PREAMBLE, COMPLETIONS_MARKER;
2241
+ var import_fs12, import_path9, import_os3, import_child_process3, FISH_COMPLETIONS_DIR, HOOK_START, HOOK_END, HOOK_PREAMBLE, COMPLETIONS_MARKER;
2220
2242
  var init_shell_hook = __esm({
2221
2243
  "src/capture/shell-hook.ts"() {
2222
2244
  "use strict";
2223
2245
  import_fs12 = __toESM(require("fs"));
2224
- import_path8 = __toESM(require("path"));
2246
+ import_path9 = __toESM(require("path"));
2225
2247
  import_os3 = __toESM(require("os"));
2226
2248
  import_child_process3 = require("child_process");
2227
2249
  init_logger();
2228
2250
  init_completion_scripts();
2229
2251
  init_paths();
2230
- FISH_COMPLETIONS_DIR = import_path8.default.join(import_os3.default.homedir(), ".config", "fish", "completions");
2252
+ FISH_COMPLETIONS_DIR = import_path9.default.join(import_os3.default.homedir(), ".config", "fish", "completions");
2231
2253
  HOOK_START = "# workscribe:hook:start";
2232
2254
  HOOK_END = "# workscribe:hook:end";
2233
2255
  HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
@@ -2311,17 +2333,17 @@ function checkHookHealth() {
2311
2333
  }
2312
2334
  return { installed: true, pinned: false, confirmedBroken };
2313
2335
  }
2314
- var import_fs13, import_path9, import_os4, import_child_process5, CLAUDE_SETTINGS_PATH, HOOK_MARKER, claudeCodeIntegration;
2336
+ var import_fs13, import_path10, import_os4, import_child_process5, CLAUDE_SETTINGS_PATH, HOOK_MARKER, claudeCodeIntegration;
2315
2337
  var init_claude_code = __esm({
2316
2338
  "src/capture/hooks/claude-code.ts"() {
2317
2339
  "use strict";
2318
2340
  import_fs13 = __toESM(require("fs"));
2319
- import_path9 = __toESM(require("path"));
2341
+ import_path10 = __toESM(require("path"));
2320
2342
  import_os4 = __toESM(require("os"));
2321
2343
  import_child_process5 = require("child_process");
2322
2344
  init_utils();
2323
2345
  init_shell_hook();
2324
- CLAUDE_SETTINGS_PATH = import_path9.default.join(import_os4.default.homedir(), ".claude", "settings.json");
2346
+ CLAUDE_SETTINGS_PATH = import_path10.default.join(import_os4.default.homedir(), ".claude", "settings.json");
2325
2347
  HOOK_MARKER = "_hook --source claude-code";
2326
2348
  claudeCodeIntegration = {
2327
2349
  displayName: "Claude Code",
@@ -2365,7 +2387,7 @@ var init_claude_code = __esm({
2365
2387
  }
2366
2388
  ]
2367
2389
  };
2368
- import_fs13.default.mkdirSync(import_path9.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
2390
+ import_fs13.default.mkdirSync(import_path10.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
2369
2391
  import_fs13.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
2370
2392
  }
2371
2393
  };
@@ -2385,6 +2407,10 @@ var init_hooks = __esm({
2385
2407
  });
2386
2408
 
2387
2409
  // src/cli/completions.ts
2410
+ function shouldRunStartupChecks(invokedCommand) {
2411
+ if (invokedCommand === void 0) return true;
2412
+ return !HIDDEN_COMMANDS.has(invokedCommand);
2413
+ }
2388
2414
  function getPublicCommands(program) {
2389
2415
  return program.commands.filter((command) => !HIDDEN_COMMANDS.has(command.name())).map((command) => ({ name: command.name(), description: command.description() }));
2390
2416
  }
@@ -2414,11 +2440,11 @@ var init_completions = __esm({
2414
2440
  // src/cli/init.ts
2415
2441
  function detectShell() {
2416
2442
  const shell = process.env.SHELL ?? "";
2417
- if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path10.default.join(import_os5.default.homedir(), ".zshrc") };
2418
- if (shell.includes("bash")) return { name: "bash", rcFile: import_path10.default.join(import_os5.default.homedir(), ".bashrc") };
2443
+ if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path11.default.join(import_os5.default.homedir(), ".zshrc") };
2444
+ if (shell.includes("bash")) return { name: "bash", rcFile: import_path11.default.join(import_os5.default.homedir(), ".bashrc") };
2419
2445
  if (shell.includes("fish")) {
2420
- const confDir = import_path10.default.join(import_os5.default.homedir(), ".config", "fish", "conf.d");
2421
- return { name: "fish", rcFile: import_path10.default.join(confDir, "workscribe.fish") };
2446
+ const confDir = import_path11.default.join(import_os5.default.homedir(), ".config", "fish", "conf.d");
2447
+ return { name: "fish", rcFile: import_path11.default.join(confDir, "workscribe.fish") };
2422
2448
  }
2423
2449
  return null;
2424
2450
  }
@@ -2426,262 +2452,299 @@ function step(current, total, label) {
2426
2452
  log.plain(import_chalk2.default.bold(`
2427
2453
  [${current}/${total}] ${label}`));
2428
2454
  }
2429
- function registerInit(program) {
2430
- program.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
2431
- const TOTAL_STEPS = 5;
2432
- log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
2433
- step(1, TOTAL_STEPS, "Creating data directories");
2434
- for (const dir of REQUIRED_DIRS) {
2435
- if (!import_fs14.default.existsSync(dir)) {
2436
- import_fs14.default.mkdirSync(dir, { recursive: true, mode: 448 });
2437
- log.success(`Created ${dir}`);
2438
- } else {
2439
- try {
2440
- import_fs14.default.chmodSync(dir, 448);
2441
- } catch {
2442
- }
2443
- log.info(`${dir} already exists`);
2444
- }
2445
- }
2446
- if (!import_fs14.default.existsSync(PROFILES_DIR)) {
2447
- import_fs14.default.mkdirSync(PROFILES_DIR, { recursive: true, mode: 448 });
2448
- }
2449
- step(2, TOTAL_STEPS, "Initialising database");
2450
- getDb();
2451
- log.success("Database ready");
2452
- step(3, TOTAL_STEPS, "Installing shell hook");
2453
- const shell = detectShell();
2454
- if (!shell) {
2455
- log.warn("Could not detect shell. Supported: zsh, bash, fish");
2456
- log.plain(
2457
- ` Manually add the hook from: ${import_chalk2.default.cyan("shell/hook.zsh")}, ${import_chalk2.default.cyan("shell/hook.bash")}, or ${import_chalk2.default.cyan("shell/hook.fish")}`
2458
- );
2455
+ function runDataDirectoriesStep() {
2456
+ step(1, TOTAL_STEPS, "Creating data directories");
2457
+ for (const dir of REQUIRED_DIRS) {
2458
+ if (!import_fs14.default.existsSync(dir)) {
2459
+ import_fs14.default.mkdirSync(dir, { recursive: true, mode: 448 });
2460
+ log.success(`Created ${dir}`);
2459
2461
  } else {
2460
- const hookFile = import_path10.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
2461
- if (!import_fs14.default.existsSync(hookFile)) {
2462
- log.warn(`Hook template not found at ${hookFile}`);
2463
- } else {
2464
- const hookContent = import_fs14.default.readFileSync(hookFile, "utf-8");
2465
- const binaryPath = resolveWorkscribeBinary();
2466
- const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath, process.execPath) : hookContent;
2467
- if (shell.name === "fish") {
2468
- const confDir = import_path10.default.dirname(shell.rcFile);
2469
- if (!import_fs14.default.existsSync(confDir)) import_fs14.default.mkdirSync(confDir, { recursive: true });
2470
- }
2471
- if (!import_fs14.default.existsSync(shell.rcFile)) import_fs14.default.writeFileSync(shell.rcFile, "");
2472
- if (!isHookInstalled(shell.rcFile)) {
2473
- log.plain("");
2474
- log.plain(import_chalk2.default.dim(" Workscribe adds a lightweight hook to your shell that captures each"));
2475
- log.plain(import_chalk2.default.dim(" command you run \u2014 the text, exit code, duration, and working directory."));
2476
- log.plain(import_chalk2.default.dim(" It runs in the background and adds no latency to your terminal."));
2477
- log.plain(import_chalk2.default.dim(" Credentials and secrets are redacted before anything is stored."));
2478
- log.plain("");
2479
- installHook(shell.rcFile, hookWithPath);
2480
- log.success(`Shell hook installed in ${shell.rcFile}`);
2481
- } else if (isHookOutdated(shell.rcFile, hookWithPath)) {
2482
- const { update } = await (0, import_prompts.default)({
2483
- type: "confirm",
2484
- name: "update",
2485
- message: `Shell hook in ${shell.rcFile} is outdated. Update?`,
2486
- initial: true
2487
- });
2488
- if (update) {
2489
- updateHook(shell.rcFile, hookWithPath);
2490
- log.success(`Shell hook updated in ${shell.rcFile}`);
2491
- } else {
2492
- log.info("Shell hook update skipped");
2493
- }
2494
- } else {
2495
- log.info(`Shell hook up to date in ${shell.rcFile}`);
2496
- }
2497
- if (isCompletionsInstalled(shell)) {
2498
- log.info("Shell completions already installed");
2499
- } else {
2500
- const { installComps } = await (0, import_prompts.default)({
2501
- type: "confirm",
2502
- name: "installComps",
2503
- message: "Install shell completions (tab-complete workscribe commands)?",
2504
- initial: true
2505
- });
2506
- if (installComps) {
2507
- try {
2508
- installCompletions(shell, getPublicCommands(program));
2509
- log.success("Shell completions installed");
2510
- } catch (error) {
2511
- log.warn(`Completions install failed: ${error.message}`);
2512
- }
2513
- }
2514
- }
2462
+ try {
2463
+ import_fs14.default.chmodSync(dir, 448);
2464
+ } catch {
2515
2465
  }
2466
+ log.info(`${dir} already exists`);
2516
2467
  }
2517
- step(4, TOTAL_STEPS, "Detecting AI coding tools");
2518
- const installedTools = loadAIToolRegistry().filter(
2519
- (entry) => entry.hook !== null && AI_HOOK_INTEGRATIONS[entry.hook]?.isInstalled()
2468
+ }
2469
+ if (!import_fs14.default.existsSync(PROFILES_DIR)) {
2470
+ import_fs14.default.mkdirSync(PROFILES_DIR, { recursive: true, mode: 448 });
2471
+ }
2472
+ }
2473
+ function initDatabaseAndConfig() {
2474
+ step(2, TOTAL_STEPS, "Initialising database");
2475
+ const config = readConfig();
2476
+ getDb();
2477
+ log.success("Database ready");
2478
+ return config;
2479
+ }
2480
+ async function runTelemetryStep(config) {
2481
+ log.plain("");
2482
+ log.plain(import_chalk2.default.dim(" Workscribe can send anonymous install stats: a random install ID,"));
2483
+ log.plain(import_chalk2.default.dim(" CLI version, OS platform, and Node.js version. No command, event, or"));
2484
+ log.plain(import_chalk2.default.dim(" summary data is ever sent. Opt out anytime:"));
2485
+ log.plain(import_chalk2.default.dim(" workscribe config set telemetry.enabled false"));
2486
+ log.plain("");
2487
+ const { telemetryEnabled } = await (0, import_prompts.default)({
2488
+ type: "confirm",
2489
+ name: "telemetryEnabled",
2490
+ message: "Send anonymous install stats?",
2491
+ initial: true
2492
+ });
2493
+ config.telemetry.enabled = telemetryEnabled;
2494
+ config.telemetry.installId = ensureInstallId(config);
2495
+ }
2496
+ async function runShellHookStep(program) {
2497
+ step(3, TOTAL_STEPS, "Installing shell hook");
2498
+ const shell = detectShell();
2499
+ if (!shell) {
2500
+ log.warn("Could not detect shell. Supported: zsh, bash, fish");
2501
+ log.plain(
2502
+ ` Manually add the hook from: ${import_chalk2.default.cyan("shell/hook.zsh")}, ${import_chalk2.default.cyan("shell/hook.bash")}, or ${import_chalk2.default.cyan("shell/hook.fish")}`
2520
2503
  );
2521
- if (installedTools.length === 0) {
2522
- log.info("No AI coding tools detected \u2014 re-run workscribe init after installing one");
2504
+ return null;
2505
+ }
2506
+ const hookFile = import_path11.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
2507
+ if (!import_fs14.default.existsSync(hookFile)) {
2508
+ log.warn(`Hook template not found at ${hookFile}`);
2509
+ return shell;
2510
+ }
2511
+ const hookContent = import_fs14.default.readFileSync(hookFile, "utf-8");
2512
+ const binaryPath = resolveWorkscribeBinary();
2513
+ const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath, process.execPath) : hookContent;
2514
+ if (shell.name === "fish") {
2515
+ const confDir = import_path11.default.dirname(shell.rcFile);
2516
+ if (!import_fs14.default.existsSync(confDir)) import_fs14.default.mkdirSync(confDir, { recursive: true });
2517
+ }
2518
+ if (!import_fs14.default.existsSync(shell.rcFile)) import_fs14.default.writeFileSync(shell.rcFile, "");
2519
+ if (!isHookInstalled(shell.rcFile)) {
2520
+ log.plain("");
2521
+ log.plain(import_chalk2.default.dim(" Workscribe adds a lightweight hook to your shell that captures each"));
2522
+ log.plain(import_chalk2.default.dim(" command you run \u2014 the text, exit code, duration, and working directory."));
2523
+ log.plain(import_chalk2.default.dim(" It runs in the background and adds no latency to your terminal."));
2524
+ log.plain(import_chalk2.default.dim(" Credentials and secrets are redacted before anything is stored."));
2525
+ log.plain("");
2526
+ installHook(shell.rcFile, hookWithPath);
2527
+ log.success(`Shell hook installed in ${shell.rcFile}`);
2528
+ } else if (isHookOutdated(shell.rcFile, hookWithPath)) {
2529
+ const { update } = await (0, import_prompts.default)({
2530
+ type: "confirm",
2531
+ name: "update",
2532
+ message: `Shell hook in ${shell.rcFile} is outdated. Update?`,
2533
+ initial: true
2534
+ });
2535
+ if (update) {
2536
+ updateHook(shell.rcFile, hookWithPath);
2537
+ log.success(`Shell hook updated in ${shell.rcFile}`);
2523
2538
  } else {
2524
- for (const tool of installedTools) {
2525
- const integration = AI_HOOK_INTEGRATIONS[tool.hook];
2526
- if (integration.isHookInstalled()) {
2527
- log.info(`${integration.displayName} session hook already installed`);
2528
- } else {
2529
- const { install } = await (0, import_prompts.default)({
2530
- type: "confirm",
2531
- name: "install",
2532
- message: `${integration.displayName} detected. Install session capture hook?`,
2533
- initial: false
2534
- });
2535
- if (install) {
2536
- integration.install();
2537
- log.success(`${integration.displayName} session hook installed`);
2538
- } else {
2539
- log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
2540
- }
2541
- }
2542
- }
2539
+ log.info("Shell hook update skipped");
2543
2540
  }
2544
- const config = readConfig();
2545
- try {
2546
- await cacheManifest();
2547
- const detected = detectProfilesForDirectory(process.cwd());
2548
- const alreadyActive = new Set(config.capture.profiles);
2549
- const toPrompt = detected.filter((entry) => !alreadyActive.has(entry.name));
2550
- if (toPrompt.length > 0) {
2551
- log.plain("");
2552
- for (const entry of toPrompt) {
2553
- const { install } = await (0, import_prompts.default)({
2554
- type: "confirm",
2555
- name: "install",
2556
- message: `${entry.displayName} project detected. Enable the ${entry.displayName} profile?`,
2557
- initial: true
2558
- });
2559
- if (install) {
2560
- await installProfile(entry.name);
2561
- config.capture.profiles = [.../* @__PURE__ */ new Set([...config.capture.profiles, entry.name])];
2562
- log.success(`${entry.displayName} profile enabled`);
2563
- }
2564
- }
2565
- writeConfig(config);
2566
- } else if (detected.length === 0) {
2567
- log.info("No project profiles detected \u2014 run workscribe profile detect from your project directory");
2541
+ } else {
2542
+ log.info(`Shell hook up to date in ${shell.rcFile}`);
2543
+ }
2544
+ if (isCompletionsInstalled(shell)) {
2545
+ log.info("Shell completions already installed");
2546
+ } else {
2547
+ const { installComps } = await (0, import_prompts.default)({
2548
+ type: "confirm",
2549
+ name: "installComps",
2550
+ message: "Install shell completions (tab-complete workscribe commands)?",
2551
+ initial: true
2552
+ });
2553
+ if (installComps) {
2554
+ try {
2555
+ installCompletions(shell, getPublicCommands(program));
2556
+ log.success("Shell completions installed");
2557
+ } catch (error) {
2558
+ log.warn(`Completions install failed: ${error.message}`);
2568
2559
  }
2569
- } catch {
2570
- log.info("Could not reach profile registry \u2014 run workscribe profile detect after connecting");
2571
2560
  }
2572
- step(5, TOTAL_STEPS, "Configuring AI provider");
2573
- let providerConfigured = !!config.ai.provider;
2574
- if (config.ai.provider) {
2575
- log.info(`AI provider already configured: ${import_chalk2.default.cyan(config.ai.provider)}`);
2561
+ }
2562
+ return shell;
2563
+ }
2564
+ async function runAIToolsStep() {
2565
+ step(4, TOTAL_STEPS, "Detecting AI coding tools");
2566
+ const installedTools = loadAIToolRegistry().filter(
2567
+ (entry) => entry.hook !== null && AI_HOOK_INTEGRATIONS[entry.hook]?.isInstalled()
2568
+ );
2569
+ if (installedTools.length === 0) {
2570
+ log.info("No AI coding tools detected \u2014 re-run workscribe init after installing one");
2571
+ return;
2572
+ }
2573
+ for (const tool of installedTools) {
2574
+ const integration = AI_HOOK_INTEGRATIONS[tool.hook];
2575
+ if (integration.isHookInstalled()) {
2576
+ log.info(`${integration.displayName} session hook already installed`);
2576
2577
  } else {
2577
- log.plain("");
2578
- const { provider } = await (0, import_prompts.default)({
2579
- type: "select",
2580
- name: "provider",
2581
- message: "Which AI provider do you want to use?",
2582
- choices: [
2583
- { title: "Anthropic (Claude) \u2014 requires API key", value: "anthropic" },
2584
- { title: "OpenAI \u2014 requires API key", value: "openai" },
2585
- { title: "Ollama (local) \u2014 free, runs on your machine", value: "ollama" },
2586
- { title: "Other (OpenAI-compatible) \u2014 Groq, Together, Mistral\u2026", value: "openai-compatible" },
2587
- { title: "Skip \u2014 configure later", value: "skip" }
2588
- ]
2578
+ const { install } = await (0, import_prompts.default)({
2579
+ type: "confirm",
2580
+ name: "install",
2581
+ message: `${integration.displayName} detected. Install session capture hook?`,
2582
+ initial: false
2589
2583
  });
2590
- if (!provider || provider === "skip") {
2591
- providerConfigured = false;
2592
- log.info("Skipped \u2014 run later: workscribe config set ai.provider <provider>");
2584
+ if (install) {
2585
+ integration.install();
2586
+ log.success(`${integration.displayName} session hook installed`);
2593
2587
  } else {
2594
- config.ai.provider = provider;
2595
- if (provider === "ollama") {
2596
- log.plain(`
2597
- ${import_chalk2.default.dim("Make sure Ollama is running: ollama serve")}`);
2598
- log.plain(
2599
- ` ${import_chalk2.default.dim("Default model: llama3 (change with: workscribe config set ai.model <model>)")}
2600
- `
2601
- );
2602
- } else {
2603
- const { apiKey } = await (0, import_prompts.default)({
2604
- type: "password",
2605
- name: "apiKey",
2606
- message: `Enter your ${provider === "openai-compatible" ? "" : provider + " "}API key:`
2607
- });
2608
- if (apiKey) config.ai.apiKey = apiKey;
2609
- if (provider === "openai-compatible") {
2610
- const { baseUrl } = await (0, import_prompts.default)({
2611
- type: "text",
2612
- name: "baseUrl",
2613
- message: "Enter the base URL (e.g. https://api.groq.com/openai/v1):"
2614
- });
2615
- if (baseUrl) config.ai.baseUrl = baseUrl;
2616
- }
2617
- }
2618
- log.plain("");
2619
- log.info(`Validating ${import_chalk2.default.cyan(provider)} connection\u2026`);
2620
- try {
2621
- const testProvider = getProvider(config);
2622
- await testProvider.validate();
2623
- log.success("API key verified");
2624
- writeConfig(config);
2625
- providerConfigured = true;
2626
- } catch (error) {
2627
- log.warn(`Validation failed: ${error.message}`);
2628
- const { retry } = await (0, import_prompts.default)({
2629
- type: "confirm",
2630
- name: "retry",
2631
- message: "Save anyway and configure later?",
2632
- initial: false
2633
- });
2634
- if (retry) {
2635
- writeConfig(config);
2636
- providerConfigured = false;
2637
- log.info("Config saved \u2014 run workscribe config set ai.apiKey <key> to update");
2638
- } else {
2639
- log.info("API key not saved \u2014 run workscribe init again to configure");
2640
- providerConfigured = false;
2641
- }
2642
- }
2588
+ log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
2643
2589
  }
2644
2590
  }
2645
- if (!import_fs14.default.existsSync(CONFIG_PATH)) {
2591
+ }
2592
+ }
2593
+ async function runProfileDetectionStep(config) {
2594
+ try {
2595
+ await cacheManifest();
2596
+ const detected = detectProfilesForDirectory(process.cwd());
2597
+ const alreadyActive = new Set(config.capture.profiles);
2598
+ const toPrompt = detected.filter((entry) => !alreadyActive.has(entry.name));
2599
+ if (toPrompt.length > 0) {
2600
+ log.plain("");
2601
+ for (const entry of toPrompt) {
2602
+ const { install } = await (0, import_prompts.default)({
2603
+ type: "confirm",
2604
+ name: "install",
2605
+ message: `${entry.displayName} project detected. Enable the ${entry.displayName} profile?`,
2606
+ initial: true
2607
+ });
2608
+ if (install) {
2609
+ await installProfile(entry.name);
2610
+ config.capture.profiles = [.../* @__PURE__ */ new Set([...config.capture.profiles, entry.name])];
2611
+ log.success(`${entry.displayName} profile enabled`);
2612
+ }
2613
+ }
2646
2614
  writeConfig(config);
2615
+ } else if (detected.length === 0) {
2616
+ log.info("No project profiles detected \u2014 run workscribe profile detect from your project directory");
2647
2617
  }
2648
- log.plain("");
2649
- log.plain(import_chalk2.default.bold(" Setup complete!"));
2650
- log.plain("");
2651
- if (shell) {
2652
- log.plain(` Activate capture now:`);
2653
- if (shell.name === "fish") {
2654
- log.plain(` Restart your terminal`);
2655
- log.plain(` ${import_chalk2.default.dim(`or run: source ${shell.rcFile}`)}`);
2656
- } else {
2657
- log.plain(` ${import_chalk2.default.cyan(`source ${shell.rcFile}`)}`);
2658
- log.plain(` Or restart your terminal.`);
2659
- }
2660
- log.plain("");
2618
+ } catch {
2619
+ log.info("Could not reach profile registry \u2014 run workscribe profile detect after connecting");
2620
+ }
2621
+ }
2622
+ async function runAIProviderStep(config) {
2623
+ step(5, TOTAL_STEPS, "Configuring AI provider");
2624
+ let providerConfigured = !!config.ai.provider;
2625
+ if (config.ai.provider) {
2626
+ log.info(`AI provider already configured: ${import_chalk2.default.cyan(config.ai.provider)}`);
2627
+ return providerConfigured;
2628
+ }
2629
+ log.plain("");
2630
+ const { provider } = await (0, import_prompts.default)({
2631
+ type: "select",
2632
+ name: "provider",
2633
+ message: "Which AI provider do you want to use?",
2634
+ choices: [
2635
+ { title: "Anthropic (Claude) \u2014 requires API key", value: "anthropic" },
2636
+ { title: "OpenAI \u2014 requires API key", value: "openai" },
2637
+ { title: "Ollama (local) \u2014 free, runs on your machine", value: "ollama" },
2638
+ { title: "Other (OpenAI-compatible) \u2014 Groq, Together, Mistral\u2026", value: "openai-compatible" },
2639
+ { title: "Skip \u2014 configure later", value: "skip" }
2640
+ ]
2641
+ });
2642
+ if (!provider || provider === "skip") {
2643
+ log.info("Skipped \u2014 run later: workscribe config set ai.provider <provider>");
2644
+ return false;
2645
+ }
2646
+ config.ai.provider = provider;
2647
+ if (provider === "ollama") {
2648
+ log.plain(`
2649
+ ${import_chalk2.default.dim("Make sure Ollama is running: ollama serve")}`);
2650
+ log.plain(` ${import_chalk2.default.dim("Default model: llama3 (change with: workscribe config set ai.model <model>)")}
2651
+ `);
2652
+ } else {
2653
+ const { apiKey } = await (0, import_prompts.default)({
2654
+ type: "password",
2655
+ name: "apiKey",
2656
+ message: `Enter your ${provider === "openai-compatible" ? "" : provider + " "}API key:`
2657
+ });
2658
+ if (apiKey) config.ai.apiKey = apiKey;
2659
+ if (provider === "openai-compatible") {
2660
+ const { baseUrl } = await (0, import_prompts.default)({
2661
+ type: "text",
2662
+ name: "baseUrl",
2663
+ message: "Enter the base URL (e.g. https://api.groq.com/openai/v1):"
2664
+ });
2665
+ if (baseUrl) config.ai.baseUrl = baseUrl;
2661
2666
  }
2662
- if (providerConfigured) {
2663
- log.plain(` Then run a few commands and generate your first summary:`);
2664
- log.plain(` ${import_chalk2.default.cyan("workscribe summary")}`);
2665
- log.plain("");
2666
- log.plain(` Check capture is working:`);
2667
- log.plain(` ${import_chalk2.default.cyan("workscribe status")}`);
2667
+ }
2668
+ log.plain("");
2669
+ log.info(`Validating ${import_chalk2.default.cyan(provider)} connection\u2026`);
2670
+ try {
2671
+ const testProvider = getProvider(config);
2672
+ await testProvider.validate();
2673
+ log.success("API key verified");
2674
+ writeConfig(config);
2675
+ providerConfigured = true;
2676
+ } catch (error) {
2677
+ log.warn(`Validation failed: ${error.message}`);
2678
+ const { retry } = await (0, import_prompts.default)({
2679
+ type: "confirm",
2680
+ name: "retry",
2681
+ message: "Save anyway and configure later?",
2682
+ initial: false
2683
+ });
2684
+ if (retry) {
2685
+ writeConfig(config);
2686
+ providerConfigured = false;
2687
+ log.info("Config saved \u2014 run workscribe config set ai.apiKey <key> to update");
2668
2688
  } else {
2669
- log.plain(` Before generating summaries, configure an AI provider:`);
2670
- log.plain(` ${import_chalk2.default.cyan("workscribe config set ai.provider anthropic")}`);
2671
- log.plain(` ${import_chalk2.default.cyan("workscribe config set ai.apiKey <your-key>")}`);
2672
- log.plain("");
2673
- log.plain(` Then run:`);
2674
- log.plain(` ${import_chalk2.default.cyan("workscribe summary")}`);
2689
+ log.info("API key not saved \u2014 run workscribe init again to configure");
2690
+ providerConfigured = false;
2675
2691
  }
2692
+ }
2693
+ return providerConfigured;
2694
+ }
2695
+ function printNextSteps(shell, providerConfigured) {
2696
+ log.plain("");
2697
+ log.plain(import_chalk2.default.bold(" Setup complete!"));
2698
+ log.plain("");
2699
+ if (shell) {
2700
+ log.plain(` Activate capture now:`);
2701
+ if (shell.name === "fish") {
2702
+ log.plain(` Restart your terminal`);
2703
+ log.plain(` ${import_chalk2.default.dim(`or run: source ${shell.rcFile}`)}`);
2704
+ } else {
2705
+ log.plain(` ${import_chalk2.default.cyan(`source ${shell.rcFile}`)}`);
2706
+ log.plain(` Or restart your terminal.`);
2707
+ }
2708
+ log.plain("");
2709
+ }
2710
+ if (providerConfigured) {
2711
+ log.plain(` Then run a few commands and generate your first summary:`);
2712
+ log.plain(` ${import_chalk2.default.cyan("workscribe summary")}`);
2676
2713
  log.plain("");
2714
+ log.plain(` Check capture is working:`);
2715
+ log.plain(` ${import_chalk2.default.cyan("workscribe status")}`);
2716
+ } else {
2717
+ log.plain(` Before generating summaries, configure an AI provider:`);
2718
+ log.plain(` ${import_chalk2.default.cyan("workscribe config set ai.provider anthropic")}`);
2719
+ log.plain(` ${import_chalk2.default.cyan("workscribe config set ai.apiKey <your-key>")}`);
2720
+ log.plain("");
2721
+ log.plain(` Then run:`);
2722
+ log.plain(` ${import_chalk2.default.cyan("workscribe summary")}`);
2723
+ }
2724
+ log.plain("");
2725
+ }
2726
+ function registerInit(program) {
2727
+ program.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
2728
+ log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
2729
+ runDataDirectoriesStep();
2730
+ const config = initDatabaseAndConfig();
2731
+ await runTelemetryStep(config);
2732
+ const shell = await runShellHookStep(program);
2733
+ await runAIToolsStep();
2734
+ await runProfileDetectionStep(config);
2735
+ const providerConfigured = await runAIProviderStep(config);
2736
+ if (!import_fs14.default.existsSync(CONFIG_PATH)) {
2737
+ writeConfig(config);
2738
+ }
2739
+ printNextSteps(shell, providerConfigured);
2677
2740
  });
2678
2741
  }
2679
- var import_fs14, import_path10, import_os5, import_prompts, import_chalk2;
2742
+ var import_fs14, import_path11, import_os5, import_prompts, import_chalk2, TOTAL_STEPS;
2680
2743
  var init_init = __esm({
2681
2744
  "src/cli/init.ts"() {
2682
2745
  "use strict";
2683
2746
  import_fs14 = __toESM(require("fs"));
2684
- import_path10 = __toESM(require("path"));
2747
+ import_path11 = __toESM(require("path"));
2685
2748
  import_os5 = __toESM(require("os"));
2686
2749
  import_prompts = __toESM(require("prompts"));
2687
2750
  import_chalk2 = __toESM(require("chalk"));
@@ -2696,6 +2759,7 @@ var init_init = __esm({
2696
2759
  init_shell_hook();
2697
2760
  init_hooks();
2698
2761
  init_completions();
2762
+ TOTAL_STEPS = 5;
2699
2763
  }
2700
2764
  });
2701
2765
 
@@ -3153,7 +3217,7 @@ function registerSummary(program) {
3153
3217
  return;
3154
3218
  }
3155
3219
  const cacheKey = isoWeekKey(today);
3156
- const cacheFile = import_path11.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
3220
+ const cacheFile = import_path12.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
3157
3221
  if (!opts.fresh && import_fs16.default.existsSync(cacheFile)) {
3158
3222
  const cached = import_fs16.default.readFileSync(cacheFile, "utf-8");
3159
3223
  log.plain("");
@@ -3288,14 +3352,14 @@ ${bullets.trim()}`;
3288
3352
  await handleWebhook(opts, config, fullSummary);
3289
3353
  });
3290
3354
  }
3291
- var import_chalk6, import_fs16, import_os6, import_path11;
3355
+ var import_chalk6, import_fs16, import_os6, import_path12;
3292
3356
  var init_summary = __esm({
3293
3357
  "src/cli/summary.ts"() {
3294
3358
  "use strict";
3295
3359
  import_chalk6 = __toESM(require("chalk"));
3296
3360
  import_fs16 = __toESM(require("fs"));
3297
3361
  import_os6 = __toESM(require("os"));
3298
- import_path11 = __toESM(require("path"));
3362
+ import_path12 = __toESM(require("path"));
3299
3363
  init_detector();
3300
3364
  init_router();
3301
3365
  init_config();
@@ -3365,7 +3429,7 @@ function registerExport(program) {
3365
3429
  const payload = { date, sessions: sessionPayloads };
3366
3430
  log.info(`Generating summary via ${config.ai.provider}\u2026`);
3367
3431
  const summary = await provider.summarize(payload);
3368
- const outputPath = opts.output ?? import_path12.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
3432
+ const outputPath = opts.output ?? import_path13.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
3369
3433
  if (!import_fs17.default.existsSync(EXPORTS_DIR)) import_fs17.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
3370
3434
  import_fs17.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
3371
3435
 
@@ -3374,12 +3438,12 @@ ${summary}
3374
3438
  log.success(`Saved: ${outputPath}`);
3375
3439
  });
3376
3440
  }
3377
- var import_fs17, import_path12;
3441
+ var import_fs17, import_path13;
3378
3442
  var init_export = __esm({
3379
3443
  "src/cli/export.ts"() {
3380
3444
  "use strict";
3381
3445
  import_fs17 = __toESM(require("fs"));
3382
- import_path12 = __toESM(require("path"));
3446
+ import_path13 = __toESM(require("path"));
3383
3447
  init_detector();
3384
3448
  init_router();
3385
3449
  init_config();
@@ -3587,10 +3651,10 @@ var init_clear = __esm({
3587
3651
  // src/cli/uninstall.ts
3588
3652
  function detectShellRcFiles() {
3589
3653
  const candidates = [
3590
- { name: "zsh", rcFile: import_path13.default.join(import_os7.default.homedir(), ".zshrc") },
3591
- { name: "bash", rcFile: import_path13.default.join(import_os7.default.homedir(), ".bashrc") },
3592
- { name: "bash", rcFile: import_path13.default.join(import_os7.default.homedir(), ".bash_profile") },
3593
- { name: "fish", rcFile: import_path13.default.join(import_os7.default.homedir(), ".config", "fish", "conf.d", "workscribe.fish") }
3654
+ { name: "zsh", rcFile: import_path14.default.join(import_os7.default.homedir(), ".zshrc") },
3655
+ { name: "bash", rcFile: import_path14.default.join(import_os7.default.homedir(), ".bashrc") },
3656
+ { name: "bash", rcFile: import_path14.default.join(import_os7.default.homedir(), ".bash_profile") },
3657
+ { name: "fish", rcFile: import_path14.default.join(import_os7.default.homedir(), ".config", "fish", "conf.d", "workscribe.fish") }
3594
3658
  ];
3595
3659
  return candidates.filter((candidate) => import_fs19.default.existsSync(candidate.rcFile) && isHookInstalled(candidate.rcFile));
3596
3660
  }
@@ -3640,12 +3704,12 @@ function registerUninstall(program) {
3640
3704
  log.plain("");
3641
3705
  });
3642
3706
  }
3643
- var import_fs19, import_path13, import_os7, import_prompts3, import_chalk9;
3707
+ var import_fs19, import_path14, import_os7, import_prompts3, import_chalk9;
3644
3708
  var init_uninstall = __esm({
3645
3709
  "src/cli/uninstall.ts"() {
3646
3710
  "use strict";
3647
3711
  import_fs19 = __toESM(require("fs"));
3648
- import_path13 = __toESM(require("path"));
3712
+ import_path14 = __toESM(require("path"));
3649
3713
  import_os7 = __toESM(require("os"));
3650
3714
  import_prompts3 = __toESM(require("prompts"));
3651
3715
  import_chalk9 = __toESM(require("chalk"));
@@ -3699,7 +3763,7 @@ function registerProfile(program) {
3699
3763
  log.plain(import_chalk10.default.dim(" " + "\u2500".repeat(60)));
3700
3764
  for (const entry of entries) {
3701
3765
  const badge = activeNames.has(entry.name) ? import_chalk10.default.green(" [active]") : "";
3702
- const installed = import_fs20.default.existsSync(import_path14.default.join(PROFILES_DIR, `${entry.name}.json`)) ? import_chalk10.default.dim(" installed") : "";
3766
+ const installed = import_fs20.default.existsSync(import_path15.default.join(PROFILES_DIR, `${entry.name}.json`)) ? import_chalk10.default.dim(" installed") : "";
3703
3767
  log.plain(` ${import_chalk10.default.cyan(entry.name.padEnd(18))} ${entry.displayName.padEnd(28)}${badge}${installed}`);
3704
3768
  if (entry.description) log.plain(` ${import_chalk10.default.dim(" ".repeat(18) + entry.description)}`);
3705
3769
  }
@@ -3735,7 +3799,7 @@ function registerProfile(program) {
3735
3799
  continue;
3736
3800
  }
3737
3801
  activeNames.delete(name);
3738
- const cachePath = import_path14.default.join(PROFILES_DIR, `${name}.json`);
3802
+ const cachePath = import_path15.default.join(PROFILES_DIR, `${name}.json`);
3739
3803
  if (import_fs20.default.existsSync(cachePath)) import_fs20.default.unlinkSync(cachePath);
3740
3804
  log.success(`Profile "${name}" removed`);
3741
3805
  }
@@ -3863,12 +3927,12 @@ function registerProfile(program) {
3863
3927
  }
3864
3928
  });
3865
3929
  }
3866
- var import_fs20, import_path14, import_chalk10, MIN_SUGGEST_COUNT;
3930
+ var import_fs20, import_path15, import_chalk10, MIN_SUGGEST_COUNT;
3867
3931
  var init_profile = __esm({
3868
3932
  "src/cli/profile.ts"() {
3869
3933
  "use strict";
3870
3934
  import_fs20 = __toESM(require("fs"));
3871
- import_path14 = __toESM(require("path"));
3935
+ import_path15 = __toESM(require("path"));
3872
3936
  import_chalk10 = __toESM(require("chalk"));
3873
3937
  init_config();
3874
3938
  init_paths();
@@ -3981,7 +4045,7 @@ function registerWeek(program) {
3981
4045
  return;
3982
4046
  }
3983
4047
  const cacheKey = isoWeekKey(today);
3984
- const cacheFile = import_path15.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
4048
+ const cacheFile = import_path16.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
3985
4049
  if (!opts.fresh && import_fs21.default.existsSync(cacheFile)) {
3986
4050
  const cached = import_fs21.default.readFileSync(cacheFile, "utf-8");
3987
4051
  log.plain("");
@@ -4008,13 +4072,13 @@ function registerWeek(program) {
4008
4072
  }
4009
4073
  });
4010
4074
  }
4011
- var import_chalk11, import_fs21, import_path15, import_os8;
4075
+ var import_chalk11, import_fs21, import_path16, import_os8;
4012
4076
  var init_week = __esm({
4013
4077
  "src/cli/week.ts"() {
4014
4078
  "use strict";
4015
4079
  import_chalk11 = __toESM(require("chalk"));
4016
4080
  import_fs21 = __toESM(require("fs"));
4017
- import_path15 = __toESM(require("path"));
4081
+ import_path16 = __toESM(require("path"));
4018
4082
  import_os8 = __toESM(require("os"));
4019
4083
  init_detector();
4020
4084
  init_router();
@@ -4048,9 +4112,9 @@ function redactConfig() {
4048
4112
  function registerBugReport(program) {
4049
4113
  program.command("bug-report").description("Generate a diagnostic report for filing a bug").action(() => {
4050
4114
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4051
- const reportPath = import_path16.default.join(WORKSCRIBE_DIR, `bug-report-${timestamp}.txt`);
4115
+ const reportPath = import_path17.default.join(WORKSCRIBE_DIR, `bug-report-${timestamp}.txt`);
4052
4116
  const systemInfo = [
4053
- `Workscribe version : ${"1.14.2"}`,
4117
+ `Workscribe version : ${"1.15.1"}`,
4054
4118
  `Node.js version : ${process.version}`,
4055
4119
  `Platform : ${process.platform}`,
4056
4120
  `Architecture : ${process.arch}`,
@@ -4081,8 +4145,8 @@ function registerBugReport(program) {
4081
4145
  process.exit(1);
4082
4146
  }
4083
4147
  const params = new URLSearchParams({
4084
- title: `Bug report \u2014 workscribe ${"1.14.2"}`,
4085
- body: `<!-- Attach ${import_path16.default.basename(reportPath)} or paste the relevant sections below -->
4148
+ title: `Bug report \u2014 workscribe ${"1.15.1"}`,
4149
+ body: `<!-- Attach ${import_path17.default.basename(reportPath)} or paste the relevant sections below -->
4086
4150
 
4087
4151
  **Describe the issue:**
4088
4152
 
@@ -4099,13 +4163,13 @@ function registerBugReport(program) {
4099
4163
  log.plain("");
4100
4164
  });
4101
4165
  }
4102
- var import_fs22, import_os9, import_path16, import_chalk12, GITHUB_ISSUES_URL, DEBUG_LOG_TAIL_LINES;
4166
+ var import_fs22, import_os9, import_path17, import_chalk12, GITHUB_ISSUES_URL, DEBUG_LOG_TAIL_LINES;
4103
4167
  var init_bug_report = __esm({
4104
4168
  "src/cli/bug-report.ts"() {
4105
4169
  "use strict";
4106
4170
  import_fs22 = __toESM(require("fs"));
4107
4171
  import_os9 = __toESM(require("os"));
4108
- import_path16 = __toESM(require("path"));
4172
+ import_path17 = __toESM(require("path"));
4109
4173
  import_chalk12 = __toESM(require("chalk"));
4110
4174
  init_paths();
4111
4175
  init_config();
@@ -4210,7 +4274,7 @@ function printSessionContext(session, heading) {
4210
4274
  function registerContext(program) {
4211
4275
  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) => {
4212
4276
  if (opts.file) {
4213
- const normalizedFile = import_path17.default.normalize(opts.file);
4277
+ const normalizedFile = import_path18.default.normalize(opts.file);
4214
4278
  const session = getLastSessionTouchingFile(normalizedFile);
4215
4279
  if (!session) {
4216
4280
  if (opts.json) log.plain(JSON.stringify(null));
@@ -4221,7 +4285,7 @@ function registerContext(program) {
4221
4285
  log.plain(JSON.stringify(toContextJson(session)));
4222
4286
  return;
4223
4287
  }
4224
- printSessionContext(session, `Last session touching ${import_path17.default.basename(normalizedFile)}`);
4288
+ printSessionContext(session, `Last session touching ${import_path18.default.basename(normalizedFile)}`);
4225
4289
  return;
4226
4290
  }
4227
4291
  const { repoName } = getGitContext(process.cwd(), null, readConfig().git.qualifyRepoName);
@@ -4243,11 +4307,11 @@ function registerContext(program) {
4243
4307
  printSessionContext(sessions[0], `Last session on ${repoName}`);
4244
4308
  });
4245
4309
  }
4246
- var import_path17, import_chalk14;
4310
+ var import_path18, import_chalk14;
4247
4311
  var init_context = __esm({
4248
4312
  "src/cli/context.ts"() {
4249
4313
  "use strict";
4250
- import_path17 = __toESM(require("path"));
4314
+ import_path18 = __toESM(require("path"));
4251
4315
  import_chalk14 = __toESM(require("chalk"));
4252
4316
  init_db();
4253
4317
  init_git();
@@ -4265,9 +4329,10 @@ function readCache() {
4265
4329
  return null;
4266
4330
  }
4267
4331
  }
4268
- function writeCache(cache) {
4332
+ function writeCache(updates) {
4269
4333
  try {
4270
- import_fs23.default.writeFileSync(UPDATE_CHECK_PATH, JSON.stringify(cache), { mode: 384 });
4334
+ const current = readCache() ?? {};
4335
+ import_fs23.default.writeFileSync(UPDATE_CHECK_PATH, JSON.stringify({ ...current, ...updates }), { mode: 384 });
4271
4336
  } catch {
4272
4337
  }
4273
4338
  }
@@ -4280,7 +4345,7 @@ function isNewer(latest, current) {
4280
4345
  return latestPatch > currentPatch;
4281
4346
  }
4282
4347
  function checkForUpdate() {
4283
- const current = "1.14.2";
4348
+ const current = "1.15.1";
4284
4349
  const cache = readCache();
4285
4350
  if (cache && isNewer(cache.latestVersion, current)) {
4286
4351
  process.stderr.write(
@@ -4300,12 +4365,35 @@ function checkForUpdate() {
4300
4365
  });
4301
4366
  }
4302
4367
  }
4303
- var import_fs23, CHECK_INTERVAL_MS;
4368
+ function buildTelemetryPayload(config) {
4369
+ return {
4370
+ installId: config.telemetry.installId,
4371
+ cliVersion: "1.15.1",
4372
+ platform: import_os10.default.platform(),
4373
+ nodeVersion: process.version
4374
+ };
4375
+ }
4376
+ function pingTelemetry() {
4377
+ const config = readConfig();
4378
+ if (!config.telemetry.enabled || !config.telemetry.installId) return;
4379
+ const cache = readCache();
4380
+ const now = Date.now();
4381
+ if (cache?.lastTelemetryPing && now - cache.lastTelemetryPing <= CHECK_INTERVAL_MS) return;
4382
+ fetch("https://workscribe.co/api/telemetry/ping", {
4383
+ method: "POST",
4384
+ headers: { "Content-Type": "application/json" },
4385
+ body: JSON.stringify(buildTelemetryPayload(config))
4386
+ }).then(() => writeCache({ lastTelemetryPing: now })).catch(() => {
4387
+ });
4388
+ }
4389
+ var import_fs23, import_os10, CHECK_INTERVAL_MS;
4304
4390
  var init_update_check = __esm({
4305
4391
  "src/utils/update-check.ts"() {
4306
4392
  "use strict";
4307
4393
  import_fs23 = __toESM(require("fs"));
4394
+ import_os10 = __toESM(require("os"));
4308
4395
  init_paths();
4396
+ init_config();
4309
4397
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
4310
4398
  }
4311
4399
  });
@@ -4336,9 +4424,17 @@ var require_main = __commonJS({
4336
4424
  init_alerts3();
4337
4425
  init_context();
4338
4426
  init_update_check();
4339
- checkForUpdate();
4427
+ init_config();
4428
+ var invokedCommand = process.argv[2];
4429
+ if (shouldRunStartupChecks(invokedCommand)) {
4430
+ checkForUpdate();
4431
+ if (invokedCommand !== "init") {
4432
+ ensureTelemetryFallback();
4433
+ }
4434
+ pingTelemetry();
4435
+ }
4340
4436
  var program = new import_commander.Command();
4341
- program.name("workscribe").description("Automatic developer work journal").version("1.14.2");
4437
+ program.name("workscribe").description("Automatic developer work journal").version("1.15.1");
4342
4438
  registerCapture(program);
4343
4439
  registerHook(program);
4344
4440
  registerInit(program);