@keepgoingdev/mcp-server 0.7.0 → 0.7.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.
- package/dist/index.js +180 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -78,7 +78,8 @@ function findGitRoot(startPath) {
|
|
|
78
78
|
const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
79
79
|
cwd: startPath,
|
|
80
80
|
encoding: "utf-8",
|
|
81
|
-
timeout: 5e3
|
|
81
|
+
timeout: 5e3,
|
|
82
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
82
83
|
}).trim();
|
|
83
84
|
return toplevel || startPath;
|
|
84
85
|
} catch {
|
|
@@ -95,12 +96,14 @@ function resolveStorageRoot(startPath) {
|
|
|
95
96
|
const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
96
97
|
cwd: startPath,
|
|
97
98
|
encoding: "utf-8",
|
|
98
|
-
timeout: 5e3
|
|
99
|
+
timeout: 5e3,
|
|
100
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
99
101
|
}).trim();
|
|
100
102
|
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
101
103
|
cwd: startPath,
|
|
102
104
|
encoding: "utf-8",
|
|
103
|
-
timeout: 5e3
|
|
105
|
+
timeout: 5e3,
|
|
106
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
104
107
|
}).trim();
|
|
105
108
|
const absoluteCommonDir = path.resolve(toplevel, commonDir);
|
|
106
109
|
const mainRoot = path.dirname(absoluteCommonDir);
|
|
@@ -1214,7 +1217,14 @@ function getLicenseForFeature(feature) {
|
|
|
1214
1217
|
return features?.includes(feature);
|
|
1215
1218
|
});
|
|
1216
1219
|
}
|
|
1220
|
+
function getAllLicensesNeedingRevalidation() {
|
|
1221
|
+
return getActiveLicenses().filter((l) => needsRevalidation(l));
|
|
1222
|
+
}
|
|
1217
1223
|
var REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
|
|
1224
|
+
function needsRevalidation(entry) {
|
|
1225
|
+
const lastValidated = new Date(entry.lastValidatedAt).getTime();
|
|
1226
|
+
return Date.now() - lastValidated > REVALIDATION_THRESHOLD_MS;
|
|
1227
|
+
}
|
|
1218
1228
|
|
|
1219
1229
|
// ../../packages/shared/src/featureGate.ts
|
|
1220
1230
|
var DefaultFeatureGate = class {
|
|
@@ -1835,12 +1845,14 @@ var SESSION_END_HOOK = {
|
|
|
1835
1845
|
}
|
|
1836
1846
|
]
|
|
1837
1847
|
};
|
|
1838
|
-
var KEEPGOING_RULES_VERSION =
|
|
1848
|
+
var KEEPGOING_RULES_VERSION = 2;
|
|
1839
1849
|
var KEEPGOING_RULES_CONTENT = `<!-- @keepgoingdev/mcp-server v${KEEPGOING_RULES_VERSION} -->
|
|
1840
1850
|
## KeepGoing
|
|
1841
1851
|
|
|
1852
|
+
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.
|
|
1853
|
+
|
|
1842
1854
|
After completing a task or meaningful piece of work, call the \`save_checkpoint\` MCP tool with:
|
|
1843
|
-
- \`summary\`: 1-2 sentences. What changed and why
|
|
1855
|
+
- \`summary\`: 1-2 sentences. What changed and why, no file paths, no implementation details (those are captured from git).
|
|
1844
1856
|
- \`nextStep\`: What to do next
|
|
1845
1857
|
- \`blocker\`: Any blocker (if applicable)
|
|
1846
1858
|
`;
|
|
@@ -1966,7 +1978,7 @@ function setupProject(options) {
|
|
|
1966
1978
|
messages.push(`Warning: ${conflict}`);
|
|
1967
1979
|
}
|
|
1968
1980
|
}
|
|
1969
|
-
|
|
1981
|
+
{
|
|
1970
1982
|
const needsUpdate = settings.statusLine?.command && statusline?.isLegacy?.(settings.statusLine.command);
|
|
1971
1983
|
if (!settings.statusLine || needsUpdate) {
|
|
1972
1984
|
settings.statusLine = {
|
|
@@ -1974,7 +1986,7 @@ function setupProject(options) {
|
|
|
1974
1986
|
command: STATUSLINE_CMD
|
|
1975
1987
|
};
|
|
1976
1988
|
settingsChanged = true;
|
|
1977
|
-
messages.push(needsUpdate ? "Statusline: Migrated to auto-updating npx command" :
|
|
1989
|
+
messages.push(needsUpdate ? "Statusline: Migrated to auto-updating npx command" : `Statusline: Added to ${scopeLabel}`);
|
|
1978
1990
|
} else {
|
|
1979
1991
|
messages.push("Statusline: Already configured in settings, skipped");
|
|
1980
1992
|
}
|
|
@@ -2096,6 +2108,39 @@ async function activateLicense(licenseKey, instanceName, options) {
|
|
|
2096
2108
|
return { valid: false, error: message };
|
|
2097
2109
|
}
|
|
2098
2110
|
}
|
|
2111
|
+
async function validateLicense(licenseKey, instanceId, options) {
|
|
2112
|
+
try {
|
|
2113
|
+
const res = await fetchWithTimeout(`${BASE_URL}/validate`, {
|
|
2114
|
+
method: "POST",
|
|
2115
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
2116
|
+
body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
|
|
2117
|
+
});
|
|
2118
|
+
const data = await safeJson(res);
|
|
2119
|
+
if (!res.ok || !data?.valid) {
|
|
2120
|
+
return { valid: false, isNetworkError: false, error: data?.error || `Validation failed (${res.status})` };
|
|
2121
|
+
}
|
|
2122
|
+
if (!options?.allowTestMode && data.license_key?.test_mode) {
|
|
2123
|
+
return { valid: false, isNetworkError: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
|
|
2124
|
+
}
|
|
2125
|
+
if (!options?.allowTestMode) {
|
|
2126
|
+
const productError = validateProductIdentity(data.meta);
|
|
2127
|
+
if (productError) {
|
|
2128
|
+
return { valid: false, isNetworkError: false, error: productError };
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return {
|
|
2132
|
+
valid: true,
|
|
2133
|
+
licenseKey: data.license_key?.key,
|
|
2134
|
+
customerName: data.meta?.customer_name,
|
|
2135
|
+
productName: data.meta?.product_name,
|
|
2136
|
+
variantId: data.meta?.variant_id,
|
|
2137
|
+
variantName: data.meta?.variant_name
|
|
2138
|
+
};
|
|
2139
|
+
} catch (err) {
|
|
2140
|
+
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";
|
|
2141
|
+
return { valid: false, isNetworkError: true, error: message };
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2099
2144
|
async function deactivateLicense(licenseKey, instanceId) {
|
|
2100
2145
|
try {
|
|
2101
2146
|
const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
|
|
@@ -2114,6 +2159,52 @@ async function deactivateLicense(licenseKey, instanceId) {
|
|
|
2114
2159
|
}
|
|
2115
2160
|
}
|
|
2116
2161
|
|
|
2162
|
+
// ../../packages/shared/src/licenseRevalidation.ts
|
|
2163
|
+
async function revalidateStaleLicenses(options) {
|
|
2164
|
+
const stale = getAllLicensesNeedingRevalidation();
|
|
2165
|
+
const result = { checked: stale.length, refreshed: 0, revoked: 0, skippedNetworkError: 0 };
|
|
2166
|
+
if (stale.length === 0) return result;
|
|
2167
|
+
const updates = /* @__PURE__ */ new Map();
|
|
2168
|
+
for (const entry of stale) {
|
|
2169
|
+
const vResult = await validateLicense(entry.licenseKey, entry.instanceId, {
|
|
2170
|
+
allowTestMode: options?.allowTestMode
|
|
2171
|
+
});
|
|
2172
|
+
if (vResult.valid) {
|
|
2173
|
+
const patch = {
|
|
2174
|
+
lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2175
|
+
};
|
|
2176
|
+
if (vResult.customerName) patch.customerName = vResult.customerName;
|
|
2177
|
+
updates.set(entry.licenseKey, patch);
|
|
2178
|
+
result.refreshed++;
|
|
2179
|
+
} else if (vResult.isNetworkError) {
|
|
2180
|
+
result.skippedNetworkError++;
|
|
2181
|
+
} else {
|
|
2182
|
+
updates.set(entry.licenseKey, { status: "inactive" });
|
|
2183
|
+
result.revoked++;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
if (updates.size > 0) {
|
|
2187
|
+
const store = readLicenseStore();
|
|
2188
|
+
for (const [key, patch] of updates) {
|
|
2189
|
+
const idx = store.licenses.findIndex((l) => l.licenseKey === key);
|
|
2190
|
+
if (idx >= 0) {
|
|
2191
|
+
Object.assign(store.licenses[idx], patch);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
writeLicenseStore(store);
|
|
2195
|
+
}
|
|
2196
|
+
return result;
|
|
2197
|
+
}
|
|
2198
|
+
async function getLicenseForFeatureWithRevalidation(feature, options) {
|
|
2199
|
+
const license = getLicenseForFeature(feature);
|
|
2200
|
+
if (!license) return void 0;
|
|
2201
|
+
if (needsRevalidation(license)) {
|
|
2202
|
+
await revalidateStaleLicenses(options);
|
|
2203
|
+
return getLicenseForFeature(feature);
|
|
2204
|
+
}
|
|
2205
|
+
return license;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2117
2208
|
// src/tools/getMomentum.ts
|
|
2118
2209
|
import { z } from "zod";
|
|
2119
2210
|
function registerGetMomentum(server, reader, workspacePath) {
|
|
@@ -2450,7 +2541,7 @@ function registerGetDecisions(server, reader) {
|
|
|
2450
2541
|
]
|
|
2451
2542
|
};
|
|
2452
2543
|
}
|
|
2453
|
-
if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !
|
|
2544
|
+
if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !await getLicenseForFeatureWithRevalidation("decisions")) {
|
|
2454
2545
|
return {
|
|
2455
2546
|
content: [
|
|
2456
2547
|
{
|
|
@@ -2516,7 +2607,7 @@ function registerGetCurrentTask(server, reader) {
|
|
|
2516
2607
|
]
|
|
2517
2608
|
};
|
|
2518
2609
|
}
|
|
2519
|
-
if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !
|
|
2610
|
+
if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !await getLicenseForFeatureWithRevalidation("session-awareness")) {
|
|
2520
2611
|
return {
|
|
2521
2612
|
content: [
|
|
2522
2613
|
{
|
|
@@ -2991,7 +3082,7 @@ async function handlePrintMomentum() {
|
|
|
2991
3082
|
process.exit(0);
|
|
2992
3083
|
}
|
|
2993
3084
|
async function handlePrintCurrent() {
|
|
2994
|
-
if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !
|
|
3085
|
+
if (process.env.KEEPGOING_PRO_BYPASS !== "1" && !await getLicenseForFeatureWithRevalidation("session-awareness")) {
|
|
2995
3086
|
process.exit(0);
|
|
2996
3087
|
}
|
|
2997
3088
|
const wsPath = resolveWsPath();
|
|
@@ -3079,7 +3170,7 @@ async function handleSaveCheckpoint() {
|
|
|
3079
3170
|
branch: gitBranch ?? void 0,
|
|
3080
3171
|
updatedAt: checkpoint.timestamp
|
|
3081
3172
|
});
|
|
3082
|
-
if (process.env.KEEPGOING_PRO_BYPASS === "1" ||
|
|
3173
|
+
if (process.env.KEEPGOING_PRO_BYPASS === "1" || await getLicenseForFeatureWithRevalidation("decisions")) {
|
|
3083
3174
|
for (let i = 0; i < commitHashes.length; i++) {
|
|
3084
3175
|
const hash = commitHashes[i];
|
|
3085
3176
|
const message = commitMessages[i];
|
|
@@ -3104,7 +3195,8 @@ async function handleSaveCheckpoint() {
|
|
|
3104
3195
|
|
|
3105
3196
|
// src/cli/transcriptUtils.ts
|
|
3106
3197
|
import fs8 from "fs";
|
|
3107
|
-
var TAIL_READ_BYTES =
|
|
3198
|
+
var TAIL_READ_BYTES = 32768;
|
|
3199
|
+
var LATEST_LABEL_READ_BYTES = 65536;
|
|
3108
3200
|
var TOOL_VERB_MAP = {
|
|
3109
3201
|
Edit: "editing",
|
|
3110
3202
|
MultiEdit: "editing",
|
|
@@ -3116,7 +3208,12 @@ var TOOL_VERB_MAP = {
|
|
|
3116
3208
|
Agent: "delegating",
|
|
3117
3209
|
WebFetch: "browsing",
|
|
3118
3210
|
WebSearch: "browsing",
|
|
3119
|
-
TodoWrite: "planning"
|
|
3211
|
+
TodoWrite: "planning",
|
|
3212
|
+
AskUserQuestion: "discussing",
|
|
3213
|
+
EnterPlanMode: "planning",
|
|
3214
|
+
ExitPlanMode: "planning",
|
|
3215
|
+
TaskCreate: "planning",
|
|
3216
|
+
TaskUpdate: "planning"
|
|
3120
3217
|
};
|
|
3121
3218
|
function truncateAtWord(text, max) {
|
|
3122
3219
|
if (text.length <= max) return text;
|
|
@@ -3184,6 +3281,50 @@ function extractSessionLabel(transcriptPath) {
|
|
|
3184
3281
|
}
|
|
3185
3282
|
return null;
|
|
3186
3283
|
}
|
|
3284
|
+
function extractLatestUserLabel(transcriptPath) {
|
|
3285
|
+
if (!transcriptPath || !fs8.existsSync(transcriptPath)) return null;
|
|
3286
|
+
try {
|
|
3287
|
+
const stat = fs8.statSync(transcriptPath);
|
|
3288
|
+
const fileSize = stat.size;
|
|
3289
|
+
if (fileSize === 0) return null;
|
|
3290
|
+
const readSize = Math.min(fileSize, LATEST_LABEL_READ_BYTES);
|
|
3291
|
+
const offset = fileSize - readSize;
|
|
3292
|
+
const buf = Buffer.alloc(readSize);
|
|
3293
|
+
const fd = fs8.openSync(transcriptPath, "r");
|
|
3294
|
+
try {
|
|
3295
|
+
fs8.readSync(fd, buf, 0, readSize, offset);
|
|
3296
|
+
} finally {
|
|
3297
|
+
fs8.closeSync(fd);
|
|
3298
|
+
}
|
|
3299
|
+
const tail = buf.toString("utf-8");
|
|
3300
|
+
const lines = tail.split("\n").reverse();
|
|
3301
|
+
for (const line of lines) {
|
|
3302
|
+
const trimmed = line.trim();
|
|
3303
|
+
if (!trimmed) continue;
|
|
3304
|
+
let entry;
|
|
3305
|
+
try {
|
|
3306
|
+
entry = JSON.parse(trimmed);
|
|
3307
|
+
} catch {
|
|
3308
|
+
continue;
|
|
3309
|
+
}
|
|
3310
|
+
if (!isUserEntry(entry)) continue;
|
|
3311
|
+
let text = extractTextFromContent(entry.message?.content);
|
|
3312
|
+
if (!text) continue;
|
|
3313
|
+
if (text.startsWith("[") || /^<[a-z][\w-]*>/.test(text)) continue;
|
|
3314
|
+
text = text.replace(/@[\w./\-]+/g, "").trim();
|
|
3315
|
+
text = text.replace(FILLER_PREFIX_RE, "").trim();
|
|
3316
|
+
text = text.replace(MARKDOWN_HEADING_RE, "").trim();
|
|
3317
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
3318
|
+
if (text.length < 20) continue;
|
|
3319
|
+
if (text.length > 80) {
|
|
3320
|
+
text = text.slice(0, 80);
|
|
3321
|
+
}
|
|
3322
|
+
return text;
|
|
3323
|
+
}
|
|
3324
|
+
} catch {
|
|
3325
|
+
}
|
|
3326
|
+
return null;
|
|
3327
|
+
}
|
|
3187
3328
|
function extractCurrentAction(transcriptPath) {
|
|
3188
3329
|
if (!transcriptPath || !fs8.existsSync(transcriptPath)) return null;
|
|
3189
3330
|
try {
|
|
@@ -3324,6 +3465,9 @@ async function handleStatusline() {
|
|
|
3324
3465
|
if (input.agent?.name) {
|
|
3325
3466
|
label = input.agent.name;
|
|
3326
3467
|
}
|
|
3468
|
+
if (!label && transcriptPath) {
|
|
3469
|
+
label = extractLatestUserLabel(transcriptPath);
|
|
3470
|
+
}
|
|
3327
3471
|
if (!label) {
|
|
3328
3472
|
try {
|
|
3329
3473
|
const gitRoot = findGitRoot(dir);
|
|
@@ -3342,7 +3486,28 @@ async function handleStatusline() {
|
|
|
3342
3486
|
if (!label && transcriptPath) {
|
|
3343
3487
|
label = extractSessionLabel(transcriptPath);
|
|
3344
3488
|
}
|
|
3345
|
-
if (!label)
|
|
3489
|
+
if (!label) {
|
|
3490
|
+
try {
|
|
3491
|
+
const gitRoot = findGitRoot(dir);
|
|
3492
|
+
const reader = new KeepGoingReader(gitRoot);
|
|
3493
|
+
if (reader.exists()) {
|
|
3494
|
+
const recent = reader.getScopedRecentSessions(10);
|
|
3495
|
+
const last = recent.find((s) => s.source !== "auto") ?? recent[0];
|
|
3496
|
+
if (last) {
|
|
3497
|
+
const ago = formatRelativeTime(last.timestamp);
|
|
3498
|
+
const summary = last.summary ? truncateAtWord(last.summary, 40) : null;
|
|
3499
|
+
const next = last.nextStep ? truncateAtWord(last.nextStep, 30) : null;
|
|
3500
|
+
const parts = [`[KG] ${ago}`];
|
|
3501
|
+
if (summary) parts.push(summary);
|
|
3502
|
+
if (next) parts.push(`\u2192 ${next}`);
|
|
3503
|
+
process.stdout.write(`${parts.join(" \xB7 ")}
|
|
3504
|
+
`);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
} catch {
|
|
3508
|
+
}
|
|
3509
|
+
process.exit(0);
|
|
3510
|
+
}
|
|
3346
3511
|
const action = transcriptPath ? extractCurrentAction(transcriptPath) : null;
|
|
3347
3512
|
const budget = action ? 40 : 55;
|
|
3348
3513
|
const displayLabel = truncateAtWord(label, budget);
|
|
@@ -3379,7 +3544,7 @@ async function handleContinueOn() {
|
|
|
3379
3544
|
// src/cli/detectDecisions.ts
|
|
3380
3545
|
async function handleDetectDecisions() {
|
|
3381
3546
|
const wsPath = resolveWsPath();
|
|
3382
|
-
if (!(process.env.KEEPGOING_PRO_BYPASS === "1" ||
|
|
3547
|
+
if (!(process.env.KEEPGOING_PRO_BYPASS === "1" || await getLicenseForFeatureWithRevalidation("decisions"))) {
|
|
3383
3548
|
process.exit(0);
|
|
3384
3549
|
}
|
|
3385
3550
|
const reader = new KeepGoingReader(wsPath);
|