@keepgoingdev/cli 1.2.0 → 1.2.2

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 +104 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -60,7 +60,8 @@ function findGitRoot(startPath) {
60
60
  const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
61
61
  cwd: startPath,
62
62
  encoding: "utf-8",
63
- timeout: 5e3
63
+ timeout: 5e3,
64
+ stdio: ["pipe", "pipe", "pipe"]
64
65
  }).trim();
65
66
  return toplevel || startPath;
66
67
  } catch {
@@ -77,12 +78,14 @@ function resolveStorageRoot(startPath) {
77
78
  const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
78
79
  cwd: startPath,
79
80
  encoding: "utf-8",
80
- timeout: 5e3
81
+ timeout: 5e3,
82
+ stdio: ["pipe", "pipe", "pipe"]
81
83
  }).trim();
82
84
  const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
83
85
  cwd: startPath,
84
86
  encoding: "utf-8",
85
- timeout: 5e3
87
+ timeout: 5e3,
88
+ stdio: ["pipe", "pipe", "pipe"]
86
89
  }).trim();
87
90
  const absoluteCommonDir = path.resolve(toplevel, commonDir);
88
91
  const mainRoot = path.dirname(absoluteCommonDir);
@@ -1096,7 +1099,14 @@ function getLicenseForFeature(feature) {
1096
1099
  return features?.includes(feature);
1097
1100
  });
1098
1101
  }
1102
+ function getAllLicensesNeedingRevalidation() {
1103
+ return getActiveLicenses().filter((l) => needsRevalidation(l));
1104
+ }
1099
1105
  var REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
1106
+ function needsRevalidation(entry) {
1107
+ const lastValidated = new Date(entry.lastValidatedAt).getTime();
1108
+ return Date.now() - lastValidated > REVALIDATION_THRESHOLD_MS;
1109
+ }
1100
1110
 
1101
1111
  // ../../packages/shared/src/featureGate.ts
1102
1112
  var DefaultFeatureGate = class {
@@ -1429,12 +1439,14 @@ var SESSION_END_HOOK = {
1429
1439
  }
1430
1440
  ]
1431
1441
  };
1432
- var KEEPGOING_RULES_VERSION = 1;
1442
+ var KEEPGOING_RULES_VERSION = 2;
1433
1443
  var KEEPGOING_RULES_CONTENT = `<!-- @keepgoingdev/mcp-server v${KEEPGOING_RULES_VERSION} -->
1434
1444
  ## KeepGoing
1435
1445
 
1446
+ When you see KeepGoing momentum data in your session context (from a SessionStart hook), share a brief welcome with the user that includes: what was last worked on, the suggested next step, and any blockers.
1447
+
1436
1448
  After completing a task or meaningful piece of work, call the \`save_checkpoint\` MCP tool with:
1437
- - \`summary\`: 1-2 sentences. What changed and why \u2014 no file paths, no implementation details (those are captured from git).
1449
+ - \`summary\`: 1-2 sentences. What changed and why, no file paths, no implementation details (those are captured from git).
1438
1450
  - \`nextStep\`: What to do next
1439
1451
  - \`blocker\`: Any blocker (if applicable)
1440
1452
  `;
@@ -1560,7 +1572,7 @@ function setupProject(options) {
1560
1572
  messages.push(`Warning: ${conflict}`);
1561
1573
  }
1562
1574
  }
1563
- if (scope === "project") {
1575
+ {
1564
1576
  const needsUpdate = settings.statusLine?.command && statusline?.isLegacy?.(settings.statusLine.command);
1565
1577
  if (!settings.statusLine || needsUpdate) {
1566
1578
  settings.statusLine = {
@@ -1568,7 +1580,7 @@ function setupProject(options) {
1568
1580
  command: STATUSLINE_CMD
1569
1581
  };
1570
1582
  settingsChanged = true;
1571
- messages.push(needsUpdate ? "Statusline: Migrated to auto-updating npx command" : "Statusline: Added to .claude/settings.json");
1583
+ messages.push(needsUpdate ? "Statusline: Migrated to auto-updating npx command" : `Statusline: Added to ${scopeLabel}`);
1572
1584
  } else {
1573
1585
  messages.push("Statusline: Already configured in settings, skipped");
1574
1586
  }
@@ -1690,6 +1702,39 @@ async function activateLicense(licenseKey, instanceName, options) {
1690
1702
  return { valid: false, error: message };
1691
1703
  }
1692
1704
  }
1705
+ async function validateLicense(licenseKey, instanceId, options) {
1706
+ try {
1707
+ const res = await fetchWithTimeout(`${BASE_URL}/validate`, {
1708
+ method: "POST",
1709
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1710
+ body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
1711
+ });
1712
+ const data = await safeJson(res);
1713
+ if (!res.ok || !data?.valid) {
1714
+ return { valid: false, isNetworkError: false, error: data?.error || `Validation failed (${res.status})` };
1715
+ }
1716
+ if (!options?.allowTestMode && data.license_key?.test_mode) {
1717
+ return { valid: false, isNetworkError: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
1718
+ }
1719
+ if (!options?.allowTestMode) {
1720
+ const productError = validateProductIdentity(data.meta);
1721
+ if (productError) {
1722
+ return { valid: false, isNetworkError: false, error: productError };
1723
+ }
1724
+ }
1725
+ return {
1726
+ valid: true,
1727
+ licenseKey: data.license_key?.key,
1728
+ customerName: data.meta?.customer_name,
1729
+ productName: data.meta?.product_name,
1730
+ variantId: data.meta?.variant_id,
1731
+ variantName: data.meta?.variant_name
1732
+ };
1733
+ } catch (err) {
1734
+ 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";
1735
+ return { valid: false, isNetworkError: true, error: message };
1736
+ }
1737
+ }
1693
1738
  async function deactivateLicense(licenseKey, instanceId) {
1694
1739
  try {
1695
1740
  const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
@@ -1708,6 +1753,52 @@ async function deactivateLicense(licenseKey, instanceId) {
1708
1753
  }
1709
1754
  }
1710
1755
 
1756
+ // ../../packages/shared/src/licenseRevalidation.ts
1757
+ async function revalidateStaleLicenses(options) {
1758
+ const stale = getAllLicensesNeedingRevalidation();
1759
+ const result = { checked: stale.length, refreshed: 0, revoked: 0, skippedNetworkError: 0 };
1760
+ if (stale.length === 0) return result;
1761
+ const updates = /* @__PURE__ */ new Map();
1762
+ for (const entry of stale) {
1763
+ const vResult = await validateLicense(entry.licenseKey, entry.instanceId, {
1764
+ allowTestMode: options?.allowTestMode
1765
+ });
1766
+ if (vResult.valid) {
1767
+ const patch = {
1768
+ lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString()
1769
+ };
1770
+ if (vResult.customerName) patch.customerName = vResult.customerName;
1771
+ updates.set(entry.licenseKey, patch);
1772
+ result.refreshed++;
1773
+ } else if (vResult.isNetworkError) {
1774
+ result.skippedNetworkError++;
1775
+ } else {
1776
+ updates.set(entry.licenseKey, { status: "inactive" });
1777
+ result.revoked++;
1778
+ }
1779
+ }
1780
+ if (updates.size > 0) {
1781
+ const store = readLicenseStore();
1782
+ for (const [key, patch] of updates) {
1783
+ const idx = store.licenses.findIndex((l) => l.licenseKey === key);
1784
+ if (idx >= 0) {
1785
+ Object.assign(store.licenses[idx], patch);
1786
+ }
1787
+ }
1788
+ writeLicenseStore(store);
1789
+ }
1790
+ return result;
1791
+ }
1792
+ async function getLicenseForFeatureWithRevalidation(feature, options) {
1793
+ const license = getLicenseForFeature(feature);
1794
+ if (!license) return void 0;
1795
+ if (needsRevalidation(license)) {
1796
+ await revalidateStaleLicenses(options);
1797
+ return getLicenseForFeature(feature);
1798
+ }
1799
+ return license;
1800
+ }
1801
+
1711
1802
  // src/render.ts
1712
1803
  var RESET = "\x1B[0m";
1713
1804
  var BOLD = "\x1B[1m";
@@ -2023,7 +2114,7 @@ import { spawn } from "child_process";
2023
2114
  import { readFileSync, existsSync } from "fs";
2024
2115
  import path9 from "path";
2025
2116
  import os4 from "os";
2026
- var CLI_VERSION = "1.2.0";
2117
+ var CLI_VERSION = "1.2.2";
2027
2118
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@keepgoingdev/cli/latest";
2028
2119
  var FETCH_TIMEOUT_MS = 5e3;
2029
2120
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -2743,7 +2834,7 @@ async function decisionsCommand(opts) {
2743
2834
  }
2744
2835
  return;
2745
2836
  }
2746
- if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !getLicenseForFeature("decisions")) {
2837
+ if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !await getLicenseForFeatureWithRevalidation("decisions")) {
2747
2838
  console.error(
2748
2839
  'Decision Detection requires a Pro license.\nRun "keepgoing activate <key>" or visit https://keepgoing.dev/add-ons to purchase.'
2749
2840
  );
@@ -2947,8 +3038,8 @@ function renderGrouped(sessions, showStat) {
2947
3038
  }
2948
3039
  }
2949
3040
  }
2950
- function logDecisions(reader, opts) {
2951
- const license = getLicenseForFeature("decisions");
3041
+ async function logDecisions(reader, opts) {
3042
+ const license = await getLicenseForFeatureWithRevalidation("decisions");
2952
3043
  if (!license) {
2953
3044
  console.log("Decision tracking requires a Pro license. Run: keepgoing activate <key>");
2954
3045
  return;
@@ -2994,7 +3085,7 @@ async function logCommand(opts) {
2994
3085
  return;
2995
3086
  }
2996
3087
  if (opts.subcommand === "decisions") {
2997
- logDecisions(reader, opts);
3088
+ await logDecisions(reader, opts);
2998
3089
  } else {
2999
3090
  logSessions(reader, opts);
3000
3091
  }
@@ -3477,7 +3568,7 @@ async function main() {
3477
3568
  }
3478
3569
  break;
3479
3570
  case "version":
3480
- console.log(`keepgoing v${"1.2.0"}`);
3571
+ console.log(`keepgoing v${"1.2.2"}`);
3481
3572
  break;
3482
3573
  case "activate":
3483
3574
  await activateCommand({ licenseKey: subcommand });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepgoingdev/cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Terminal CLI for KeepGoing. Resume side projects without the mental friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",