@keepgoingdev/cli 1.1.1 → 1.2.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.
Files changed (2) hide show
  1. package/dist/index.js +461 -114
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -320,7 +320,8 @@ function buildRecentActivity(lastSession, recentSessions, recentCommitMessages)
320
320
  parts.push("1 recent session");
321
321
  }
322
322
  if (lastSession.summary) {
323
- parts.push(`Last: ${lastSession.summary}`);
323
+ const brief = lastSession.summary.length > 120 ? lastSession.summary.slice(0, 117) + "..." : lastSession.summary;
324
+ parts.push(`Last: ${brief}`);
324
325
  }
325
326
  if (lastSession.touchedFiles.length > 0) {
326
327
  parts.push(`${lastSession.touchedFiles.length} files touched`);
@@ -585,9 +586,59 @@ function formatContinueOnPrompt(context, options) {
585
586
  }
586
587
 
587
588
  // ../../packages/shared/src/storage.ts
589
+ import fs2 from "fs";
590
+ import path4 from "path";
591
+ import { randomUUID as randomUUID2, createHash } from "crypto";
592
+
593
+ // ../../packages/shared/src/registry.ts
588
594
  import fs from "fs";
595
+ import os from "os";
589
596
  import path3 from "path";
590
- import { randomUUID as randomUUID2, createHash } from "crypto";
597
+ var KEEPGOING_DIR = path3.join(os.homedir(), ".keepgoing");
598
+ var KNOWN_PROJECTS_FILE = path3.join(KEEPGOING_DIR, "known-projects.json");
599
+ var STALE_PROJECT_MS = 90 * 24 * 60 * 60 * 1e3;
600
+ function readKnownProjects() {
601
+ try {
602
+ if (fs.existsSync(KNOWN_PROJECTS_FILE)) {
603
+ const raw = JSON.parse(fs.readFileSync(KNOWN_PROJECTS_FILE, "utf-8"));
604
+ if (raw && Array.isArray(raw.projects)) {
605
+ return raw;
606
+ }
607
+ }
608
+ } catch {
609
+ }
610
+ return { version: 1, projects: [] };
611
+ }
612
+ function writeKnownProjects(data) {
613
+ if (!fs.existsSync(KEEPGOING_DIR)) {
614
+ fs.mkdirSync(KEEPGOING_DIR, { recursive: true });
615
+ }
616
+ const tmpFile = KNOWN_PROJECTS_FILE + ".tmp";
617
+ fs.writeFileSync(tmpFile, JSON.stringify(data, null, 2) + "\n", "utf-8");
618
+ fs.renameSync(tmpFile, KNOWN_PROJECTS_FILE);
619
+ }
620
+ function registerProject(projectPath, projectName) {
621
+ try {
622
+ const data = readKnownProjects();
623
+ const now = (/* @__PURE__ */ new Date()).toISOString();
624
+ const name = projectName || path3.basename(projectPath);
625
+ const existingIdx = data.projects.findIndex((p) => p.path === projectPath);
626
+ if (existingIdx >= 0) {
627
+ data.projects[existingIdx].lastSeen = now;
628
+ if (projectName) {
629
+ data.projects[existingIdx].name = name;
630
+ }
631
+ } else {
632
+ data.projects.push({ path: projectPath, name, lastSeen: now });
633
+ }
634
+ const cutoff = Date.now() - STALE_PROJECT_MS;
635
+ data.projects = data.projects.filter((p) => new Date(p.lastSeen).getTime() > cutoff);
636
+ writeKnownProjects(data);
637
+ } catch {
638
+ }
639
+ }
640
+
641
+ // ../../packages/shared/src/storage.ts
591
642
  var STORAGE_DIR = ".keepgoing";
592
643
  var META_FILE = "meta.json";
593
644
  var SESSIONS_FILE = "sessions.json";
@@ -609,23 +660,23 @@ var KeepGoingWriter = class {
609
660
  currentTasksFilePath;
610
661
  constructor(workspacePath) {
611
662
  const mainRoot = resolveStorageRoot(workspacePath);
612
- this.storagePath = path3.join(mainRoot, STORAGE_DIR);
613
- this.sessionsFilePath = path3.join(this.storagePath, SESSIONS_FILE);
614
- this.stateFilePath = path3.join(this.storagePath, STATE_FILE);
615
- this.metaFilePath = path3.join(this.storagePath, META_FILE);
616
- this.currentTasksFilePath = path3.join(this.storagePath, CURRENT_TASKS_FILE);
663
+ this.storagePath = path4.join(mainRoot, STORAGE_DIR);
664
+ this.sessionsFilePath = path4.join(this.storagePath, SESSIONS_FILE);
665
+ this.stateFilePath = path4.join(this.storagePath, STATE_FILE);
666
+ this.metaFilePath = path4.join(this.storagePath, META_FILE);
667
+ this.currentTasksFilePath = path4.join(this.storagePath, CURRENT_TASKS_FILE);
617
668
  }
618
669
  ensureDir() {
619
- if (!fs.existsSync(this.storagePath)) {
620
- fs.mkdirSync(this.storagePath, { recursive: true });
670
+ if (!fs2.existsSync(this.storagePath)) {
671
+ fs2.mkdirSync(this.storagePath, { recursive: true });
621
672
  }
622
673
  }
623
674
  saveCheckpoint(checkpoint, projectName) {
624
675
  this.ensureDir();
625
676
  let sessionsData;
626
677
  try {
627
- if (fs.existsSync(this.sessionsFilePath)) {
628
- const raw = JSON.parse(fs.readFileSync(this.sessionsFilePath, "utf-8"));
678
+ if (fs2.existsSync(this.sessionsFilePath)) {
679
+ const raw = JSON.parse(fs2.readFileSync(this.sessionsFilePath, "utf-8"));
629
680
  if (Array.isArray(raw)) {
630
681
  sessionsData = { version: 1, project: projectName, sessions: raw };
631
682
  } else {
@@ -643,20 +694,22 @@ var KeepGoingWriter = class {
643
694
  if (sessionsData.sessions.length > MAX_SESSIONS) {
644
695
  sessionsData.sessions = sessionsData.sessions.slice(-MAX_SESSIONS);
645
696
  }
646
- fs.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), "utf-8");
697
+ fs2.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), "utf-8");
647
698
  const state = {
648
699
  lastSessionId: checkpoint.id,
649
700
  lastKnownBranch: checkpoint.gitBranch,
650
701
  lastActivityAt: checkpoint.timestamp
651
702
  };
652
- fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), "utf-8");
703
+ fs2.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), "utf-8");
653
704
  this.updateMeta(checkpoint.timestamp);
705
+ const mainRoot = resolveStorageRoot(this.storagePath);
706
+ registerProject(mainRoot, projectName);
654
707
  }
655
708
  updateMeta(timestamp) {
656
709
  let meta;
657
710
  try {
658
- if (fs.existsSync(this.metaFilePath)) {
659
- meta = JSON.parse(fs.readFileSync(this.metaFilePath, "utf-8"));
711
+ if (fs2.existsSync(this.metaFilePath)) {
712
+ meta = JSON.parse(fs2.readFileSync(this.metaFilePath, "utf-8"));
660
713
  meta.lastUpdated = timestamp;
661
714
  } else {
662
715
  meta = {
@@ -672,7 +725,7 @@ var KeepGoingWriter = class {
672
725
  lastUpdated: timestamp
673
726
  };
674
727
  }
675
- fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
728
+ fs2.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
676
729
  }
677
730
  // ---------------------------------------------------------------------------
678
731
  // Multi-session API
@@ -680,8 +733,8 @@ var KeepGoingWriter = class {
680
733
  /** Read all current tasks from current-tasks.json. Auto-prunes stale sessions. */
681
734
  readCurrentTasks() {
682
735
  try {
683
- if (fs.existsSync(this.currentTasksFilePath)) {
684
- const raw = JSON.parse(fs.readFileSync(this.currentTasksFilePath, "utf-8"));
736
+ if (fs2.existsSync(this.currentTasksFilePath)) {
737
+ const raw = JSON.parse(fs2.readFileSync(this.currentTasksFilePath, "utf-8"));
685
738
  const tasks = Array.isArray(raw) ? raw : raw.tasks ?? [];
686
739
  return this.pruneStale(tasks);
687
740
  }
@@ -696,6 +749,8 @@ var KeepGoingWriter = class {
696
749
  upsertSession(update) {
697
750
  this.ensureDir();
698
751
  this.upsertSessionCore(update);
752
+ const mainRoot = resolveStorageRoot(this.storagePath);
753
+ registerProject(mainRoot);
699
754
  }
700
755
  /** Core upsert logic: merges the update into current-tasks.json and returns the pruned task list. */
701
756
  upsertSessionCore(update) {
@@ -734,8 +789,8 @@ var KeepGoingWriter = class {
734
789
  // ---------------------------------------------------------------------------
735
790
  readAllTasksRaw() {
736
791
  try {
737
- if (fs.existsSync(this.currentTasksFilePath)) {
738
- const raw = JSON.parse(fs.readFileSync(this.currentTasksFilePath, "utf-8"));
792
+ if (fs2.existsSync(this.currentTasksFilePath)) {
793
+ const raw = JSON.parse(fs2.readFileSync(this.currentTasksFilePath, "utf-8"));
739
794
  return Array.isArray(raw) ? [...raw] : [...raw.tasks ?? []];
740
795
  }
741
796
  } catch {
@@ -747,7 +802,7 @@ var KeepGoingWriter = class {
747
802
  }
748
803
  writeTasksFile(tasks) {
749
804
  const data = { version: 1, tasks };
750
- fs.writeFileSync(this.currentTasksFilePath, JSON.stringify(data, null, 2), "utf-8");
805
+ fs2.writeFileSync(this.currentTasksFilePath, JSON.stringify(data, null, 2), "utf-8");
751
806
  }
752
807
  };
753
808
  function generateSessionId(context) {
@@ -926,35 +981,35 @@ function capitalize(s) {
926
981
  }
927
982
 
928
983
  // ../../packages/shared/src/decisionStorage.ts
929
- import fs3 from "fs";
930
- import path5 from "path";
984
+ import fs4 from "fs";
985
+ import path6 from "path";
931
986
 
932
987
  // ../../packages/shared/src/license.ts
933
988
  import crypto from "crypto";
934
- import fs2 from "fs";
935
- import os from "os";
936
- import path4 from "path";
989
+ import fs3 from "fs";
990
+ import os2 from "os";
991
+ import path5 from "path";
937
992
  var LICENSE_FILE = "license.json";
938
993
  var DEVICE_ID_FILE = "device-id";
939
994
  function getGlobalLicenseDir() {
940
- return path4.join(os.homedir(), ".keepgoing");
995
+ return path5.join(os2.homedir(), ".keepgoing");
941
996
  }
942
997
  function getGlobalLicensePath() {
943
- return path4.join(getGlobalLicenseDir(), LICENSE_FILE);
998
+ return path5.join(getGlobalLicenseDir(), LICENSE_FILE);
944
999
  }
945
1000
  function getDeviceId() {
946
1001
  const dir = getGlobalLicenseDir();
947
- const filePath = path4.join(dir, DEVICE_ID_FILE);
1002
+ const filePath = path5.join(dir, DEVICE_ID_FILE);
948
1003
  try {
949
- const existing = fs2.readFileSync(filePath, "utf-8").trim();
1004
+ const existing = fs3.readFileSync(filePath, "utf-8").trim();
950
1005
  if (existing) return existing;
951
1006
  } catch {
952
1007
  }
953
1008
  const id = crypto.randomUUID();
954
- if (!fs2.existsSync(dir)) {
955
- fs2.mkdirSync(dir, { recursive: true });
1009
+ if (!fs3.existsSync(dir)) {
1010
+ fs3.mkdirSync(dir, { recursive: true });
956
1011
  }
957
- fs2.writeFileSync(filePath, id, "utf-8");
1012
+ fs3.writeFileSync(filePath, id, "utf-8");
958
1013
  return id;
959
1014
  }
960
1015
  var DECISION_DETECTION_VARIANT_ID = 1361527;
@@ -988,10 +1043,10 @@ function readLicenseStore() {
988
1043
  const licensePath = getGlobalLicensePath();
989
1044
  let store;
990
1045
  try {
991
- if (!fs2.existsSync(licensePath)) {
1046
+ if (!fs3.existsSync(licensePath)) {
992
1047
  store = { version: 2, licenses: [] };
993
1048
  } else {
994
- const raw = fs2.readFileSync(licensePath, "utf-8");
1049
+ const raw = fs3.readFileSync(licensePath, "utf-8");
995
1050
  const data = JSON.parse(raw);
996
1051
  if (data?.version === 2 && Array.isArray(data.licenses)) {
997
1052
  store = data;
@@ -1008,11 +1063,11 @@ function readLicenseStore() {
1008
1063
  }
1009
1064
  function writeLicenseStore(store) {
1010
1065
  const dirPath = getGlobalLicenseDir();
1011
- if (!fs2.existsSync(dirPath)) {
1012
- fs2.mkdirSync(dirPath, { recursive: true });
1066
+ if (!fs3.existsSync(dirPath)) {
1067
+ fs3.mkdirSync(dirPath, { recursive: true });
1013
1068
  }
1014
- const licensePath = path4.join(dirPath, LICENSE_FILE);
1015
- fs2.writeFileSync(licensePath, JSON.stringify(store, null, 2), "utf-8");
1069
+ const licensePath = path5.join(dirPath, LICENSE_FILE);
1070
+ fs3.writeFileSync(licensePath, JSON.stringify(store, null, 2), "utf-8");
1016
1071
  _cachedStore = store;
1017
1072
  _cacheTimestamp = Date.now();
1018
1073
  }
@@ -1041,7 +1096,14 @@ function getLicenseForFeature(feature) {
1041
1096
  return features?.includes(feature);
1042
1097
  });
1043
1098
  }
1099
+ function getAllLicensesNeedingRevalidation() {
1100
+ return getActiveLicenses().filter((l) => needsRevalidation(l));
1101
+ }
1044
1102
  var REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
1103
+ function needsRevalidation(entry) {
1104
+ const lastValidated = new Date(entry.lastValidatedAt).getTime();
1105
+ return Date.now() - lastValidated > REVALIDATION_THRESHOLD_MS;
1106
+ }
1045
1107
 
1046
1108
  // ../../packages/shared/src/featureGate.ts
1047
1109
  var DefaultFeatureGate = class {
@@ -1052,8 +1114,8 @@ var DefaultFeatureGate = class {
1052
1114
  var currentGate = new DefaultFeatureGate();
1053
1115
 
1054
1116
  // ../../packages/shared/src/reader.ts
1055
- import fs4 from "fs";
1056
- import path6 from "path";
1117
+ import fs5 from "fs";
1118
+ import path7 from "path";
1057
1119
  var STORAGE_DIR2 = ".keepgoing";
1058
1120
  var META_FILE2 = "meta.json";
1059
1121
  var SESSIONS_FILE2 = "sessions.json";
@@ -1075,16 +1137,16 @@ var KeepGoingReader = class {
1075
1137
  this.workspacePath = workspacePath;
1076
1138
  const mainRoot = resolveStorageRoot(workspacePath);
1077
1139
  this._isWorktree = mainRoot !== workspacePath;
1078
- this.storagePath = path6.join(mainRoot, STORAGE_DIR2);
1079
- this.metaFilePath = path6.join(this.storagePath, META_FILE2);
1080
- this.sessionsFilePath = path6.join(this.storagePath, SESSIONS_FILE2);
1081
- this.decisionsFilePath = path6.join(this.storagePath, DECISIONS_FILE);
1082
- this.stateFilePath = path6.join(this.storagePath, STATE_FILE2);
1083
- this.currentTasksFilePath = path6.join(this.storagePath, CURRENT_TASKS_FILE2);
1140
+ this.storagePath = path7.join(mainRoot, STORAGE_DIR2);
1141
+ this.metaFilePath = path7.join(this.storagePath, META_FILE2);
1142
+ this.sessionsFilePath = path7.join(this.storagePath, SESSIONS_FILE2);
1143
+ this.decisionsFilePath = path7.join(this.storagePath, DECISIONS_FILE);
1144
+ this.stateFilePath = path7.join(this.storagePath, STATE_FILE2);
1145
+ this.currentTasksFilePath = path7.join(this.storagePath, CURRENT_TASKS_FILE2);
1084
1146
  }
1085
1147
  /** Check if .keepgoing/ directory exists. */
1086
1148
  exists() {
1087
- return fs4.existsSync(this.storagePath);
1149
+ return fs5.existsSync(this.storagePath);
1088
1150
  }
1089
1151
  /** Read state.json, returns undefined if missing or corrupt. */
1090
1152
  getState() {
@@ -1322,10 +1384,10 @@ var KeepGoingReader = class {
1322
1384
  }
1323
1385
  readJsonFile(filePath) {
1324
1386
  try {
1325
- if (!fs4.existsSync(filePath)) {
1387
+ if (!fs5.existsSync(filePath)) {
1326
1388
  return void 0;
1327
1389
  }
1328
- const raw = fs4.readFileSync(filePath, "utf-8");
1390
+ const raw = fs5.readFileSync(filePath, "utf-8");
1329
1391
  return JSON.parse(raw);
1330
1392
  } catch {
1331
1393
  return void 0;
@@ -1334,9 +1396,9 @@ var KeepGoingReader = class {
1334
1396
  };
1335
1397
 
1336
1398
  // ../../packages/shared/src/setup.ts
1337
- import fs5 from "fs";
1338
- import os2 from "os";
1339
- import path7 from "path";
1399
+ import fs6 from "fs";
1400
+ import os3 from "os";
1401
+ import path8 from "path";
1340
1402
  var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
1341
1403
  var SESSION_START_HOOK = {
1342
1404
  matcher: "",
@@ -1365,36 +1427,55 @@ var POST_TOOL_USE_HOOK = {
1365
1427
  }
1366
1428
  ]
1367
1429
  };
1368
- var CLAUDE_MD_SECTION = `
1430
+ var SESSION_END_HOOK = {
1431
+ matcher: "",
1432
+ hooks: [
1433
+ {
1434
+ type: "command",
1435
+ command: "npx -y @keepgoingdev/mcp-server --save-checkpoint"
1436
+ }
1437
+ ]
1438
+ };
1439
+ var KEEPGOING_RULES_VERSION = 1;
1440
+ var KEEPGOING_RULES_CONTENT = `<!-- @keepgoingdev/mcp-server v${KEEPGOING_RULES_VERSION} -->
1369
1441
  ## KeepGoing
1370
1442
 
1371
1443
  After completing a task or meaningful piece of work, call the \`save_checkpoint\` MCP tool with:
1372
- - \`summary\`: What you accomplished
1373
- - \`nextStep\`: What should be done next
1444
+ - \`summary\`: 1-2 sentences. What changed and why \u2014 no file paths, no implementation details (those are captured from git).
1445
+ - \`nextStep\`: What to do next
1374
1446
  - \`blocker\`: Any blocker (if applicable)
1375
1447
  `;
1448
+ function getRulesFileVersion(content) {
1449
+ const match = content.match(/<!-- @keepgoingdev\/mcp-server v(\d+) -->/);
1450
+ return match ? parseInt(match[1], 10) : null;
1451
+ }
1376
1452
  var STATUSLINE_CMD = "npx -y @keepgoingdev/mcp-server --statusline";
1453
+ function detectClaudeDir() {
1454
+ return process.env.CLAUDE_CONFIG_DIR || path8.join(os3.homedir(), ".claude");
1455
+ }
1377
1456
  function hasKeepGoingHook(hookEntries) {
1378
1457
  return hookEntries.some(
1379
1458
  (entry) => entry?.hooks?.some((h) => typeof h?.command === "string" && h.command.includes(KEEPGOING_MARKER))
1380
1459
  );
1381
1460
  }
1382
- function resolveScopePaths(scope, workspacePath) {
1461
+ function resolveScopePaths(scope, workspacePath, overrideClaudeDir) {
1383
1462
  if (scope === "user") {
1384
- const claudeDir2 = path7.join(os2.homedir(), ".claude");
1463
+ const claudeDir2 = overrideClaudeDir || detectClaudeDir();
1385
1464
  return {
1386
1465
  claudeDir: claudeDir2,
1387
- settingsPath: path7.join(claudeDir2, "settings.json"),
1388
- claudeMdPath: path7.join(claudeDir2, "CLAUDE.md")
1466
+ settingsPath: path8.join(claudeDir2, "settings.json"),
1467
+ claudeMdPath: path8.join(claudeDir2, "CLAUDE.md"),
1468
+ rulesPath: path8.join(claudeDir2, "rules", "keepgoing.md")
1389
1469
  };
1390
1470
  }
1391
- const claudeDir = path7.join(workspacePath, ".claude");
1392
- const dotClaudeMdPath = path7.join(workspacePath, ".claude", "CLAUDE.md");
1393
- const rootClaudeMdPath = path7.join(workspacePath, "CLAUDE.md");
1471
+ const claudeDir = path8.join(workspacePath, ".claude");
1472
+ const dotClaudeMdPath = path8.join(workspacePath, ".claude", "CLAUDE.md");
1473
+ const rootClaudeMdPath = path8.join(workspacePath, "CLAUDE.md");
1394
1474
  return {
1395
1475
  claudeDir,
1396
- settingsPath: path7.join(claudeDir, "settings.json"),
1397
- claudeMdPath: fs5.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath
1476
+ settingsPath: path8.join(claudeDir, "settings.json"),
1477
+ claudeMdPath: fs6.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath,
1478
+ rulesPath: path8.join(workspacePath, ".claude", "rules", "keepgoing.md")
1398
1479
  };
1399
1480
  }
1400
1481
  function writeHooksToSettings(settings) {
@@ -1423,15 +1504,22 @@ function writeHooksToSettings(settings) {
1423
1504
  settings.hooks.PostToolUse.push(POST_TOOL_USE_HOOK);
1424
1505
  changed = true;
1425
1506
  }
1507
+ if (!Array.isArray(settings.hooks.SessionEnd)) {
1508
+ settings.hooks.SessionEnd = [];
1509
+ }
1510
+ if (!hasKeepGoingHook(settings.hooks.SessionEnd)) {
1511
+ settings.hooks.SessionEnd.push(SESSION_END_HOOK);
1512
+ changed = true;
1513
+ }
1426
1514
  return changed;
1427
1515
  }
1428
1516
  function checkHookConflict(scope, workspacePath) {
1429
1517
  const otherPaths = resolveScopePaths(scope === "user" ? "project" : "user", workspacePath);
1430
- if (!fs5.existsSync(otherPaths.settingsPath)) {
1518
+ if (!fs6.existsSync(otherPaths.settingsPath)) {
1431
1519
  return null;
1432
1520
  }
1433
1521
  try {
1434
- const otherSettings = JSON.parse(fs5.readFileSync(otherPaths.settingsPath, "utf-8"));
1522
+ const otherSettings = JSON.parse(fs6.readFileSync(otherPaths.settingsPath, "utf-8"));
1435
1523
  const hooks = otherSettings?.hooks;
1436
1524
  if (!hooks) return null;
1437
1525
  const hasConflict = Array.isArray(hooks.SessionStart) && hasKeepGoingHook(hooks.SessionStart) || Array.isArray(hooks.Stop) && hasKeepGoingHook(hooks.Stop);
@@ -1450,15 +1538,20 @@ function setupProject(options) {
1450
1538
  scope = "project",
1451
1539
  sessionHooks = true,
1452
1540
  claudeMd = true,
1541
+ claudeDir: claudeDirOverride,
1453
1542
  statusline
1454
1543
  } = options;
1455
1544
  const messages = [];
1456
1545
  let changed = false;
1457
- const { claudeDir, settingsPath, claudeMdPath } = resolveScopePaths(scope, workspacePath);
1458
- const scopeLabel = scope === "user" ? "~/.claude/settings.json" : ".claude/settings.json";
1546
+ const { claudeDir, settingsPath, claudeMdPath, rulesPath } = resolveScopePaths(
1547
+ scope,
1548
+ workspacePath,
1549
+ claudeDirOverride
1550
+ );
1551
+ const scopeLabel = scope === "user" ? path8.join("~", path8.relative(os3.homedir(), claudeDir), "settings.json").replace(/\\/g, "/") : ".claude/settings.json";
1459
1552
  let settings = {};
1460
- if (fs5.existsSync(settingsPath)) {
1461
- settings = JSON.parse(fs5.readFileSync(settingsPath, "utf-8"));
1553
+ if (fs6.existsSync(settingsPath)) {
1554
+ settings = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
1462
1555
  }
1463
1556
  let settingsChanged = false;
1464
1557
  if (sessionHooks) {
@@ -1489,29 +1582,45 @@ function setupProject(options) {
1489
1582
  statusline?.cleanup?.();
1490
1583
  }
1491
1584
  if (settingsChanged) {
1492
- if (!fs5.existsSync(claudeDir)) {
1493
- fs5.mkdirSync(claudeDir, { recursive: true });
1585
+ if (!fs6.existsSync(claudeDir)) {
1586
+ fs6.mkdirSync(claudeDir, { recursive: true });
1494
1587
  }
1495
- fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1588
+ fs6.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1496
1589
  changed = true;
1497
1590
  }
1498
1591
  if (claudeMd) {
1499
- let existing = "";
1500
- if (fs5.existsSync(claudeMdPath)) {
1501
- existing = fs5.readFileSync(claudeMdPath, "utf-8");
1502
- }
1503
- const mdLabel = scope === "user" ? "~/.claude/CLAUDE.md" : "CLAUDE.md";
1504
- if (existing.includes("## KeepGoing")) {
1505
- messages.push(`CLAUDE.md: KeepGoing section already present in ${mdLabel}, skipped`);
1592
+ const rulesDir = path8.dirname(rulesPath);
1593
+ const rulesLabel = scope === "user" ? path8.join(path8.relative(os3.homedir(), path8.dirname(rulesPath)), "keepgoing.md").replace(/\\/g, "/") : ".claude/rules/keepgoing.md";
1594
+ if (fs6.existsSync(rulesPath)) {
1595
+ const existing = fs6.readFileSync(rulesPath, "utf-8");
1596
+ const existingVersion = getRulesFileVersion(existing);
1597
+ if (existingVersion === null) {
1598
+ messages.push(`Rules file: Custom file found at ${rulesLabel}, skipping`);
1599
+ } else if (existingVersion >= KEEPGOING_RULES_VERSION) {
1600
+ messages.push(`Rules file: Already up to date (v${existingVersion}), skipped`);
1601
+ } else {
1602
+ if (!fs6.existsSync(rulesDir)) {
1603
+ fs6.mkdirSync(rulesDir, { recursive: true });
1604
+ }
1605
+ fs6.writeFileSync(rulesPath, KEEPGOING_RULES_CONTENT);
1606
+ changed = true;
1607
+ messages.push(`Rules file: Updated v${existingVersion} \u2192 v${KEEPGOING_RULES_VERSION} at ${rulesLabel}`);
1608
+ }
1506
1609
  } else {
1507
- const updated = existing + CLAUDE_MD_SECTION;
1508
- const mdDir = path7.dirname(claudeMdPath);
1509
- if (!fs5.existsSync(mdDir)) {
1510
- fs5.mkdirSync(mdDir, { recursive: true });
1610
+ const existingClaudeMd = fs6.existsSync(claudeMdPath) ? fs6.readFileSync(claudeMdPath, "utf-8") : "";
1611
+ if (!fs6.existsSync(rulesDir)) {
1612
+ fs6.mkdirSync(rulesDir, { recursive: true });
1511
1613
  }
1512
- fs5.writeFileSync(claudeMdPath, updated);
1614
+ fs6.writeFileSync(rulesPath, KEEPGOING_RULES_CONTENT);
1513
1615
  changed = true;
1514
- messages.push(`CLAUDE.md: Added KeepGoing section to ${mdLabel}`);
1616
+ if (existingClaudeMd.includes("## KeepGoing")) {
1617
+ const mdLabel = scope === "user" ? "~/.claude/CLAUDE.md" : "CLAUDE.md";
1618
+ messages.push(
1619
+ `Rules file: Created ${rulesLabel} (you can now remove the ## KeepGoing section from ${mdLabel})`
1620
+ );
1621
+ } else {
1622
+ messages.push(`Rules file: Created ${rulesLabel}`);
1623
+ }
1515
1624
  }
1516
1625
  }
1517
1626
  return { messages, changed };
@@ -1588,6 +1697,39 @@ async function activateLicense(licenseKey, instanceName, options) {
1588
1697
  return { valid: false, error: message };
1589
1698
  }
1590
1699
  }
1700
+ async function validateLicense(licenseKey, instanceId, options) {
1701
+ try {
1702
+ const res = await fetchWithTimeout(`${BASE_URL}/validate`, {
1703
+ method: "POST",
1704
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1705
+ body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
1706
+ });
1707
+ const data = await safeJson(res);
1708
+ if (!res.ok || !data?.valid) {
1709
+ return { valid: false, isNetworkError: false, error: data?.error || `Validation failed (${res.status})` };
1710
+ }
1711
+ if (!options?.allowTestMode && data.license_key?.test_mode) {
1712
+ return { valid: false, isNetworkError: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
1713
+ }
1714
+ if (!options?.allowTestMode) {
1715
+ const productError = validateProductIdentity(data.meta);
1716
+ if (productError) {
1717
+ return { valid: false, isNetworkError: false, error: productError };
1718
+ }
1719
+ }
1720
+ return {
1721
+ valid: true,
1722
+ licenseKey: data.license_key?.key,
1723
+ customerName: data.meta?.customer_name,
1724
+ productName: data.meta?.product_name,
1725
+ variantId: data.meta?.variant_id,
1726
+ variantName: data.meta?.variant_name
1727
+ };
1728
+ } catch (err) {
1729
+ const message = err instanceof Error && err.name === "AbortError" ? "Request timed out. Please check your network connection and try again." : err instanceof Error ? err.message : "Network error";
1730
+ return { valid: false, isNetworkError: true, error: message };
1731
+ }
1732
+ }
1591
1733
  async function deactivateLicense(licenseKey, instanceId) {
1592
1734
  try {
1593
1735
  const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
@@ -1606,6 +1748,52 @@ async function deactivateLicense(licenseKey, instanceId) {
1606
1748
  }
1607
1749
  }
1608
1750
 
1751
+ // ../../packages/shared/src/licenseRevalidation.ts
1752
+ async function revalidateStaleLicenses(options) {
1753
+ const stale = getAllLicensesNeedingRevalidation();
1754
+ const result = { checked: stale.length, refreshed: 0, revoked: 0, skippedNetworkError: 0 };
1755
+ if (stale.length === 0) return result;
1756
+ const updates = /* @__PURE__ */ new Map();
1757
+ for (const entry of stale) {
1758
+ const vResult = await validateLicense(entry.licenseKey, entry.instanceId, {
1759
+ allowTestMode: options?.allowTestMode
1760
+ });
1761
+ if (vResult.valid) {
1762
+ const patch = {
1763
+ lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString()
1764
+ };
1765
+ if (vResult.customerName) patch.customerName = vResult.customerName;
1766
+ updates.set(entry.licenseKey, patch);
1767
+ result.refreshed++;
1768
+ } else if (vResult.isNetworkError) {
1769
+ result.skippedNetworkError++;
1770
+ } else {
1771
+ updates.set(entry.licenseKey, { status: "inactive" });
1772
+ result.revoked++;
1773
+ }
1774
+ }
1775
+ if (updates.size > 0) {
1776
+ const store = readLicenseStore();
1777
+ for (const [key, patch] of updates) {
1778
+ const idx = store.licenses.findIndex((l) => l.licenseKey === key);
1779
+ if (idx >= 0) {
1780
+ Object.assign(store.licenses[idx], patch);
1781
+ }
1782
+ }
1783
+ writeLicenseStore(store);
1784
+ }
1785
+ return result;
1786
+ }
1787
+ async function getLicenseForFeatureWithRevalidation(feature, options) {
1788
+ const license = getLicenseForFeature(feature);
1789
+ if (!license) return void 0;
1790
+ if (needsRevalidation(license)) {
1791
+ await revalidateStaleLicenses(options);
1792
+ return getLicenseForFeature(feature);
1793
+ }
1794
+ return license;
1795
+ }
1796
+
1609
1797
  // src/render.ts
1610
1798
  var RESET = "\x1B[0m";
1611
1799
  var BOLD = "\x1B[1m";
@@ -1919,14 +2107,14 @@ function renderEnrichedBriefingQuiet(briefing) {
1919
2107
  // src/updateCheck.ts
1920
2108
  import { spawn } from "child_process";
1921
2109
  import { readFileSync, existsSync } from "fs";
1922
- import path8 from "path";
1923
- import os3 from "os";
1924
- var CLI_VERSION = "1.1.1";
2110
+ import path9 from "path";
2111
+ import os4 from "os";
2112
+ var CLI_VERSION = "1.2.1";
1925
2113
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@keepgoingdev/cli/latest";
1926
2114
  var FETCH_TIMEOUT_MS = 5e3;
1927
2115
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1928
- var CACHE_DIR = path8.join(os3.homedir(), ".keepgoing");
1929
- var CACHE_PATH = path8.join(CACHE_DIR, "update-check.json");
2116
+ var CACHE_DIR = path9.join(os4.homedir(), ".keepgoing");
2117
+ var CACHE_PATH = path9.join(CACHE_DIR, "update-check.json");
1930
2118
  function isNewerVersion(current, latest) {
1931
2119
  const cur = current.split(".").map(Number);
1932
2120
  const lat = latest.split(".").map(Number);
@@ -2041,7 +2229,7 @@ async function statusCommand(opts) {
2041
2229
  }
2042
2230
 
2043
2231
  // src/commands/save.ts
2044
- import path9 from "path";
2232
+ import path10 from "path";
2045
2233
  async function saveCommand(opts) {
2046
2234
  const { cwd, message, nextStepOverride, json, quiet, force } = opts;
2047
2235
  const isManual = !!message;
@@ -2070,9 +2258,9 @@ async function saveCommand(opts) {
2070
2258
  sessionStartTime: lastSession?.timestamp ?? now,
2071
2259
  lastActivityTime: now
2072
2260
  });
2073
- const summary = message ?? buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path9.basename(f)).join(", ")}`;
2261
+ const summary = message ?? buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path10.basename(f)).join(", ")}`;
2074
2262
  const nextStep = nextStepOverride ?? buildSmartNextStep(events);
2075
- const projectName = path9.basename(resolveStorageRoot(cwd));
2263
+ const projectName = path10.basename(resolveStorageRoot(cwd));
2076
2264
  const sessionId = generateSessionId({
2077
2265
  workspaceRoot: cwd,
2078
2266
  branch: gitBranch ?? void 0,
@@ -2105,12 +2293,25 @@ async function saveCommand(opts) {
2105
2293
  }
2106
2294
 
2107
2295
  // src/commands/hook.ts
2108
- import fs6 from "fs";
2109
- import path10 from "path";
2110
- import os4 from "os";
2296
+ import fs7 from "fs";
2297
+ import path11 from "path";
2298
+ import os5 from "os";
2111
2299
  import { execSync } from "child_process";
2112
2300
  var HOOK_MARKER_START = "# keepgoing-hook-start";
2113
2301
  var HOOK_MARKER_END = "# keepgoing-hook-end";
2302
+ var POST_COMMIT_MARKER_START = "# keepgoing-post-commit-start";
2303
+ var POST_COMMIT_MARKER_END = "# keepgoing-post-commit-end";
2304
+ var KEEPGOING_HOOKS_DIR = path11.join(os5.homedir(), ".keepgoing", "hooks");
2305
+ var POST_COMMIT_HOOK_PATH = path11.join(KEEPGOING_HOOKS_DIR, "post-commit");
2306
+ var KEEPGOING_MANAGED_MARKER = path11.join(KEEPGOING_HOOKS_DIR, ".keepgoing-managed");
2307
+ var POST_COMMIT_HOOK = `#!/bin/sh
2308
+ ${POST_COMMIT_MARKER_START}
2309
+ # Runs after every git commit. Detects high-signal decisions.
2310
+ if command -v keepgoing-mcp-server >/dev/null 2>&1; then
2311
+ keepgoing-mcp-server --detect-decisions 2>/dev/null &
2312
+ fi
2313
+ ${POST_COMMIT_MARKER_END}
2314
+ `;
2114
2315
  var ZSH_HOOK = `${HOOK_MARKER_START}
2115
2316
  # KeepGoing shell hook \u2014 auto-injected by 'keepgoing hook install'
2116
2317
  if command -v keepgoing >/dev/null 2>&1; then
@@ -2143,7 +2344,7 @@ if command -v keepgoing >/dev/null 2>&1
2143
2344
  end
2144
2345
  ${HOOK_MARKER_END}`;
2145
2346
  function detectShellRcFile(shellOverride) {
2146
- const home = os4.homedir();
2347
+ const home = os5.homedir();
2147
2348
  let shell;
2148
2349
  if (shellOverride) {
2149
2350
  shell = shellOverride.toLowerCase();
@@ -2166,17 +2367,155 @@ function detectShellRcFile(shellOverride) {
2166
2367
  }
2167
2368
  }
2168
2369
  if (shell === "zsh") {
2169
- return { shell: "zsh", rcFile: path10.join(home, ".zshrc") };
2370
+ return { shell: "zsh", rcFile: path11.join(home, ".zshrc") };
2170
2371
  }
2171
2372
  if (shell === "bash") {
2172
- return { shell: "bash", rcFile: path10.join(home, ".bashrc") };
2373
+ return { shell: "bash", rcFile: path11.join(home, ".bashrc") };
2173
2374
  }
2174
2375
  if (shell === "fish") {
2175
- const xdgConfig = process.env["XDG_CONFIG_HOME"] || path10.join(home, ".config");
2176
- return { shell: "fish", rcFile: path10.join(xdgConfig, "fish", "config.fish") };
2376
+ const xdgConfig = process.env["XDG_CONFIG_HOME"] || path11.join(home, ".config");
2377
+ return { shell: "fish", rcFile: path11.join(xdgConfig, "fish", "config.fish") };
2177
2378
  }
2178
2379
  return void 0;
2179
2380
  }
2381
+ function resolveGlobalGitignorePath() {
2382
+ try {
2383
+ const configured = execSync("git config --global core.excludesfile", {
2384
+ encoding: "utf-8",
2385
+ stdio: ["pipe", "pipe", "pipe"]
2386
+ }).trim();
2387
+ if (configured) {
2388
+ return configured.startsWith("~") ? path11.join(os5.homedir(), configured.slice(1)) : configured;
2389
+ }
2390
+ } catch {
2391
+ }
2392
+ return path11.join(os5.homedir(), ".gitignore_global");
2393
+ }
2394
+ function installGlobalGitignore() {
2395
+ const ignorePath = resolveGlobalGitignorePath();
2396
+ let existing = "";
2397
+ try {
2398
+ existing = fs7.readFileSync(ignorePath, "utf-8");
2399
+ } catch {
2400
+ }
2401
+ if (existing.split("\n").some((line) => line.trim() === ".keepgoing")) {
2402
+ console.log(`Global gitignore: .keepgoing already present in ${ignorePath}`);
2403
+ } else {
2404
+ const suffix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
2405
+ fs7.appendFileSync(ignorePath, `${suffix}.keepgoing
2406
+ `, "utf-8");
2407
+ console.log(`Global gitignore: .keepgoing added to ${ignorePath}`);
2408
+ }
2409
+ try {
2410
+ execSync("git config --global core.excludesfile", {
2411
+ encoding: "utf-8",
2412
+ stdio: ["pipe", "pipe", "pipe"]
2413
+ }).trim();
2414
+ } catch {
2415
+ execSync(`git config --global core.excludesfile "${ignorePath}"`, {
2416
+ stdio: ["pipe", "pipe", "pipe"]
2417
+ });
2418
+ }
2419
+ }
2420
+ function uninstallGlobalGitignore() {
2421
+ const ignorePath = resolveGlobalGitignorePath();
2422
+ let existing = "";
2423
+ try {
2424
+ existing = fs7.readFileSync(ignorePath, "utf-8");
2425
+ } catch {
2426
+ return;
2427
+ }
2428
+ const lines = existing.split("\n");
2429
+ const filtered = lines.filter((line) => line.trim() !== ".keepgoing");
2430
+ if (filtered.length !== lines.length) {
2431
+ fs7.writeFileSync(ignorePath, filtered.join("\n"), "utf-8");
2432
+ console.log(`Global gitignore: .keepgoing removed from ${ignorePath}`);
2433
+ }
2434
+ }
2435
+ function installPostCommitHook() {
2436
+ fs7.mkdirSync(KEEPGOING_HOOKS_DIR, { recursive: true });
2437
+ if (fs7.existsSync(POST_COMMIT_HOOK_PATH)) {
2438
+ const existing = fs7.readFileSync(POST_COMMIT_HOOK_PATH, "utf-8");
2439
+ if (existing.includes(POST_COMMIT_MARKER_START)) {
2440
+ console.log(`Git post-commit hook: already installed at ${POST_COMMIT_HOOK_PATH}`);
2441
+ } else {
2442
+ const suffix = existing.endsWith("\n") ? "" : "\n";
2443
+ const block = `${suffix}
2444
+ ${POST_COMMIT_MARKER_START}
2445
+ # Runs after every git commit. Detects high-signal decisions.
2446
+ if command -v keepgoing-mcp-server >/dev/null 2>&1; then
2447
+ keepgoing-mcp-server --detect-decisions 2>/dev/null &
2448
+ fi
2449
+ ${POST_COMMIT_MARKER_END}
2450
+ `;
2451
+ fs7.appendFileSync(POST_COMMIT_HOOK_PATH, block, "utf-8");
2452
+ console.log(`Git post-commit hook: appended to existing ${POST_COMMIT_HOOK_PATH}`);
2453
+ }
2454
+ } else {
2455
+ fs7.writeFileSync(POST_COMMIT_HOOK_PATH, POST_COMMIT_HOOK, { mode: 493 });
2456
+ console.log(`Git post-commit hook: installed at ${POST_COMMIT_HOOK_PATH}`);
2457
+ }
2458
+ fs7.chmodSync(POST_COMMIT_HOOK_PATH, 493);
2459
+ let currentHooksPath;
2460
+ try {
2461
+ currentHooksPath = execSync("git config --global core.hooksPath", {
2462
+ encoding: "utf-8",
2463
+ stdio: ["pipe", "pipe", "pipe"]
2464
+ }).trim();
2465
+ } catch {
2466
+ }
2467
+ if (currentHooksPath === KEEPGOING_HOOKS_DIR) {
2468
+ console.log(`Git hooks path: already set to ${KEEPGOING_HOOKS_DIR}`);
2469
+ } else if (currentHooksPath) {
2470
+ console.log(`Git hooks path: set to ${currentHooksPath}`);
2471
+ console.log(` To use KeepGoing's post-commit hook, add the following to ${currentHooksPath}/post-commit:`);
2472
+ console.log(` if command -v keepgoing-mcp-server >/dev/null 2>&1; then`);
2473
+ console.log(` keepgoing-mcp-server --detect-decisions 2>/dev/null &`);
2474
+ console.log(` fi`);
2475
+ } else {
2476
+ console.log(`Git post-commit hook: saved to ${POST_COMMIT_HOOK_PATH}`);
2477
+ console.log(` To activate it globally, run:`);
2478
+ console.log(` git config --global core.hooksPath "${KEEPGOING_HOOKS_DIR}"`);
2479
+ console.log(` Note: this overrides per-repo .git/hooks/ for all repositories.`);
2480
+ }
2481
+ }
2482
+ function uninstallPostCommitHook() {
2483
+ if (fs7.existsSync(POST_COMMIT_HOOK_PATH)) {
2484
+ const existing = fs7.readFileSync(POST_COMMIT_HOOK_PATH, "utf-8");
2485
+ if (existing.includes(POST_COMMIT_MARKER_START)) {
2486
+ const pattern = new RegExp(
2487
+ `
2488
+ ?${POST_COMMIT_MARKER_START}[\\s\\S]*?${POST_COMMIT_MARKER_END}
2489
+ ?`,
2490
+ "g"
2491
+ );
2492
+ const updated = existing.replace(pattern, "").trim();
2493
+ if (!updated || updated === "#!/bin/sh") {
2494
+ fs7.unlinkSync(POST_COMMIT_HOOK_PATH);
2495
+ console.log(`Git post-commit hook: removed ${POST_COMMIT_HOOK_PATH}`);
2496
+ } else {
2497
+ fs7.writeFileSync(POST_COMMIT_HOOK_PATH, updated + "\n", "utf-8");
2498
+ console.log(`Git post-commit hook: KeepGoing block removed from ${POST_COMMIT_HOOK_PATH}`);
2499
+ }
2500
+ }
2501
+ }
2502
+ try {
2503
+ const currentHooksPath = execSync("git config --global core.hooksPath", {
2504
+ encoding: "utf-8",
2505
+ stdio: ["pipe", "pipe", "pipe"]
2506
+ }).trim();
2507
+ if (currentHooksPath === KEEPGOING_HOOKS_DIR) {
2508
+ execSync("git config --global --unset core.hooksPath", {
2509
+ stdio: ["pipe", "pipe", "pipe"]
2510
+ });
2511
+ console.log("Git hooks path: global core.hooksPath unset");
2512
+ }
2513
+ } catch {
2514
+ }
2515
+ if (fs7.existsSync(KEEPGOING_MANAGED_MARKER)) {
2516
+ fs7.unlinkSync(KEEPGOING_MANAGED_MARKER);
2517
+ }
2518
+ }
2180
2519
  function hookInstallCommand(shellOverride) {
2181
2520
  const detected = detectShellRcFile(shellOverride);
2182
2521
  if (!detected) {
@@ -2189,18 +2528,23 @@ function hookInstallCommand(shellOverride) {
2189
2528
  const hookBlock = shell === "zsh" ? ZSH_HOOK : shell === "fish" ? FISH_HOOK : BASH_HOOK;
2190
2529
  let existing = "";
2191
2530
  try {
2192
- existing = fs6.readFileSync(rcFile, "utf-8");
2531
+ existing = fs7.readFileSync(rcFile, "utf-8");
2193
2532
  } catch {
2194
2533
  }
2195
2534
  if (existing.includes(HOOK_MARKER_START)) {
2196
2535
  console.log(`KeepGoing hook is already installed in ${rcFile}.`);
2536
+ installGlobalGitignore();
2537
+ installPostCommitHook();
2197
2538
  return;
2198
2539
  }
2199
- fs6.appendFileSync(rcFile, `
2540
+ fs7.appendFileSync(rcFile, `
2200
2541
  ${hookBlock}
2201
2542
  `, "utf-8");
2202
2543
  console.log(`KeepGoing hook installed in ${rcFile}.`);
2203
- console.log(`Reload your shell config to activate it:
2544
+ installGlobalGitignore();
2545
+ installPostCommitHook();
2546
+ console.log(`
2547
+ Reload your shell config to activate it:
2204
2548
  `);
2205
2549
  console.log(` source ${rcFile}`);
2206
2550
  }
@@ -2215,7 +2559,7 @@ function hookUninstallCommand(shellOverride) {
2215
2559
  const { rcFile } = detected;
2216
2560
  let existing = "";
2217
2561
  try {
2218
- existing = fs6.readFileSync(rcFile, "utf-8");
2562
+ existing = fs7.readFileSync(rcFile, "utf-8");
2219
2563
  } catch {
2220
2564
  console.log(`${rcFile} not found \u2014 nothing to remove.`);
2221
2565
  return;
@@ -2231,9 +2575,12 @@ function hookUninstallCommand(shellOverride) {
2231
2575
  "g"
2232
2576
  );
2233
2577
  const updated = existing.replace(pattern, "").replace(/\n{3,}/g, "\n\n");
2234
- fs6.writeFileSync(rcFile, updated, "utf-8");
2578
+ fs7.writeFileSync(rcFile, updated, "utf-8");
2235
2579
  console.log(`KeepGoing hook removed from ${rcFile}.`);
2236
- console.log(`Reload your shell config to deactivate it:
2580
+ uninstallGlobalGitignore();
2581
+ uninstallPostCommitHook();
2582
+ console.log(`
2583
+ Reload your shell config to deactivate it:
2237
2584
  `);
2238
2585
  console.log(` source ${rcFile}`);
2239
2586
  }
@@ -2482,7 +2829,7 @@ async function decisionsCommand(opts) {
2482
2829
  }
2483
2830
  return;
2484
2831
  }
2485
- if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !getLicenseForFeature("decisions")) {
2832
+ if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !await getLicenseForFeatureWithRevalidation("decisions")) {
2486
2833
  console.error(
2487
2834
  'Decision Detection requires a Pro license.\nRun "keepgoing activate <key>" or visit https://keepgoing.dev/add-ons to purchase.'
2488
2835
  );
@@ -2686,8 +3033,8 @@ function renderGrouped(sessions, showStat) {
2686
3033
  }
2687
3034
  }
2688
3035
  }
2689
- function logDecisions(reader, opts) {
2690
- const license = getLicenseForFeature("decisions");
3036
+ async function logDecisions(reader, opts) {
3037
+ const license = await getLicenseForFeatureWithRevalidation("decisions");
2691
3038
  if (!license) {
2692
3039
  console.log("Decision tracking requires a Pro license. Run: keepgoing activate <key>");
2693
3040
  return;
@@ -2733,7 +3080,7 @@ async function logCommand(opts) {
2733
3080
  return;
2734
3081
  }
2735
3082
  if (opts.subcommand === "decisions") {
2736
- logDecisions(reader, opts);
3083
+ await logDecisions(reader, opts);
2737
3084
  } else {
2738
3085
  logSessions(reader, opts);
2739
3086
  }
@@ -3216,7 +3563,7 @@ async function main() {
3216
3563
  }
3217
3564
  break;
3218
3565
  case "version":
3219
- console.log(`keepgoing v${"1.1.1"}`);
3566
+ console.log(`keepgoing v${"1.2.1"}`);
3220
3567
  break;
3221
3568
  case "activate":
3222
3569
  await activateCommand({ licenseKey: subcommand });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepgoingdev/cli",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Terminal CLI for KeepGoing. Resume side projects without the mental friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",