@workscribe/cli 1.14.1 → 1.15.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
@@ -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
  }
@@ -2103,7 +2125,7 @@ ${commandList}
2103
2125
  ;;
2104
2126
  esac
2105
2127
  }
2106
- _workscribe`;
2128
+ compdef _workscribe workscribe`;
2107
2129
  }
2108
2130
  function fishCompletion(commands) {
2109
2131
  const lines = [
@@ -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;
@@ -2185,10 +2207,10 @@ function injectBinaryPath(hookContent, binaryPath, nodePath) {
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
- return import_fs12.default.readFileSync(shell.rcFile, "utf-8").includes("workscribe completions");
2213
+ return import_fs12.default.readFileSync(shell.rcFile, "utf-8").includes(COMPLETIONS_MARKER);
2192
2214
  } catch {
2193
2215
  return false;
2194
2216
  }
@@ -2198,37 +2220,40 @@ 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
- const sourceLine = shell.name === "zsh" ? `
2207
- # Workscribe completions
2228
+ if (!isCompletionsInstalled(shell)) {
2229
+ const sourceLine = shell.name === "zsh" ? `
2230
+ ${COMPLETIONS_MARKER}
2208
2231
  autoload -Uz compinit && compinit
2209
2232
  source "${cacheFile}"
2210
2233
  ` : `
2211
- # Workscribe completions
2234
+ ${COMPLETIONS_MARKER}
2212
2235
  source "${cacheFile}"
2213
2236
  `;
2214
- import_fs12.default.appendFileSync(shell.rcFile, sourceLine);
2237
+ import_fs12.default.appendFileSync(shell.rcFile, sourceLine);
2238
+ }
2215
2239
  }
2216
2240
  }
2217
- var import_fs12, import_path8, import_os3, import_child_process3, FISH_COMPLETIONS_DIR, HOOK_START, HOOK_END, HOOK_PREAMBLE;
2241
+ var import_fs12, import_path9, import_os3, import_child_process3, FISH_COMPLETIONS_DIR, HOOK_START, HOOK_END, HOOK_PREAMBLE, COMPLETIONS_MARKER;
2218
2242
  var init_shell_hook = __esm({
2219
2243
  "src/capture/shell-hook.ts"() {
2220
2244
  "use strict";
2221
2245
  import_fs12 = __toESM(require("fs"));
2222
- import_path8 = __toESM(require("path"));
2246
+ import_path9 = __toESM(require("path"));
2223
2247
  import_os3 = __toESM(require("os"));
2224
2248
  import_child_process3 = require("child_process");
2225
2249
  init_logger();
2226
2250
  init_completion_scripts();
2227
2251
  init_paths();
2228
- 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");
2229
2253
  HOOK_START = "# workscribe:hook:start";
2230
2254
  HOOK_END = "# workscribe:hook:end";
2231
2255
  HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
2256
+ COMPLETIONS_MARKER = "# Workscribe completions";
2232
2257
  }
2233
2258
  });
2234
2259
 
@@ -2308,17 +2333,17 @@ function checkHookHealth() {
2308
2333
  }
2309
2334
  return { installed: true, pinned: false, confirmedBroken };
2310
2335
  }
2311
- 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;
2312
2337
  var init_claude_code = __esm({
2313
2338
  "src/capture/hooks/claude-code.ts"() {
2314
2339
  "use strict";
2315
2340
  import_fs13 = __toESM(require("fs"));
2316
- import_path9 = __toESM(require("path"));
2341
+ import_path10 = __toESM(require("path"));
2317
2342
  import_os4 = __toESM(require("os"));
2318
2343
  import_child_process5 = require("child_process");
2319
2344
  init_utils();
2320
2345
  init_shell_hook();
2321
- 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");
2322
2347
  HOOK_MARKER = "_hook --source claude-code";
2323
2348
  claudeCodeIntegration = {
2324
2349
  displayName: "Claude Code",
@@ -2362,7 +2387,7 @@ var init_claude_code = __esm({
2362
2387
  }
2363
2388
  ]
2364
2389
  };
2365
- 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 });
2366
2391
  import_fs13.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
2367
2392
  }
2368
2393
  };
@@ -2382,6 +2407,10 @@ var init_hooks = __esm({
2382
2407
  });
2383
2408
 
2384
2409
  // src/cli/completions.ts
2410
+ function shouldRunStartupChecks(invokedCommand) {
2411
+ if (invokedCommand === void 0) return true;
2412
+ return !HIDDEN_COMMANDS.has(invokedCommand);
2413
+ }
2385
2414
  function getPublicCommands(program) {
2386
2415
  return program.commands.filter((command) => !HIDDEN_COMMANDS.has(command.name())).map((command) => ({ name: command.name(), description: command.description() }));
2387
2416
  }
@@ -2411,11 +2440,11 @@ var init_completions = __esm({
2411
2440
  // src/cli/init.ts
2412
2441
  function detectShell() {
2413
2442
  const shell = process.env.SHELL ?? "";
2414
- if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path10.default.join(import_os5.default.homedir(), ".zshrc") };
2415
- 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") };
2416
2445
  if (shell.includes("fish")) {
2417
- const confDir = import_path10.default.join(import_os5.default.homedir(), ".config", "fish", "conf.d");
2418
- 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") };
2419
2448
  }
2420
2449
  return null;
2421
2450
  }
@@ -2423,262 +2452,299 @@ function step(current, total, label) {
2423
2452
  log.plain(import_chalk2.default.bold(`
2424
2453
  [${current}/${total}] ${label}`));
2425
2454
  }
2426
- function registerInit(program) {
2427
- program.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
2428
- const TOTAL_STEPS = 5;
2429
- log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
2430
- step(1, TOTAL_STEPS, "Creating data directories");
2431
- for (const dir of REQUIRED_DIRS) {
2432
- if (!import_fs14.default.existsSync(dir)) {
2433
- import_fs14.default.mkdirSync(dir, { recursive: true, mode: 448 });
2434
- log.success(`Created ${dir}`);
2435
- } else {
2436
- try {
2437
- import_fs14.default.chmodSync(dir, 448);
2438
- } catch {
2439
- }
2440
- log.info(`${dir} already exists`);
2441
- }
2442
- }
2443
- if (!import_fs14.default.existsSync(PROFILES_DIR)) {
2444
- import_fs14.default.mkdirSync(PROFILES_DIR, { recursive: true, mode: 448 });
2445
- }
2446
- step(2, TOTAL_STEPS, "Initialising database");
2447
- getDb();
2448
- log.success("Database ready");
2449
- step(3, TOTAL_STEPS, "Installing shell hook");
2450
- const shell = detectShell();
2451
- if (!shell) {
2452
- log.warn("Could not detect shell. Supported: zsh, bash, fish");
2453
- log.plain(
2454
- ` 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")}`
2455
- );
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}`);
2456
2461
  } else {
2457
- const hookFile = import_path10.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
2458
- if (!import_fs14.default.existsSync(hookFile)) {
2459
- log.warn(`Hook template not found at ${hookFile}`);
2460
- } else {
2461
- const hookContent = import_fs14.default.readFileSync(hookFile, "utf-8");
2462
- const binaryPath = resolveWorkscribeBinary();
2463
- const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath, process.execPath) : hookContent;
2464
- if (shell.name === "fish") {
2465
- const confDir = import_path10.default.dirname(shell.rcFile);
2466
- if (!import_fs14.default.existsSync(confDir)) import_fs14.default.mkdirSync(confDir, { recursive: true });
2467
- }
2468
- if (!import_fs14.default.existsSync(shell.rcFile)) import_fs14.default.writeFileSync(shell.rcFile, "");
2469
- if (!isHookInstalled(shell.rcFile)) {
2470
- log.plain("");
2471
- log.plain(import_chalk2.default.dim(" Workscribe adds a lightweight hook to your shell that captures each"));
2472
- log.plain(import_chalk2.default.dim(" command you run \u2014 the text, exit code, duration, and working directory."));
2473
- log.plain(import_chalk2.default.dim(" It runs in the background and adds no latency to your terminal."));
2474
- log.plain(import_chalk2.default.dim(" Credentials and secrets are redacted before anything is stored."));
2475
- log.plain("");
2476
- installHook(shell.rcFile, hookWithPath);
2477
- log.success(`Shell hook installed in ${shell.rcFile}`);
2478
- } else if (isHookOutdated(shell.rcFile, hookWithPath)) {
2479
- const { update } = await (0, import_prompts.default)({
2480
- type: "confirm",
2481
- name: "update",
2482
- message: `Shell hook in ${shell.rcFile} is outdated. Update?`,
2483
- initial: true
2484
- });
2485
- if (update) {
2486
- updateHook(shell.rcFile, hookWithPath);
2487
- log.success(`Shell hook updated in ${shell.rcFile}`);
2488
- } else {
2489
- log.info("Shell hook update skipped");
2490
- }
2491
- } else {
2492
- log.info(`Shell hook up to date in ${shell.rcFile}`);
2493
- }
2494
- if (isCompletionsInstalled(shell)) {
2495
- log.info("Shell completions already installed");
2496
- } else {
2497
- const { installComps } = await (0, import_prompts.default)({
2498
- type: "confirm",
2499
- name: "installComps",
2500
- message: "Install shell completions (tab-complete workscribe commands)?",
2501
- initial: true
2502
- });
2503
- if (installComps) {
2504
- try {
2505
- installCompletions(shell, getPublicCommands(program));
2506
- log.success("Shell completions installed");
2507
- } catch (error) {
2508
- log.warn(`Completions install failed: ${error.message}`);
2509
- }
2510
- }
2511
- }
2462
+ try {
2463
+ import_fs14.default.chmodSync(dir, 448);
2464
+ } catch {
2512
2465
  }
2466
+ log.info(`${dir} already exists`);
2513
2467
  }
2514
- step(4, TOTAL_STEPS, "Detecting AI coding tools");
2515
- const installedTools = loadAIToolRegistry().filter(
2516
- (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")}`
2517
2503
  );
2518
- if (installedTools.length === 0) {
2519
- 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}`);
2520
2538
  } else {
2521
- for (const tool of installedTools) {
2522
- const integration = AI_HOOK_INTEGRATIONS[tool.hook];
2523
- if (integration.isHookInstalled()) {
2524
- log.info(`${integration.displayName} session hook already installed`);
2525
- } else {
2526
- const { install } = await (0, import_prompts.default)({
2527
- type: "confirm",
2528
- name: "install",
2529
- message: `${integration.displayName} detected. Install session capture hook?`,
2530
- initial: false
2531
- });
2532
- if (install) {
2533
- integration.install();
2534
- log.success(`${integration.displayName} session hook installed`);
2535
- } else {
2536
- log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
2537
- }
2538
- }
2539
- }
2539
+ log.info("Shell hook update skipped");
2540
2540
  }
2541
- const config = readConfig();
2542
- try {
2543
- await cacheManifest();
2544
- const detected = detectProfilesForDirectory(process.cwd());
2545
- const alreadyActive = new Set(config.capture.profiles);
2546
- const toPrompt = detected.filter((entry) => !alreadyActive.has(entry.name));
2547
- if (toPrompt.length > 0) {
2548
- log.plain("");
2549
- for (const entry of toPrompt) {
2550
- const { install } = await (0, import_prompts.default)({
2551
- type: "confirm",
2552
- name: "install",
2553
- message: `${entry.displayName} project detected. Enable the ${entry.displayName} profile?`,
2554
- initial: true
2555
- });
2556
- if (install) {
2557
- await installProfile(entry.name);
2558
- config.capture.profiles = [.../* @__PURE__ */ new Set([...config.capture.profiles, entry.name])];
2559
- log.success(`${entry.displayName} profile enabled`);
2560
- }
2561
- }
2562
- writeConfig(config);
2563
- } else if (detected.length === 0) {
2564
- 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}`);
2565
2559
  }
2566
- } catch {
2567
- log.info("Could not reach profile registry \u2014 run workscribe profile detect after connecting");
2568
2560
  }
2569
- step(5, TOTAL_STEPS, "Configuring AI provider");
2570
- let providerConfigured = !!config.ai.provider;
2571
- if (config.ai.provider) {
2572
- 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`);
2573
2577
  } else {
2574
- log.plain("");
2575
- const { provider } = await (0, import_prompts.default)({
2576
- type: "select",
2577
- name: "provider",
2578
- message: "Which AI provider do you want to use?",
2579
- choices: [
2580
- { title: "Anthropic (Claude) \u2014 requires API key", value: "anthropic" },
2581
- { title: "OpenAI \u2014 requires API key", value: "openai" },
2582
- { title: "Ollama (local) \u2014 free, runs on your machine", value: "ollama" },
2583
- { title: "Other (OpenAI-compatible) \u2014 Groq, Together, Mistral\u2026", value: "openai-compatible" },
2584
- { title: "Skip \u2014 configure later", value: "skip" }
2585
- ]
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
2586
2583
  });
2587
- if (!provider || provider === "skip") {
2588
- providerConfigured = false;
2589
- 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`);
2590
2587
  } else {
2591
- config.ai.provider = provider;
2592
- if (provider === "ollama") {
2593
- log.plain(`
2594
- ${import_chalk2.default.dim("Make sure Ollama is running: ollama serve")}`);
2595
- log.plain(
2596
- ` ${import_chalk2.default.dim("Default model: llama3 (change with: workscribe config set ai.model <model>)")}
2597
- `
2598
- );
2599
- } else {
2600
- const { apiKey } = await (0, import_prompts.default)({
2601
- type: "password",
2602
- name: "apiKey",
2603
- message: `Enter your ${provider === "openai-compatible" ? "" : provider + " "}API key:`
2604
- });
2605
- if (apiKey) config.ai.apiKey = apiKey;
2606
- if (provider === "openai-compatible") {
2607
- const { baseUrl } = await (0, import_prompts.default)({
2608
- type: "text",
2609
- name: "baseUrl",
2610
- message: "Enter the base URL (e.g. https://api.groq.com/openai/v1):"
2611
- });
2612
- if (baseUrl) config.ai.baseUrl = baseUrl;
2613
- }
2614
- }
2615
- log.plain("");
2616
- log.info(`Validating ${import_chalk2.default.cyan(provider)} connection\u2026`);
2617
- try {
2618
- const testProvider = getProvider(config);
2619
- await testProvider.validate();
2620
- log.success("API key verified");
2621
- writeConfig(config);
2622
- providerConfigured = true;
2623
- } catch (error) {
2624
- log.warn(`Validation failed: ${error.message}`);
2625
- const { retry } = await (0, import_prompts.default)({
2626
- type: "confirm",
2627
- name: "retry",
2628
- message: "Save anyway and configure later?",
2629
- initial: false
2630
- });
2631
- if (retry) {
2632
- writeConfig(config);
2633
- providerConfigured = false;
2634
- log.info("Config saved \u2014 run workscribe config set ai.apiKey <key> to update");
2635
- } else {
2636
- log.info("API key not saved \u2014 run workscribe init again to configure");
2637
- providerConfigured = false;
2638
- }
2639
- }
2588
+ log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
2640
2589
  }
2641
2590
  }
2642
- 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
+ }
2643
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");
2644
2617
  }
2645
- log.plain("");
2646
- log.plain(import_chalk2.default.bold(" Setup complete!"));
2647
- log.plain("");
2648
- if (shell) {
2649
- log.plain(` Activate capture now:`);
2650
- if (shell.name === "fish") {
2651
- log.plain(` Restart your terminal`);
2652
- log.plain(` ${import_chalk2.default.dim(`or run: source ${shell.rcFile}`)}`);
2653
- } else {
2654
- log.plain(` ${import_chalk2.default.cyan(`source ${shell.rcFile}`)}`);
2655
- log.plain(` Or restart your terminal.`);
2656
- }
2657
- 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;
2658
2666
  }
2659
- if (providerConfigured) {
2660
- log.plain(` Then run a few commands and generate your first summary:`);
2661
- log.plain(` ${import_chalk2.default.cyan("workscribe summary")}`);
2662
- log.plain("");
2663
- log.plain(` Check capture is working:`);
2664
- 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");
2665
2688
  } else {
2666
- log.plain(` Before generating summaries, configure an AI provider:`);
2667
- log.plain(` ${import_chalk2.default.cyan("workscribe config set ai.provider anthropic")}`);
2668
- log.plain(` ${import_chalk2.default.cyan("workscribe config set ai.apiKey <your-key>")}`);
2669
- log.plain("");
2670
- log.plain(` Then run:`);
2671
- 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;
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.`);
2672
2707
  }
2673
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")}`);
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);
2674
2740
  });
2675
2741
  }
2676
- 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;
2677
2743
  var init_init = __esm({
2678
2744
  "src/cli/init.ts"() {
2679
2745
  "use strict";
2680
2746
  import_fs14 = __toESM(require("fs"));
2681
- import_path10 = __toESM(require("path"));
2747
+ import_path11 = __toESM(require("path"));
2682
2748
  import_os5 = __toESM(require("os"));
2683
2749
  import_prompts = __toESM(require("prompts"));
2684
2750
  import_chalk2 = __toESM(require("chalk"));
@@ -2693,6 +2759,7 @@ var init_init = __esm({
2693
2759
  init_shell_hook();
2694
2760
  init_hooks();
2695
2761
  init_completions();
2762
+ TOTAL_STEPS = 5;
2696
2763
  }
2697
2764
  });
2698
2765
 
@@ -3150,7 +3217,7 @@ function registerSummary(program) {
3150
3217
  return;
3151
3218
  }
3152
3219
  const cacheKey = isoWeekKey(today);
3153
- const cacheFile = import_path11.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
3220
+ const cacheFile = import_path12.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
3154
3221
  if (!opts.fresh && import_fs16.default.existsSync(cacheFile)) {
3155
3222
  const cached = import_fs16.default.readFileSync(cacheFile, "utf-8");
3156
3223
  log.plain("");
@@ -3285,14 +3352,14 @@ ${bullets.trim()}`;
3285
3352
  await handleWebhook(opts, config, fullSummary);
3286
3353
  });
3287
3354
  }
3288
- var import_chalk6, import_fs16, import_os6, import_path11;
3355
+ var import_chalk6, import_fs16, import_os6, import_path12;
3289
3356
  var init_summary = __esm({
3290
3357
  "src/cli/summary.ts"() {
3291
3358
  "use strict";
3292
3359
  import_chalk6 = __toESM(require("chalk"));
3293
3360
  import_fs16 = __toESM(require("fs"));
3294
3361
  import_os6 = __toESM(require("os"));
3295
- import_path11 = __toESM(require("path"));
3362
+ import_path12 = __toESM(require("path"));
3296
3363
  init_detector();
3297
3364
  init_router();
3298
3365
  init_config();
@@ -3362,7 +3429,7 @@ function registerExport(program) {
3362
3429
  const payload = { date, sessions: sessionPayloads };
3363
3430
  log.info(`Generating summary via ${config.ai.provider}\u2026`);
3364
3431
  const summary = await provider.summarize(payload);
3365
- 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`);
3366
3433
  if (!import_fs17.default.existsSync(EXPORTS_DIR)) import_fs17.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
3367
3434
  import_fs17.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
3368
3435
 
@@ -3371,12 +3438,12 @@ ${summary}
3371
3438
  log.success(`Saved: ${outputPath}`);
3372
3439
  });
3373
3440
  }
3374
- var import_fs17, import_path12;
3441
+ var import_fs17, import_path13;
3375
3442
  var init_export = __esm({
3376
3443
  "src/cli/export.ts"() {
3377
3444
  "use strict";
3378
3445
  import_fs17 = __toESM(require("fs"));
3379
- import_path12 = __toESM(require("path"));
3446
+ import_path13 = __toESM(require("path"));
3380
3447
  init_detector();
3381
3448
  init_router();
3382
3449
  init_config();
@@ -3584,10 +3651,10 @@ var init_clear = __esm({
3584
3651
  // src/cli/uninstall.ts
3585
3652
  function detectShellRcFiles() {
3586
3653
  const candidates = [
3587
- { name: "zsh", rcFile: import_path13.default.join(import_os7.default.homedir(), ".zshrc") },
3588
- { name: "bash", rcFile: import_path13.default.join(import_os7.default.homedir(), ".bashrc") },
3589
- { name: "bash", rcFile: import_path13.default.join(import_os7.default.homedir(), ".bash_profile") },
3590
- { 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") }
3591
3658
  ];
3592
3659
  return candidates.filter((candidate) => import_fs19.default.existsSync(candidate.rcFile) && isHookInstalled(candidate.rcFile));
3593
3660
  }
@@ -3637,12 +3704,12 @@ function registerUninstall(program) {
3637
3704
  log.plain("");
3638
3705
  });
3639
3706
  }
3640
- var import_fs19, import_path13, import_os7, import_prompts3, import_chalk9;
3707
+ var import_fs19, import_path14, import_os7, import_prompts3, import_chalk9;
3641
3708
  var init_uninstall = __esm({
3642
3709
  "src/cli/uninstall.ts"() {
3643
3710
  "use strict";
3644
3711
  import_fs19 = __toESM(require("fs"));
3645
- import_path13 = __toESM(require("path"));
3712
+ import_path14 = __toESM(require("path"));
3646
3713
  import_os7 = __toESM(require("os"));
3647
3714
  import_prompts3 = __toESM(require("prompts"));
3648
3715
  import_chalk9 = __toESM(require("chalk"));
@@ -3696,7 +3763,7 @@ function registerProfile(program) {
3696
3763
  log.plain(import_chalk10.default.dim(" " + "\u2500".repeat(60)));
3697
3764
  for (const entry of entries) {
3698
3765
  const badge = activeNames.has(entry.name) ? import_chalk10.default.green(" [active]") : "";
3699
- 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") : "";
3700
3767
  log.plain(` ${import_chalk10.default.cyan(entry.name.padEnd(18))} ${entry.displayName.padEnd(28)}${badge}${installed}`);
3701
3768
  if (entry.description) log.plain(` ${import_chalk10.default.dim(" ".repeat(18) + entry.description)}`);
3702
3769
  }
@@ -3732,7 +3799,7 @@ function registerProfile(program) {
3732
3799
  continue;
3733
3800
  }
3734
3801
  activeNames.delete(name);
3735
- const cachePath = import_path14.default.join(PROFILES_DIR, `${name}.json`);
3802
+ const cachePath = import_path15.default.join(PROFILES_DIR, `${name}.json`);
3736
3803
  if (import_fs20.default.existsSync(cachePath)) import_fs20.default.unlinkSync(cachePath);
3737
3804
  log.success(`Profile "${name}" removed`);
3738
3805
  }
@@ -3860,12 +3927,12 @@ function registerProfile(program) {
3860
3927
  }
3861
3928
  });
3862
3929
  }
3863
- var import_fs20, import_path14, import_chalk10, MIN_SUGGEST_COUNT;
3930
+ var import_fs20, import_path15, import_chalk10, MIN_SUGGEST_COUNT;
3864
3931
  var init_profile = __esm({
3865
3932
  "src/cli/profile.ts"() {
3866
3933
  "use strict";
3867
3934
  import_fs20 = __toESM(require("fs"));
3868
- import_path14 = __toESM(require("path"));
3935
+ import_path15 = __toESM(require("path"));
3869
3936
  import_chalk10 = __toESM(require("chalk"));
3870
3937
  init_config();
3871
3938
  init_paths();
@@ -3978,7 +4045,7 @@ function registerWeek(program) {
3978
4045
  return;
3979
4046
  }
3980
4047
  const cacheKey = isoWeekKey(today);
3981
- const cacheFile = import_path15.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
4048
+ const cacheFile = import_path16.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
3982
4049
  if (!opts.fresh && import_fs21.default.existsSync(cacheFile)) {
3983
4050
  const cached = import_fs21.default.readFileSync(cacheFile, "utf-8");
3984
4051
  log.plain("");
@@ -4005,13 +4072,13 @@ function registerWeek(program) {
4005
4072
  }
4006
4073
  });
4007
4074
  }
4008
- var import_chalk11, import_fs21, import_path15, import_os8;
4075
+ var import_chalk11, import_fs21, import_path16, import_os8;
4009
4076
  var init_week = __esm({
4010
4077
  "src/cli/week.ts"() {
4011
4078
  "use strict";
4012
4079
  import_chalk11 = __toESM(require("chalk"));
4013
4080
  import_fs21 = __toESM(require("fs"));
4014
- import_path15 = __toESM(require("path"));
4081
+ import_path16 = __toESM(require("path"));
4015
4082
  import_os8 = __toESM(require("os"));
4016
4083
  init_detector();
4017
4084
  init_router();
@@ -4045,9 +4112,9 @@ function redactConfig() {
4045
4112
  function registerBugReport(program) {
4046
4113
  program.command("bug-report").description("Generate a diagnostic report for filing a bug").action(() => {
4047
4114
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4048
- 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`);
4049
4116
  const systemInfo = [
4050
- `Workscribe version : ${"1.14.1"}`,
4117
+ `Workscribe version : ${"1.15.0"}`,
4051
4118
  `Node.js version : ${process.version}`,
4052
4119
  `Platform : ${process.platform}`,
4053
4120
  `Architecture : ${process.arch}`,
@@ -4078,8 +4145,8 @@ function registerBugReport(program) {
4078
4145
  process.exit(1);
4079
4146
  }
4080
4147
  const params = new URLSearchParams({
4081
- title: `Bug report \u2014 workscribe ${"1.14.1"}`,
4082
- body: `<!-- Attach ${import_path16.default.basename(reportPath)} or paste the relevant sections below -->
4148
+ title: `Bug report \u2014 workscribe ${"1.15.0"}`,
4149
+ body: `<!-- Attach ${import_path17.default.basename(reportPath)} or paste the relevant sections below -->
4083
4150
 
4084
4151
  **Describe the issue:**
4085
4152
 
@@ -4096,13 +4163,13 @@ function registerBugReport(program) {
4096
4163
  log.plain("");
4097
4164
  });
4098
4165
  }
4099
- 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;
4100
4167
  var init_bug_report = __esm({
4101
4168
  "src/cli/bug-report.ts"() {
4102
4169
  "use strict";
4103
4170
  import_fs22 = __toESM(require("fs"));
4104
4171
  import_os9 = __toESM(require("os"));
4105
- import_path16 = __toESM(require("path"));
4172
+ import_path17 = __toESM(require("path"));
4106
4173
  import_chalk12 = __toESM(require("chalk"));
4107
4174
  init_paths();
4108
4175
  init_config();
@@ -4207,7 +4274,7 @@ function printSessionContext(session, heading) {
4207
4274
  function registerContext(program) {
4208
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) => {
4209
4276
  if (opts.file) {
4210
- const normalizedFile = import_path17.default.normalize(opts.file);
4277
+ const normalizedFile = import_path18.default.normalize(opts.file);
4211
4278
  const session = getLastSessionTouchingFile(normalizedFile);
4212
4279
  if (!session) {
4213
4280
  if (opts.json) log.plain(JSON.stringify(null));
@@ -4218,7 +4285,7 @@ function registerContext(program) {
4218
4285
  log.plain(JSON.stringify(toContextJson(session)));
4219
4286
  return;
4220
4287
  }
4221
- printSessionContext(session, `Last session touching ${import_path17.default.basename(normalizedFile)}`);
4288
+ printSessionContext(session, `Last session touching ${import_path18.default.basename(normalizedFile)}`);
4222
4289
  return;
4223
4290
  }
4224
4291
  const { repoName } = getGitContext(process.cwd(), null, readConfig().git.qualifyRepoName);
@@ -4240,11 +4307,11 @@ function registerContext(program) {
4240
4307
  printSessionContext(sessions[0], `Last session on ${repoName}`);
4241
4308
  });
4242
4309
  }
4243
- var import_path17, import_chalk14;
4310
+ var import_path18, import_chalk14;
4244
4311
  var init_context = __esm({
4245
4312
  "src/cli/context.ts"() {
4246
4313
  "use strict";
4247
- import_path17 = __toESM(require("path"));
4314
+ import_path18 = __toESM(require("path"));
4248
4315
  import_chalk14 = __toESM(require("chalk"));
4249
4316
  init_db();
4250
4317
  init_git();
@@ -4262,9 +4329,10 @@ function readCache() {
4262
4329
  return null;
4263
4330
  }
4264
4331
  }
4265
- function writeCache(cache) {
4332
+ function writeCache(updates) {
4266
4333
  try {
4267
- 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 });
4268
4336
  } catch {
4269
4337
  }
4270
4338
  }
@@ -4277,7 +4345,7 @@ function isNewer(latest, current) {
4277
4345
  return latestPatch > currentPatch;
4278
4346
  }
4279
4347
  function checkForUpdate() {
4280
- const current = "1.14.1";
4348
+ const current = "1.15.0";
4281
4349
  const cache = readCache();
4282
4350
  if (cache && isNewer(cache.latestVersion, current)) {
4283
4351
  process.stderr.write(
@@ -4297,12 +4365,35 @@ function checkForUpdate() {
4297
4365
  });
4298
4366
  }
4299
4367
  }
4300
- var import_fs23, CHECK_INTERVAL_MS;
4368
+ function buildTelemetryPayload(config) {
4369
+ return {
4370
+ installId: config.telemetry.installId,
4371
+ cliVersion: "1.15.0",
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;
4301
4390
  var init_update_check = __esm({
4302
4391
  "src/utils/update-check.ts"() {
4303
4392
  "use strict";
4304
4393
  import_fs23 = __toESM(require("fs"));
4394
+ import_os10 = __toESM(require("os"));
4305
4395
  init_paths();
4396
+ init_config();
4306
4397
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
4307
4398
  }
4308
4399
  });
@@ -4333,9 +4424,17 @@ var require_main = __commonJS({
4333
4424
  init_alerts3();
4334
4425
  init_context();
4335
4426
  init_update_check();
4336
- 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
+ }
4337
4436
  var program = new import_commander.Command();
4338
- program.name("workscribe").description("Automatic developer work journal").version("1.14.1");
4437
+ program.name("workscribe").description("Automatic developer work journal").version("1.15.0");
4339
4438
  registerCapture(program);
4340
4439
  registerHook(program);
4341
4440
  registerInit(program);