@keepgoingdev/mcp-server 0.5.6 → 0.5.7

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
@@ -1144,96 +1144,7 @@ function tryDetectDecision(opts) {
1144
1144
  };
1145
1145
  }
1146
1146
 
1147
- // ../../packages/shared/src/licenseClient.ts
1148
- var BASE_URL = "https://api.lemonsqueezy.com/v1/licenses";
1149
- var REQUEST_TIMEOUT_MS = 15e3;
1150
- var EXPECTED_STORE_ID = 301555;
1151
- var EXPECTED_PRODUCT_ID = 864311;
1152
- function fetchWithTimeout(url, init) {
1153
- const controller = new AbortController();
1154
- const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
1155
- return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
1156
- }
1157
- function validateProductIdentity(meta) {
1158
- if (!meta) return "License response missing product metadata.";
1159
- if (meta.store_id !== EXPECTED_STORE_ID || meta.product_id !== EXPECTED_PRODUCT_ID) {
1160
- return "This license key does not belong to KeepGoing.";
1161
- }
1162
- return void 0;
1163
- }
1164
- async function safeJson(res) {
1165
- try {
1166
- const text = await res.text();
1167
- return JSON.parse(text);
1168
- } catch {
1169
- return null;
1170
- }
1171
- }
1172
- async function activateLicense(licenseKey, instanceName, options) {
1173
- try {
1174
- const res = await fetchWithTimeout(`${BASE_URL}/activate`, {
1175
- method: "POST",
1176
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1177
- body: new URLSearchParams({ license_key: licenseKey, instance_name: instanceName })
1178
- });
1179
- const data = await safeJson(res);
1180
- if (!res.ok || !data?.activated) {
1181
- return { valid: false, error: data?.error || `Activation failed (${res.status})` };
1182
- }
1183
- if (!options?.allowTestMode && data.license_key?.test_mode) {
1184
- if (data.license_key?.key && data.instance?.id) {
1185
- await deactivateLicense(data.license_key.key, data.instance.id);
1186
- }
1187
- return { valid: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
1188
- }
1189
- if (!options?.allowTestMode) {
1190
- const productError = validateProductIdentity(data.meta);
1191
- if (productError) {
1192
- if (data.license_key?.key && data.instance?.id) {
1193
- await deactivateLicense(data.license_key.key, data.instance.id);
1194
- }
1195
- return { valid: false, error: productError };
1196
- }
1197
- if (data.meta?.variant_id && !KNOWN_VARIANT_IDS.has(data.meta.variant_id)) {
1198
- if (data.license_key?.key && data.instance?.id) {
1199
- await deactivateLicense(data.license_key.key, data.instance.id);
1200
- }
1201
- return { valid: false, error: "This license key is for an unrecognized add-on variant. Please update KeepGoing or contact support." };
1202
- }
1203
- }
1204
- return {
1205
- valid: true,
1206
- licenseKey: data.license_key?.key,
1207
- instanceId: data.instance?.id,
1208
- customerName: data.meta?.customer_name,
1209
- productName: data.meta?.product_name,
1210
- variantId: data.meta?.variant_id,
1211
- variantName: data.meta?.variant_name
1212
- };
1213
- } catch (err) {
1214
- 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";
1215
- return { valid: false, error: message };
1216
- }
1217
- }
1218
- async function deactivateLicense(licenseKey, instanceId) {
1219
- try {
1220
- const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
1221
- method: "POST",
1222
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1223
- body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
1224
- });
1225
- const data = await safeJson(res);
1226
- if (!res.ok || !data?.deactivated) {
1227
- return { deactivated: false, error: data?.error || `Deactivation failed (${res.status})` };
1228
- }
1229
- return { deactivated: true };
1230
- } catch (err) {
1231
- 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";
1232
- return { deactivated: false, error: message };
1233
- }
1234
- }
1235
-
1236
- // src/storage.ts
1147
+ // ../../packages/shared/src/reader.ts
1237
1148
  import fs4 from "fs";
1238
1149
  import path5 from "path";
1239
1150
  var STORAGE_DIR3 = ".keepgoing";
@@ -1515,6 +1426,280 @@ var KeepGoingReader = class {
1515
1426
  }
1516
1427
  };
1517
1428
 
1429
+ // ../../packages/shared/src/setup.ts
1430
+ import fs5 from "fs";
1431
+ import os2 from "os";
1432
+ import path6 from "path";
1433
+ var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
1434
+ var SESSION_START_HOOK = {
1435
+ matcher: "",
1436
+ hooks: [
1437
+ {
1438
+ type: "command",
1439
+ command: "npx -y @keepgoingdev/mcp-server --print-momentum"
1440
+ }
1441
+ ]
1442
+ };
1443
+ var STOP_HOOK = {
1444
+ matcher: "",
1445
+ hooks: [
1446
+ {
1447
+ type: "command",
1448
+ command: "npx -y @keepgoingdev/mcp-server --save-checkpoint"
1449
+ }
1450
+ ]
1451
+ };
1452
+ var POST_TOOL_USE_HOOK = {
1453
+ matcher: "Edit|Write|MultiEdit",
1454
+ hooks: [
1455
+ {
1456
+ type: "command",
1457
+ command: "npx -y @keepgoingdev/mcp-server --update-task-from-hook"
1458
+ }
1459
+ ]
1460
+ };
1461
+ var CLAUDE_MD_SECTION = `
1462
+ ## KeepGoing
1463
+
1464
+ After completing a task or meaningful piece of work, call the \`save_checkpoint\` MCP tool with:
1465
+ - \`summary\`: What you accomplished
1466
+ - \`nextStep\`: What should be done next
1467
+ - \`blocker\`: Any blocker (if applicable)
1468
+ `;
1469
+ var STATUSLINE_CMD = "npx -y @keepgoingdev/mcp-server --statusline";
1470
+ function hasKeepGoingHook(hookEntries) {
1471
+ return hookEntries.some(
1472
+ (entry) => entry?.hooks?.some((h) => typeof h?.command === "string" && h.command.includes(KEEPGOING_MARKER))
1473
+ );
1474
+ }
1475
+ function resolveScopePaths(scope, workspacePath) {
1476
+ if (scope === "user") {
1477
+ const claudeDir2 = path6.join(os2.homedir(), ".claude");
1478
+ return {
1479
+ claudeDir: claudeDir2,
1480
+ settingsPath: path6.join(claudeDir2, "settings.json"),
1481
+ claudeMdPath: path6.join(claudeDir2, "CLAUDE.md")
1482
+ };
1483
+ }
1484
+ const claudeDir = path6.join(workspacePath, ".claude");
1485
+ const dotClaudeMdPath = path6.join(workspacePath, ".claude", "CLAUDE.md");
1486
+ const rootClaudeMdPath = path6.join(workspacePath, "CLAUDE.md");
1487
+ return {
1488
+ claudeDir,
1489
+ settingsPath: path6.join(claudeDir, "settings.json"),
1490
+ claudeMdPath: fs5.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath
1491
+ };
1492
+ }
1493
+ function writeHooksToSettings(settings) {
1494
+ let changed = false;
1495
+ if (!settings.hooks) {
1496
+ settings.hooks = {};
1497
+ }
1498
+ if (!Array.isArray(settings.hooks.SessionStart)) {
1499
+ settings.hooks.SessionStart = [];
1500
+ }
1501
+ if (!hasKeepGoingHook(settings.hooks.SessionStart)) {
1502
+ settings.hooks.SessionStart.push(SESSION_START_HOOK);
1503
+ changed = true;
1504
+ }
1505
+ if (!Array.isArray(settings.hooks.Stop)) {
1506
+ settings.hooks.Stop = [];
1507
+ }
1508
+ if (!hasKeepGoingHook(settings.hooks.Stop)) {
1509
+ settings.hooks.Stop.push(STOP_HOOK);
1510
+ changed = true;
1511
+ }
1512
+ if (!Array.isArray(settings.hooks.PostToolUse)) {
1513
+ settings.hooks.PostToolUse = [];
1514
+ }
1515
+ if (!hasKeepGoingHook(settings.hooks.PostToolUse)) {
1516
+ settings.hooks.PostToolUse.push(POST_TOOL_USE_HOOK);
1517
+ changed = true;
1518
+ }
1519
+ return changed;
1520
+ }
1521
+ function checkHookConflict(scope, workspacePath) {
1522
+ const otherPaths = resolveScopePaths(scope === "user" ? "project" : "user", workspacePath);
1523
+ if (!fs5.existsSync(otherPaths.settingsPath)) {
1524
+ return null;
1525
+ }
1526
+ try {
1527
+ const otherSettings = JSON.parse(fs5.readFileSync(otherPaths.settingsPath, "utf-8"));
1528
+ const hooks = otherSettings?.hooks;
1529
+ if (!hooks) return null;
1530
+ const hasConflict = Array.isArray(hooks.SessionStart) && hasKeepGoingHook(hooks.SessionStart) || Array.isArray(hooks.Stop) && hasKeepGoingHook(hooks.Stop);
1531
+ if (hasConflict) {
1532
+ const otherScope = scope === "user" ? "project" : "user";
1533
+ const otherFile = otherPaths.settingsPath;
1534
+ return `KeepGoing hooks are also configured at ${otherScope} scope (${otherFile}). Having hooks at both scopes may cause them to fire twice. Consider removing the ${otherScope}-level hooks if you want to use ${scope}-level only.`;
1535
+ }
1536
+ } catch {
1537
+ }
1538
+ return null;
1539
+ }
1540
+ function setupProject(options) {
1541
+ const {
1542
+ workspacePath,
1543
+ scope = "project",
1544
+ sessionHooks = true,
1545
+ claudeMd = true,
1546
+ hasProLicense = false,
1547
+ statusline
1548
+ } = options;
1549
+ const messages = [];
1550
+ let changed = false;
1551
+ const { claudeDir, settingsPath, claudeMdPath } = resolveScopePaths(scope, workspacePath);
1552
+ const scopeLabel = scope === "user" ? "~/.claude/settings.json" : ".claude/settings.json";
1553
+ let settings = {};
1554
+ if (fs5.existsSync(settingsPath)) {
1555
+ settings = JSON.parse(fs5.readFileSync(settingsPath, "utf-8"));
1556
+ }
1557
+ let settingsChanged = false;
1558
+ if (sessionHooks) {
1559
+ const hooksChanged = writeHooksToSettings(settings);
1560
+ settingsChanged = hooksChanged;
1561
+ if (hooksChanged) {
1562
+ messages.push(`Session hooks: Added to ${scopeLabel}`);
1563
+ } else {
1564
+ messages.push("Session hooks: Already present, skipped");
1565
+ }
1566
+ const conflict = checkHookConflict(scope, workspacePath);
1567
+ if (conflict) {
1568
+ messages.push(`Warning: ${conflict}`);
1569
+ }
1570
+ }
1571
+ if (scope === "project" && hasProLicense) {
1572
+ const needsUpdate = settings.statusLine?.command && statusline?.isLegacy?.(settings.statusLine.command);
1573
+ if (!settings.statusLine || needsUpdate) {
1574
+ settings.statusLine = {
1575
+ type: "command",
1576
+ command: STATUSLINE_CMD
1577
+ };
1578
+ settingsChanged = true;
1579
+ messages.push(needsUpdate ? "Statusline: Migrated to auto-updating npx command" : "Statusline: Added to .claude/settings.json");
1580
+ } else {
1581
+ messages.push("Statusline: Already configured in settings, skipped");
1582
+ }
1583
+ statusline?.cleanup?.();
1584
+ }
1585
+ if (settingsChanged) {
1586
+ if (!fs5.existsSync(claudeDir)) {
1587
+ fs5.mkdirSync(claudeDir, { recursive: true });
1588
+ }
1589
+ fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1590
+ changed = true;
1591
+ }
1592
+ if (claudeMd) {
1593
+ let existing = "";
1594
+ if (fs5.existsSync(claudeMdPath)) {
1595
+ existing = fs5.readFileSync(claudeMdPath, "utf-8");
1596
+ }
1597
+ const mdLabel = scope === "user" ? "~/.claude/CLAUDE.md" : "CLAUDE.md";
1598
+ if (existing.includes("## KeepGoing")) {
1599
+ messages.push(`CLAUDE.md: KeepGoing section already present in ${mdLabel}, skipped`);
1600
+ } else {
1601
+ const updated = existing + CLAUDE_MD_SECTION;
1602
+ const mdDir = path6.dirname(claudeMdPath);
1603
+ if (!fs5.existsSync(mdDir)) {
1604
+ fs5.mkdirSync(mdDir, { recursive: true });
1605
+ }
1606
+ fs5.writeFileSync(claudeMdPath, updated);
1607
+ changed = true;
1608
+ messages.push(`CLAUDE.md: Added KeepGoing section to ${mdLabel}`);
1609
+ }
1610
+ }
1611
+ return { messages, changed };
1612
+ }
1613
+
1614
+ // ../../packages/shared/src/licenseClient.ts
1615
+ var BASE_URL = "https://api.lemonsqueezy.com/v1/licenses";
1616
+ var REQUEST_TIMEOUT_MS = 15e3;
1617
+ var EXPECTED_STORE_ID = 301555;
1618
+ var EXPECTED_PRODUCT_ID = 864311;
1619
+ function fetchWithTimeout(url, init) {
1620
+ const controller = new AbortController();
1621
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
1622
+ return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
1623
+ }
1624
+ function validateProductIdentity(meta) {
1625
+ if (!meta) return "License response missing product metadata.";
1626
+ if (meta.store_id !== EXPECTED_STORE_ID || meta.product_id !== EXPECTED_PRODUCT_ID) {
1627
+ return "This license key does not belong to KeepGoing.";
1628
+ }
1629
+ return void 0;
1630
+ }
1631
+ async function safeJson(res) {
1632
+ try {
1633
+ const text = await res.text();
1634
+ return JSON.parse(text);
1635
+ } catch {
1636
+ return null;
1637
+ }
1638
+ }
1639
+ async function activateLicense(licenseKey, instanceName, options) {
1640
+ try {
1641
+ const res = await fetchWithTimeout(`${BASE_URL}/activate`, {
1642
+ method: "POST",
1643
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1644
+ body: new URLSearchParams({ license_key: licenseKey, instance_name: instanceName })
1645
+ });
1646
+ const data = await safeJson(res);
1647
+ if (!res.ok || !data?.activated) {
1648
+ return { valid: false, error: data?.error || `Activation failed (${res.status})` };
1649
+ }
1650
+ if (!options?.allowTestMode && data.license_key?.test_mode) {
1651
+ if (data.license_key?.key && data.instance?.id) {
1652
+ await deactivateLicense(data.license_key.key, data.instance.id);
1653
+ }
1654
+ return { valid: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
1655
+ }
1656
+ if (!options?.allowTestMode) {
1657
+ const productError = validateProductIdentity(data.meta);
1658
+ if (productError) {
1659
+ if (data.license_key?.key && data.instance?.id) {
1660
+ await deactivateLicense(data.license_key.key, data.instance.id);
1661
+ }
1662
+ return { valid: false, error: productError };
1663
+ }
1664
+ if (data.meta?.variant_id && !KNOWN_VARIANT_IDS.has(data.meta.variant_id)) {
1665
+ if (data.license_key?.key && data.instance?.id) {
1666
+ await deactivateLicense(data.license_key.key, data.instance.id);
1667
+ }
1668
+ return { valid: false, error: "This license key is for an unrecognized add-on variant. Please update KeepGoing or contact support." };
1669
+ }
1670
+ }
1671
+ return {
1672
+ valid: true,
1673
+ licenseKey: data.license_key?.key,
1674
+ instanceId: data.instance?.id,
1675
+ customerName: data.meta?.customer_name,
1676
+ productName: data.meta?.product_name,
1677
+ variantId: data.meta?.variant_id,
1678
+ variantName: data.meta?.variant_name
1679
+ };
1680
+ } catch (err) {
1681
+ 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";
1682
+ return { valid: false, error: message };
1683
+ }
1684
+ }
1685
+ async function deactivateLicense(licenseKey, instanceId) {
1686
+ try {
1687
+ const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
1688
+ method: "POST",
1689
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1690
+ body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
1691
+ });
1692
+ const data = await safeJson(res);
1693
+ if (!res.ok || !data?.deactivated) {
1694
+ return { deactivated: false, error: data?.error || `Deactivation failed (${res.status})` };
1695
+ }
1696
+ return { deactivated: true };
1697
+ } catch (err) {
1698
+ 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";
1699
+ return { deactivated: false, error: message };
1700
+ }
1701
+ }
1702
+
1518
1703
  // src/tools/getMomentum.ts
1519
1704
  function registerGetMomentum(server, reader, workspacePath) {
1520
1705
  server.tool(
@@ -1732,7 +1917,7 @@ function registerGetReentryBriefing(server, reader, workspacePath) {
1732
1917
  }
1733
1918
 
1734
1919
  // src/tools/saveCheckpoint.ts
1735
- import path6 from "path";
1920
+ import path7 from "path";
1736
1921
  import { z as z2 } from "zod";
1737
1922
  function registerSaveCheckpoint(server, reader, workspacePath) {
1738
1923
  server.tool(
@@ -1748,7 +1933,7 @@ function registerSaveCheckpoint(server, reader, workspacePath) {
1748
1933
  const gitBranch = getCurrentBranch(workspacePath);
1749
1934
  const touchedFiles = getTouchedFiles(workspacePath);
1750
1935
  const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);
1751
- const projectName = path6.basename(resolveStorageRoot(workspacePath));
1936
+ const projectName = path7.basename(resolveStorageRoot(workspacePath));
1752
1937
  const sessionId = generateSessionId({ workspaceRoot: workspacePath, branch: gitBranch ?? void 0, worktreePath: workspacePath });
1753
1938
  const checkpoint = createCheckpoint({
1754
1939
  summary,
@@ -1960,31 +2145,28 @@ function registerGetCurrentTask(server, reader) {
1960
2145
  }
1961
2146
 
1962
2147
  // src/tools/setupProject.ts
1963
- import fs6 from "fs";
1964
- import os3 from "os";
1965
- import path8 from "path";
1966
2148
  import { z as z4 } from "zod";
1967
2149
 
1968
2150
  // src/cli/migrate.ts
1969
- import fs5 from "fs";
1970
- import os2 from "os";
1971
- import path7 from "path";
1972
- var STATUSLINE_CMD = "npx -y @keepgoingdev/mcp-server --statusline";
2151
+ import fs6 from "fs";
2152
+ import os3 from "os";
2153
+ import path8 from "path";
2154
+ var STATUSLINE_CMD2 = "npx -y @keepgoingdev/mcp-server --statusline";
1973
2155
  function isLegacyStatusline(command) {
1974
2156
  return !command.includes("--statusline") && command.includes("keepgoing-statusline");
1975
2157
  }
1976
2158
  function migrateStatusline(wsPath) {
1977
- const settingsPath = path7.join(wsPath, ".claude", "settings.json");
1978
- if (!fs5.existsSync(settingsPath)) return void 0;
2159
+ const settingsPath = path8.join(wsPath, ".claude", "settings.json");
2160
+ if (!fs6.existsSync(settingsPath)) return void 0;
1979
2161
  try {
1980
- const settings = JSON.parse(fs5.readFileSync(settingsPath, "utf-8"));
2162
+ const settings = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
1981
2163
  const cmd = settings.statusLine?.command;
1982
2164
  if (!cmd || !isLegacyStatusline(cmd)) return void 0;
1983
2165
  settings.statusLine = {
1984
2166
  type: "command",
1985
- command: STATUSLINE_CMD
2167
+ command: STATUSLINE_CMD2
1986
2168
  };
1987
- fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
2169
+ fs6.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1988
2170
  cleanupLegacyScript();
1989
2171
  return "[KeepGoing] Migrated statusline to auto-updating command (restart Claude Code to apply)";
1990
2172
  } catch {
@@ -1992,122 +2174,16 @@ function migrateStatusline(wsPath) {
1992
2174
  }
1993
2175
  }
1994
2176
  function cleanupLegacyScript() {
1995
- const legacyScript = path7.join(os2.homedir(), ".claude", "keepgoing-statusline.sh");
1996
- if (fs5.existsSync(legacyScript)) {
2177
+ const legacyScript = path8.join(os3.homedir(), ".claude", "keepgoing-statusline.sh");
2178
+ if (fs6.existsSync(legacyScript)) {
1997
2179
  try {
1998
- fs5.unlinkSync(legacyScript);
2180
+ fs6.unlinkSync(legacyScript);
1999
2181
  } catch {
2000
2182
  }
2001
2183
  }
2002
2184
  }
2003
2185
 
2004
2186
  // src/tools/setupProject.ts
2005
- var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
2006
- var SESSION_START_HOOK = {
2007
- matcher: "",
2008
- hooks: [
2009
- {
2010
- type: "command",
2011
- command: "npx -y @keepgoingdev/mcp-server --print-momentum"
2012
- }
2013
- ]
2014
- };
2015
- var STOP_HOOK = {
2016
- matcher: "",
2017
- hooks: [
2018
- {
2019
- type: "command",
2020
- command: "npx -y @keepgoingdev/mcp-server --save-checkpoint"
2021
- }
2022
- ]
2023
- };
2024
- var POST_TOOL_USE_HOOK = {
2025
- matcher: "Edit|Write|MultiEdit",
2026
- hooks: [
2027
- {
2028
- type: "command",
2029
- command: "npx -y @keepgoingdev/mcp-server --update-task-from-hook"
2030
- }
2031
- ]
2032
- };
2033
- var CLAUDE_MD_SECTION = `
2034
- ## KeepGoing
2035
-
2036
- After completing a task or meaningful piece of work, call the \`save_checkpoint\` MCP tool with:
2037
- - \`summary\`: What you accomplished
2038
- - \`nextStep\`: What should be done next
2039
- - \`blocker\`: Any blocker (if applicable)
2040
- `;
2041
- function hasKeepGoingHook(hookEntries) {
2042
- return hookEntries.some(
2043
- (entry) => entry?.hooks?.some((h) => typeof h?.command === "string" && h.command.includes(KEEPGOING_MARKER))
2044
- );
2045
- }
2046
- function resolveScopePaths(scope, workspacePath) {
2047
- if (scope === "user") {
2048
- const claudeDir2 = path8.join(os3.homedir(), ".claude");
2049
- return {
2050
- claudeDir: claudeDir2,
2051
- settingsPath: path8.join(claudeDir2, "settings.json"),
2052
- claudeMdPath: path8.join(claudeDir2, "CLAUDE.md")
2053
- };
2054
- }
2055
- const claudeDir = path8.join(workspacePath, ".claude");
2056
- const dotClaudeMdPath = path8.join(workspacePath, ".claude", "CLAUDE.md");
2057
- const rootClaudeMdPath = path8.join(workspacePath, "CLAUDE.md");
2058
- return {
2059
- claudeDir,
2060
- settingsPath: path8.join(claudeDir, "settings.json"),
2061
- claudeMdPath: fs6.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath
2062
- };
2063
- }
2064
- function writeHooksToSettings(settings) {
2065
- let changed = false;
2066
- if (!settings.hooks) {
2067
- settings.hooks = {};
2068
- }
2069
- if (!Array.isArray(settings.hooks.SessionStart)) {
2070
- settings.hooks.SessionStart = [];
2071
- }
2072
- if (!hasKeepGoingHook(settings.hooks.SessionStart)) {
2073
- settings.hooks.SessionStart.push(SESSION_START_HOOK);
2074
- changed = true;
2075
- }
2076
- if (!Array.isArray(settings.hooks.Stop)) {
2077
- settings.hooks.Stop = [];
2078
- }
2079
- if (!hasKeepGoingHook(settings.hooks.Stop)) {
2080
- settings.hooks.Stop.push(STOP_HOOK);
2081
- changed = true;
2082
- }
2083
- if (!Array.isArray(settings.hooks.PostToolUse)) {
2084
- settings.hooks.PostToolUse = [];
2085
- }
2086
- if (!hasKeepGoingHook(settings.hooks.PostToolUse)) {
2087
- settings.hooks.PostToolUse.push(POST_TOOL_USE_HOOK);
2088
- changed = true;
2089
- }
2090
- return changed;
2091
- }
2092
- function checkHookConflict(scope, workspacePath) {
2093
- const otherPaths = resolveScopePaths(scope === "user" ? "project" : "user", workspacePath);
2094
- if (!fs6.existsSync(otherPaths.settingsPath)) {
2095
- return null;
2096
- }
2097
- try {
2098
- const otherSettings = JSON.parse(fs6.readFileSync(otherPaths.settingsPath, "utf-8"));
2099
- const hooks = otherSettings?.hooks;
2100
- if (!hooks) return null;
2101
- const hasConflict = Array.isArray(hooks.SessionStart) && hasKeepGoingHook(hooks.SessionStart) || Array.isArray(hooks.Stop) && hasKeepGoingHook(hooks.Stop);
2102
- if (hasConflict) {
2103
- const otherScope = scope === "user" ? "project" : "user";
2104
- const otherFile = otherPaths.settingsPath;
2105
- return `**Warning:** KeepGoing hooks are also configured at ${otherScope} scope (\`${otherFile}\`). Having hooks at both scopes may cause them to fire twice. Consider removing the ${otherScope}-level hooks if you want to use ${scope}-level only.`;
2106
- }
2107
- } catch {
2108
- }
2109
- return null;
2110
- }
2111
2187
  function registerSetupProject(server, workspacePath) {
2112
2188
  server.tool(
2113
2189
  "setup_project",
@@ -2118,69 +2194,23 @@ function registerSetupProject(server, workspacePath) {
2118
2194
  scope: z4.enum(["project", "user"]).optional().default("project").describe('Where to write config: "user" for global (~/.claude/), "project" for per-project (.claude/)')
2119
2195
  },
2120
2196
  async ({ sessionHooks, claudeMd, scope }) => {
2121
- const results = [];
2122
- const { claudeDir, settingsPath, claudeMdPath } = resolveScopePaths(scope, workspacePath);
2123
- const scopeLabel = scope === "user" ? "`~/.claude/settings.json`" : "`.claude/settings.json`";
2124
- let settings = {};
2125
- if (fs6.existsSync(settingsPath)) {
2126
- settings = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
2127
- }
2128
- let settingsChanged = false;
2129
- if (sessionHooks) {
2130
- const hooksChanged = writeHooksToSettings(settings);
2131
- settingsChanged = hooksChanged;
2132
- if (hooksChanged) {
2133
- results.push(`**Session hooks:** Added to ${scopeLabel}`);
2134
- } else {
2135
- results.push("**Session hooks:** Already present, skipped");
2136
- }
2137
- const conflict = checkHookConflict(scope, workspacePath);
2138
- if (conflict) {
2139
- results.push(conflict);
2197
+ const hasProLicense = process.env.KEEPGOING_PRO_BYPASS === "1" || !!getLicenseForFeature("session-awareness");
2198
+ const result = setupProject({
2199
+ workspacePath,
2200
+ scope,
2201
+ sessionHooks,
2202
+ claudeMd,
2203
+ hasProLicense,
2204
+ statusline: {
2205
+ isLegacy: isLegacyStatusline,
2206
+ cleanup: cleanupLegacyScript
2140
2207
  }
2141
- }
2142
- if (scope === "project") {
2143
- if (process.env.KEEPGOING_PRO_BYPASS === "1" || getLicenseForFeature("session-awareness")) {
2144
- const needsUpdate = settings.statusLine?.command && isLegacyStatusline(settings.statusLine.command);
2145
- if (!settings.statusLine || needsUpdate) {
2146
- settings.statusLine = {
2147
- type: "command",
2148
- command: STATUSLINE_CMD
2149
- };
2150
- settingsChanged = true;
2151
- results.push(needsUpdate ? "**Statusline:** Migrated to auto-updating `npx` command" : "**Statusline:** Added to `.claude/settings.json`");
2152
- } else {
2153
- results.push("**Statusline:** `statusLine` already configured in settings, skipped");
2154
- }
2155
- cleanupLegacyScript();
2156
- }
2157
- }
2158
- if (settingsChanged) {
2159
- if (!fs6.existsSync(claudeDir)) {
2160
- fs6.mkdirSync(claudeDir, { recursive: true });
2161
- }
2162
- fs6.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
2163
- }
2164
- if (claudeMd) {
2165
- let existing = "";
2166
- if (fs6.existsSync(claudeMdPath)) {
2167
- existing = fs6.readFileSync(claudeMdPath, "utf-8");
2168
- }
2169
- const mdLabel = scope === "user" ? "`~/.claude/CLAUDE.md`" : "`CLAUDE.md`";
2170
- if (existing.includes("## KeepGoing")) {
2171
- results.push(`**CLAUDE.md:** KeepGoing section already present in ${mdLabel}, skipped`);
2172
- } else {
2173
- const updated = existing + CLAUDE_MD_SECTION;
2174
- const mdDir = path8.dirname(claudeMdPath);
2175
- if (!fs6.existsSync(mdDir)) {
2176
- fs6.mkdirSync(mdDir, { recursive: true });
2177
- }
2178
- fs6.writeFileSync(claudeMdPath, updated);
2179
- results.push(`**CLAUDE.md:** Added KeepGoing section to ${mdLabel}`);
2180
- }
2181
- }
2208
+ });
2209
+ const formatted = result.messages.map((msg) => {
2210
+ return msg.replace(/^([^:]+:)/, "**$1**");
2211
+ });
2182
2212
  return {
2183
- content: [{ type: "text", text: results.join("\n") }]
2213
+ content: [{ type: "text", text: formatted.join("\n") }]
2184
2214
  };
2185
2215
  }
2186
2216
  );