@girardmedia/bootspring 2.5.8 → 2.5.10
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/cli/index.js +52 -5
- package/dist/core/index.d.ts +1 -1
- package/dist/core.js +3 -3
- package/dist/mcp-server.js +535 -22
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3378,7 +3378,7 @@ var init_release = __esm({
|
|
|
3378
3378
|
"../../packages/shared/src/release.ts"() {
|
|
3379
3379
|
"use strict";
|
|
3380
3380
|
init_cjs_shims();
|
|
3381
|
-
BOOTSPRING_VERSION = "2.5.
|
|
3381
|
+
BOOTSPRING_VERSION = "2.5.10";
|
|
3382
3382
|
BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
|
|
3383
3383
|
}
|
|
3384
3384
|
});
|
|
@@ -48530,19 +48530,62 @@ init_src();
|
|
|
48530
48530
|
// src/middleware.ts
|
|
48531
48531
|
init_cjs_shims();
|
|
48532
48532
|
init_src2();
|
|
48533
|
+
function levenshtein(a, b) {
|
|
48534
|
+
const m = a.length, n = b.length;
|
|
48535
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
48536
|
+
for (let i2 = 0; i2 <= m; i2++) dp[i2][0] = i2;
|
|
48537
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
48538
|
+
for (let i2 = 1; i2 <= m; i2++)
|
|
48539
|
+
for (let j = 1; j <= n; j++)
|
|
48540
|
+
dp[i2][j] = Math.min(
|
|
48541
|
+
dp[i2 - 1][j] + 1,
|
|
48542
|
+
dp[i2][j - 1] + 1,
|
|
48543
|
+
dp[i2 - 1][j - 1] + (a[i2 - 1] !== b[j - 1] ? 1 : 0)
|
|
48544
|
+
);
|
|
48545
|
+
return dp[m][n];
|
|
48546
|
+
}
|
|
48547
|
+
function suggestSubcommand(typed, cmd) {
|
|
48548
|
+
const names = cmd.commands.flatMap((c) => [c.name(), ...c.aliases()]);
|
|
48549
|
+
let best = "";
|
|
48550
|
+
let bestDist = Infinity;
|
|
48551
|
+
for (const name of names) {
|
|
48552
|
+
const d = levenshtein(typed.toLowerCase(), name.toLowerCase());
|
|
48553
|
+
if (d < bestDist) {
|
|
48554
|
+
bestDist = d;
|
|
48555
|
+
best = name;
|
|
48556
|
+
}
|
|
48557
|
+
}
|
|
48558
|
+
const threshold = Math.ceil(Math.max(typed.length, best.length) * 0.4);
|
|
48559
|
+
return bestDist <= threshold ? best : null;
|
|
48560
|
+
}
|
|
48533
48561
|
function applyHealingMiddleware(program3) {
|
|
48534
48562
|
wrapCommand(program3);
|
|
48535
48563
|
}
|
|
48536
48564
|
function wrapCommand(cmd) {
|
|
48565
|
+
if (!cmd._actionHandler && cmd.commands.length > 0 && cmd.parent) {
|
|
48566
|
+
cmd.action(() => {
|
|
48567
|
+
cmd.outputHelp();
|
|
48568
|
+
});
|
|
48569
|
+
}
|
|
48537
48570
|
const listeners = cmd._actionHandler;
|
|
48538
48571
|
if (listeners) {
|
|
48539
48572
|
const originalAction = listeners;
|
|
48540
48573
|
const hasSubcommands = cmd.commands.length > 0;
|
|
48541
48574
|
cmd._actionHandler = async (...args) => {
|
|
48542
|
-
if (hasSubcommands && args
|
|
48543
|
-
|
|
48544
|
-
|
|
48545
|
-
|
|
48575
|
+
if (hasSubcommands && cmd.args?.length > 0) {
|
|
48576
|
+
const unknownArg = cmd.args[0];
|
|
48577
|
+
if (typeof unknownArg === "string") {
|
|
48578
|
+
if (unknownArg === "help") {
|
|
48579
|
+
cmd.outputHelp();
|
|
48580
|
+
return;
|
|
48581
|
+
}
|
|
48582
|
+
const suggestion = suggestSubcommand(unknownArg, cmd);
|
|
48583
|
+
const hint = suggestion ? `
|
|
48584
|
+
(Did you mean ${suggestion}?)` : "";
|
|
48585
|
+
console.error(`error: unknown command '${unknownArg}'${hint}`);
|
|
48586
|
+
process.exitCode = 1;
|
|
48587
|
+
return;
|
|
48588
|
+
}
|
|
48546
48589
|
}
|
|
48547
48590
|
const commandName = cmd.name();
|
|
48548
48591
|
presence_exports.sendHeartbeat({ activity: `bootspring ${commandName}` });
|
|
@@ -49553,6 +49596,10 @@ ${COLORS.cyan}${COLORS.bold}MCP Connectors${COLORS.reset}
|
|
|
49553
49596
|
}
|
|
49554
49597
|
});
|
|
49555
49598
|
mcpCmd.action(async () => {
|
|
49599
|
+
if (process.stdin.isTTY) {
|
|
49600
|
+
mcpCmd.outputHelp();
|
|
49601
|
+
return;
|
|
49602
|
+
}
|
|
49556
49603
|
console.error("Starting MCP Proxy Server...");
|
|
49557
49604
|
try {
|
|
49558
49605
|
const mcpServer = await Promise.resolve().then(() => (init_src3(), src_exports3));
|
package/dist/core/index.d.ts
CHANGED
|
@@ -470,7 +470,7 @@ interface InstallContext {
|
|
|
470
470
|
scriptPath: string;
|
|
471
471
|
}
|
|
472
472
|
declare const PACKAGE_NAME = "@girardmedia/bootspring";
|
|
473
|
-
declare const CURRENT_VERSION = "2.5.
|
|
473
|
+
declare const CURRENT_VERSION = "2.5.10";
|
|
474
474
|
declare const DEFAULT_INTERVAL_MS: number;
|
|
475
475
|
declare const STATE_PATH: string;
|
|
476
476
|
declare function compareVersions(a: string, b: string): number;
|
package/dist/core.js
CHANGED
|
@@ -372,7 +372,7 @@ var init_release = __esm({
|
|
|
372
372
|
"../../packages/shared/src/release.ts"() {
|
|
373
373
|
"use strict";
|
|
374
374
|
init_cjs_shims();
|
|
375
|
-
BOOTSPRING_VERSION = "2.5.
|
|
375
|
+
BOOTSPRING_VERSION = "2.5.10";
|
|
376
376
|
BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
|
|
377
377
|
}
|
|
378
378
|
});
|
|
@@ -21577,7 +21577,7 @@ ${COLORS2.dim}Run "bootspring mcp" for server options${COLORS2.reset}
|
|
|
21577
21577
|
console.log(`${COLORS2.dim}Run "bootspring mcp" for setup instructions.${COLORS2.reset}
|
|
21578
21578
|
`);
|
|
21579
21579
|
}
|
|
21580
|
-
var BOOTSPRING_VERSION2 = "2.5.
|
|
21580
|
+
var BOOTSPRING_VERSION2 = "2.5.10";
|
|
21581
21581
|
var BOOTSPRING_PACKAGE_NAME2 = "@girardmedia/bootspring";
|
|
21582
21582
|
var REDACTED2 = "[REDACTED]";
|
|
21583
21583
|
var SENSITIVE_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|refresh[_-]?token|authorization|x[_-]?api[_-]?key|project[_-]?id)$/i;
|
|
@@ -21625,7 +21625,7 @@ var require_package = __commonJS({
|
|
|
21625
21625
|
"../../../package.json"(exports2, module2) {
|
|
21626
21626
|
module2.exports = {
|
|
21627
21627
|
name: "bootspring-workspace",
|
|
21628
|
-
version: "2.5.
|
|
21628
|
+
version: "2.5.10",
|
|
21629
21629
|
private: true,
|
|
21630
21630
|
description: "Workspace tooling for the Bootspring monorepo",
|
|
21631
21631
|
keywords: [
|
package/dist/mcp-server.js
CHANGED
|
@@ -31370,7 +31370,7 @@ var init_release = __esm({
|
|
|
31370
31370
|
"../../packages/shared/src/release.ts"() {
|
|
31371
31371
|
"use strict";
|
|
31372
31372
|
init_cjs_shims();
|
|
31373
|
-
BOOTSPRING_VERSION = "2.5.
|
|
31373
|
+
BOOTSPRING_VERSION = "2.5.10";
|
|
31374
31374
|
BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
|
|
31375
31375
|
}
|
|
31376
31376
|
});
|
|
@@ -52219,7 +52219,7 @@ var require_package = __commonJS({
|
|
|
52219
52219
|
"../../../package.json"(exports2, module2) {
|
|
52220
52220
|
module2.exports = {
|
|
52221
52221
|
name: "bootspring-workspace",
|
|
52222
|
-
version: "2.5.
|
|
52222
|
+
version: "2.5.10",
|
|
52223
52223
|
private: true,
|
|
52224
52224
|
description: "Workspace tooling for the Bootspring monorepo",
|
|
52225
52225
|
keywords: [
|
|
@@ -52345,6 +52345,527 @@ var core = require_dist2();
|
|
|
52345
52345
|
var api2 = core.api;
|
|
52346
52346
|
var auth2 = core.auth;
|
|
52347
52347
|
var VERSION = require_package().version;
|
|
52348
|
+
function getProjectRoot() {
|
|
52349
|
+
return process.cwd();
|
|
52350
|
+
}
|
|
52351
|
+
function loadBuildState() {
|
|
52352
|
+
const fs3 = require("fs");
|
|
52353
|
+
const path3 = require("path");
|
|
52354
|
+
const stateFile = path3.join(getProjectRoot(), "planning", "BUILD_STATE.json");
|
|
52355
|
+
if (!fs3.existsSync(stateFile)) return null;
|
|
52356
|
+
try {
|
|
52357
|
+
return JSON.parse(fs3.readFileSync(stateFile, "utf-8"));
|
|
52358
|
+
} catch {
|
|
52359
|
+
return null;
|
|
52360
|
+
}
|
|
52361
|
+
}
|
|
52362
|
+
function saveBuildState(state) {
|
|
52363
|
+
const fs3 = require("fs");
|
|
52364
|
+
const path3 = require("path");
|
|
52365
|
+
const planDir = path3.join(getProjectRoot(), "planning");
|
|
52366
|
+
if (!fs3.existsSync(planDir)) fs3.mkdirSync(planDir, { recursive: true });
|
|
52367
|
+
state.metadata = state.metadata || {};
|
|
52368
|
+
state.metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
52369
|
+
if (state.loopSession) state.loopSession.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
52370
|
+
fs3.writeFileSync(path3.join(planDir, "BUILD_STATE.json"), JSON.stringify(state, null, 2));
|
|
52371
|
+
}
|
|
52372
|
+
function buildGetStats(state) {
|
|
52373
|
+
if (!state) return null;
|
|
52374
|
+
const q = state.implementationQueue || [];
|
|
52375
|
+
const completed = q.filter((t) => t.status === "completed").length;
|
|
52376
|
+
const pending = q.filter((t) => t.status === "pending").length;
|
|
52377
|
+
const inProgress = q.filter((t) => t.status === "in_progress").length;
|
|
52378
|
+
const blocked = q.filter((t) => t.status === "blocked").length;
|
|
52379
|
+
const skipped = q.filter((t) => t.status === "skipped").length;
|
|
52380
|
+
const total = q.length;
|
|
52381
|
+
const percent = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
52382
|
+
return { total, completed, pending, inProgress, blocked, skipped, percent };
|
|
52383
|
+
}
|
|
52384
|
+
function buildGetNextTask(state) {
|
|
52385
|
+
if (!state) return null;
|
|
52386
|
+
const q = state.implementationQueue || [];
|
|
52387
|
+
const ip = q.find((t) => t.status === "in_progress");
|
|
52388
|
+
if (ip) return ip;
|
|
52389
|
+
for (const task of q.filter((t) => t.status === "pending")) {
|
|
52390
|
+
if (!task.dependencies || task.dependencies.length === 0) return task;
|
|
52391
|
+
const allDone = task.dependencies.every((depId) => {
|
|
52392
|
+
const dep = q.find((t) => t.id === depId);
|
|
52393
|
+
return dep && dep.status === "completed";
|
|
52394
|
+
});
|
|
52395
|
+
if (allDone) return task;
|
|
52396
|
+
}
|
|
52397
|
+
return null;
|
|
52398
|
+
}
|
|
52399
|
+
function buildUpdateTaskStatus(state, taskId, status) {
|
|
52400
|
+
const task = (state.implementationQueue || []).find((t) => t.id === taskId);
|
|
52401
|
+
if (!task) return false;
|
|
52402
|
+
task.status = status;
|
|
52403
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
52404
|
+
if (status === "completed") task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
52405
|
+
if (status === "completed" || status === "skipped") {
|
|
52406
|
+
state.loopSession = state.loopSession || {};
|
|
52407
|
+
state.loopSession.currentIteration = (state.loopSession.currentIteration || 0) + 1;
|
|
52408
|
+
}
|
|
52409
|
+
return true;
|
|
52410
|
+
}
|
|
52411
|
+
function formatBuildTask(task) {
|
|
52412
|
+
if (!task) return null;
|
|
52413
|
+
return {
|
|
52414
|
+
id: task.id,
|
|
52415
|
+
title: task.title,
|
|
52416
|
+
description: task.description,
|
|
52417
|
+
phase: task.phase,
|
|
52418
|
+
source: task.source,
|
|
52419
|
+
sourceSection: task.sourceSection,
|
|
52420
|
+
acceptanceCriteria: task.acceptanceCriteria || []
|
|
52421
|
+
};
|
|
52422
|
+
}
|
|
52423
|
+
async function executeLocalBuild(args) {
|
|
52424
|
+
const action = args?.action || "status";
|
|
52425
|
+
const state = loadBuildState();
|
|
52426
|
+
switch (action) {
|
|
52427
|
+
case "status": {
|
|
52428
|
+
if (!state) {
|
|
52429
|
+
return { content: [{ type: "text", text: JSON.stringify({ initialized: false, message: "No build state found. Create planning/TODO.md and run action=sync, or use action=init.", hint: "Run: bootspring seed go" }, null, 2) }] };
|
|
52430
|
+
}
|
|
52431
|
+
const stats = buildGetStats(state);
|
|
52432
|
+
const currentTask = (state.implementationQueue || []).find((t) => t.status === "in_progress");
|
|
52433
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52434
|
+
project: state.projectName,
|
|
52435
|
+
phase: state.currentPhase,
|
|
52436
|
+
status: state.status,
|
|
52437
|
+
progress: { completed: stats.completed, pending: stats.pending, inProgress: stats.inProgress, total: stats.total, percent: stats.percent },
|
|
52438
|
+
currentTask: currentTask ? { id: currentTask.id, title: currentTask.title } : null,
|
|
52439
|
+
nextAction: currentTask ? "Complete the current task, then use action=done" : "Use action=next to get the next task"
|
|
52440
|
+
}, null, 2) }] };
|
|
52441
|
+
}
|
|
52442
|
+
case "next": {
|
|
52443
|
+
if (!state) {
|
|
52444
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No build state found", hint: "Use action=init to initialize" }, null, 2) }] };
|
|
52445
|
+
}
|
|
52446
|
+
const inProgress = (state.implementationQueue || []).find((t) => t.status === "in_progress");
|
|
52447
|
+
if (inProgress) {
|
|
52448
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52449
|
+
message: "Task already in progress",
|
|
52450
|
+
state: "in_progress",
|
|
52451
|
+
task: formatBuildTask(inProgress),
|
|
52452
|
+
hint: "Complete this task with action=done, or use action=skip to skip it"
|
|
52453
|
+
}, null, 2) }] };
|
|
52454
|
+
}
|
|
52455
|
+
const nextTask = buildGetNextTask(state);
|
|
52456
|
+
if (!nextTask) {
|
|
52457
|
+
const stats2 = buildGetStats(state);
|
|
52458
|
+
return { content: [{ type: "text", text: JSON.stringify({ message: "All tasks complete!", progress: { completed: stats2.completed, total: stats2.total }, celebration: "Build finished!" }, null, 2) }] };
|
|
52459
|
+
}
|
|
52460
|
+
buildUpdateTaskStatus(state, nextTask.id, "in_progress");
|
|
52461
|
+
saveBuildState(state);
|
|
52462
|
+
const stats = buildGetStats(state);
|
|
52463
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52464
|
+
task: formatBuildTask(nextTask),
|
|
52465
|
+
state: "task_ready",
|
|
52466
|
+
file: "planning/TODO.md",
|
|
52467
|
+
progress: { completed: stats.completed, total: stats.total, percent: stats.percent },
|
|
52468
|
+
instructions: [
|
|
52469
|
+
`Find ${nextTask.id} in planning/TODO.md for full details`,
|
|
52470
|
+
"Implement the task in the codebase",
|
|
52471
|
+
"Ensure acceptance criteria are met",
|
|
52472
|
+
"Run quality checks (lint, test)",
|
|
52473
|
+
"Commit changes with descriptive message",
|
|
52474
|
+
"Use action=done when complete"
|
|
52475
|
+
]
|
|
52476
|
+
}, null, 2) }] };
|
|
52477
|
+
}
|
|
52478
|
+
case "current": {
|
|
52479
|
+
if (!state) {
|
|
52480
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No build state found" }, null, 2) }] };
|
|
52481
|
+
}
|
|
52482
|
+
const currentTask = (state.implementationQueue || []).find((t) => t.status === "in_progress");
|
|
52483
|
+
if (!currentTask) {
|
|
52484
|
+
return { content: [{ type: "text", text: JSON.stringify({ message: "No task currently in progress", hint: "Use action=next to get the next task" }, null, 2) }] };
|
|
52485
|
+
}
|
|
52486
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52487
|
+
task: formatBuildTask(currentTask),
|
|
52488
|
+
instructions: ["Implement this task in the codebase", "Run quality checks (lint, test)", "Commit your changes", "Use action=done to mark complete"]
|
|
52489
|
+
}, null, 2) }] };
|
|
52490
|
+
}
|
|
52491
|
+
case "done": {
|
|
52492
|
+
if (!state) {
|
|
52493
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No build state found" }, null, 2) }] };
|
|
52494
|
+
}
|
|
52495
|
+
const inProgress = (state.implementationQueue || []).find((t) => t.status === "in_progress");
|
|
52496
|
+
if (!inProgress) {
|
|
52497
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No task currently in progress", hint: "Use action=next to get a task first" }, null, 2) }] };
|
|
52498
|
+
}
|
|
52499
|
+
buildUpdateTaskStatus(state, inProgress.id, "completed");
|
|
52500
|
+
const nextTask = buildGetNextTask(state);
|
|
52501
|
+
if (nextTask) {
|
|
52502
|
+
buildUpdateTaskStatus(state, nextTask.id, "in_progress");
|
|
52503
|
+
}
|
|
52504
|
+
saveBuildState(state);
|
|
52505
|
+
const stats = buildGetStats(state);
|
|
52506
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52507
|
+
completed: { id: inProgress.id, title: inProgress.title },
|
|
52508
|
+
progress: { completed: stats.completed, total: stats.total, percent: stats.percent },
|
|
52509
|
+
nextTask: nextTask ? { ...formatBuildTask(nextTask), file: "planning/TODO.md" } : null,
|
|
52510
|
+
allComplete: !nextTask,
|
|
52511
|
+
message: nextTask ? `Complete! Next: implement ${nextTask.id} \u2014 ${nextTask.title}` : "Build complete! All tasks finished."
|
|
52512
|
+
}, null, 2) }] };
|
|
52513
|
+
}
|
|
52514
|
+
case "skip": {
|
|
52515
|
+
if (!state) {
|
|
52516
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No build state found" }, null, 2) }] };
|
|
52517
|
+
}
|
|
52518
|
+
const inProgress = (state.implementationQueue || []).find((t) => t.status === "in_progress");
|
|
52519
|
+
if (!inProgress) {
|
|
52520
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No task to skip", hint: "Use action=next to get a task first" }, null, 2) }] };
|
|
52521
|
+
}
|
|
52522
|
+
buildUpdateTaskStatus(state, inProgress.id, "skipped");
|
|
52523
|
+
const nextTask = buildGetNextTask(state);
|
|
52524
|
+
if (nextTask) {
|
|
52525
|
+
buildUpdateTaskStatus(state, nextTask.id, "in_progress");
|
|
52526
|
+
}
|
|
52527
|
+
saveBuildState(state);
|
|
52528
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52529
|
+
skipped: { id: inProgress.id, title: inProgress.title, reason: args?.reason || "Skipped" },
|
|
52530
|
+
nextTask: nextTask ? formatBuildTask(nextTask) : null,
|
|
52531
|
+
message: nextTask ? `Skipped. Next: ${nextTask.title}` : "No more tasks available"
|
|
52532
|
+
}, null, 2) }] };
|
|
52533
|
+
}
|
|
52534
|
+
case "list": {
|
|
52535
|
+
if (!state) {
|
|
52536
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No build state found" }, null, 2) }] };
|
|
52537
|
+
}
|
|
52538
|
+
const tasks = (state.implementationQueue || []).map((t) => ({ id: t.id, title: t.title, status: t.status, phase: t.phase }));
|
|
52539
|
+
return { content: [{ type: "text", text: JSON.stringify({ project: state.projectName, tasks }, null, 2) }] };
|
|
52540
|
+
}
|
|
52541
|
+
case "advance": {
|
|
52542
|
+
if (!state) {
|
|
52543
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No build state found", hint: "Use action=init or action=sync first" }, null, 2) }] };
|
|
52544
|
+
}
|
|
52545
|
+
const activeTask = (state.implementationQueue || []).find((t) => t.status === "in_progress");
|
|
52546
|
+
if (activeTask) {
|
|
52547
|
+
if (args?.autoDone) {
|
|
52548
|
+
try {
|
|
52549
|
+
const { execSync } = require("child_process");
|
|
52550
|
+
const gitStatus = execSync("git status --porcelain", { cwd: getProjectRoot(), encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
52551
|
+
if (gitStatus.trim().length === 0) {
|
|
52552
|
+
buildUpdateTaskStatus(state, activeTask.id, "completed");
|
|
52553
|
+
const nextTask2 = buildGetNextTask(state);
|
|
52554
|
+
if (nextTask2) buildUpdateTaskStatus(state, nextTask2.id, "in_progress");
|
|
52555
|
+
saveBuildState(state);
|
|
52556
|
+
const stats = buildGetStats(state);
|
|
52557
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52558
|
+
state: nextTask2 ? "advanced" : "all_complete",
|
|
52559
|
+
autoCompleted: true,
|
|
52560
|
+
completed: { id: activeTask.id, title: activeTask.title },
|
|
52561
|
+
nextTask: nextTask2 ? formatBuildTask(nextTask2) : null,
|
|
52562
|
+
progress: { completed: stats.completed, total: stats.total, percent: stats.percent }
|
|
52563
|
+
}, null, 2) }] };
|
|
52564
|
+
}
|
|
52565
|
+
} catch {
|
|
52566
|
+
}
|
|
52567
|
+
}
|
|
52568
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52569
|
+
state: "in_progress",
|
|
52570
|
+
task: formatBuildTask(activeTask),
|
|
52571
|
+
hint: "Finish implementation and use action=done, or call action=advance with autoDone=true after commit"
|
|
52572
|
+
}, null, 2) }] };
|
|
52573
|
+
}
|
|
52574
|
+
const nextTask = buildGetNextTask(state);
|
|
52575
|
+
if (!nextTask) {
|
|
52576
|
+
const stats = buildGetStats(state);
|
|
52577
|
+
return { content: [{ type: "text", text: JSON.stringify({ state: "all_complete", message: "All tasks complete!", progress: { completed: stats.completed, total: stats.total, percent: stats.percent } }, null, 2) }] };
|
|
52578
|
+
}
|
|
52579
|
+
buildUpdateTaskStatus(state, nextTask.id, "in_progress");
|
|
52580
|
+
saveBuildState(state);
|
|
52581
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52582
|
+
state: "task_ready",
|
|
52583
|
+
task: formatBuildTask(nextTask),
|
|
52584
|
+
file: "planning/TODO.md",
|
|
52585
|
+
hint: "Implement task, then call action=advance (or action=done) to continue loop"
|
|
52586
|
+
}, null, 2) }] };
|
|
52587
|
+
}
|
|
52588
|
+
case "sync": {
|
|
52589
|
+
const fs3 = require("fs");
|
|
52590
|
+
const pathMod = require("path");
|
|
52591
|
+
const projectRoot = getProjectRoot();
|
|
52592
|
+
const todoPath = pathMod.join(projectRoot, "planning", "TODO.md");
|
|
52593
|
+
if (!fs3.existsSync(todoPath)) {
|
|
52594
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "planning/TODO.md not found", hint: "Create planning/TODO.md with tasks first" }, null, 2) }] };
|
|
52595
|
+
}
|
|
52596
|
+
const todoContent = fs3.readFileSync(todoPath, "utf-8");
|
|
52597
|
+
const taskRegex = /^-\s*\[([ xX])\]\s*(?:\*\*)?([a-zA-Z]+-\d+)(?:\*\*)?[:\s]+(.+?)$/gm;
|
|
52598
|
+
const tasks = [];
|
|
52599
|
+
let match;
|
|
52600
|
+
while ((match = taskRegex.exec(todoContent)) !== null) {
|
|
52601
|
+
const checked = match[1].toLowerCase() === "x";
|
|
52602
|
+
tasks.push({
|
|
52603
|
+
id: match[2],
|
|
52604
|
+
title: match[3].trim().replace(/\*\*/g, ""),
|
|
52605
|
+
status: checked ? "completed" : "pending",
|
|
52606
|
+
phase: "mvp",
|
|
52607
|
+
source: "TODO.md"
|
|
52608
|
+
});
|
|
52609
|
+
}
|
|
52610
|
+
if (tasks.length === 0) {
|
|
52611
|
+
const simpleLine = /(?:bs|task)-\d+[:\s]+(.+)/gi;
|
|
52612
|
+
let lineMatch;
|
|
52613
|
+
while ((lineMatch = simpleLine.exec(todoContent)) !== null) {
|
|
52614
|
+
tasks.push({
|
|
52615
|
+
id: lineMatch[0].split(/[:\s]/)[0],
|
|
52616
|
+
title: lineMatch[1]?.trim() || lineMatch[0],
|
|
52617
|
+
status: "pending",
|
|
52618
|
+
phase: "mvp",
|
|
52619
|
+
source: "TODO.md"
|
|
52620
|
+
});
|
|
52621
|
+
}
|
|
52622
|
+
}
|
|
52623
|
+
const currentState = state || {
|
|
52624
|
+
version: "1.0.0",
|
|
52625
|
+
projectName: "Project",
|
|
52626
|
+
status: "pending",
|
|
52627
|
+
currentPhase: "mvp",
|
|
52628
|
+
phases: {},
|
|
52629
|
+
implementationQueue: [],
|
|
52630
|
+
mvpCriteria: { features: [], completionPercentage: 0, allCriteriaMet: false },
|
|
52631
|
+
loopSession: { sessionId: `build-${Date.now()}`, currentIteration: 0, maxIterations: 500, startedAt: (/* @__PURE__ */ new Date()).toISOString(), lastUpdated: null, pausedAt: null },
|
|
52632
|
+
metadata: { createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), seedSource: "TODO.md", preseedDocs: [] }
|
|
52633
|
+
};
|
|
52634
|
+
if (args?.replace) {
|
|
52635
|
+
currentState.implementationQueue = tasks;
|
|
52636
|
+
} else {
|
|
52637
|
+
const existingIds = new Set((currentState.implementationQueue || []).map((t) => t.id));
|
|
52638
|
+
for (const task of tasks) {
|
|
52639
|
+
if (!existingIds.has(task.id)) {
|
|
52640
|
+
currentState.implementationQueue = currentState.implementationQueue || [];
|
|
52641
|
+
currentState.implementationQueue.push(task);
|
|
52642
|
+
}
|
|
52643
|
+
}
|
|
52644
|
+
}
|
|
52645
|
+
saveBuildState(currentState);
|
|
52646
|
+
const stats = buildGetStats(currentState);
|
|
52647
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52648
|
+
success: true,
|
|
52649
|
+
source: "TODO.md",
|
|
52650
|
+
tasksFound: tasks.length,
|
|
52651
|
+
mode: args?.replace ? "replace" : "merge",
|
|
52652
|
+
progress: { total: stats.total, completed: stats.completed, pending: stats.pending },
|
|
52653
|
+
nextStep: "Use action=next to get the first task"
|
|
52654
|
+
}, null, 2) }] };
|
|
52655
|
+
}
|
|
52656
|
+
case "init": {
|
|
52657
|
+
const fs3 = require("fs");
|
|
52658
|
+
const pathMod = require("path");
|
|
52659
|
+
const projectRoot = getProjectRoot();
|
|
52660
|
+
const todoPath = pathMod.join(projectRoot, "planning", "TODO.md");
|
|
52661
|
+
if (fs3.existsSync(todoPath)) {
|
|
52662
|
+
return executeLocalBuild({ ...args, action: "sync", replace: true });
|
|
52663
|
+
}
|
|
52664
|
+
const newState = {
|
|
52665
|
+
version: "1.0.0",
|
|
52666
|
+
projectName: pathMod.basename(projectRoot),
|
|
52667
|
+
status: "pending",
|
|
52668
|
+
currentPhase: null,
|
|
52669
|
+
phases: { foundation: { status: "pending", tasks: [] }, mvp: { status: "pending", tasks: [] }, launch: { status: "pending", tasks: [] } },
|
|
52670
|
+
implementationQueue: [],
|
|
52671
|
+
mvpCriteria: { features: [], completionPercentage: 0, allCriteriaMet: false },
|
|
52672
|
+
loopSession: { sessionId: `build-${Date.now()}`, currentIteration: 0, maxIterations: 500, startedAt: (/* @__PURE__ */ new Date()).toISOString(), lastUpdated: null, pausedAt: null },
|
|
52673
|
+
metadata: { createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), seedSource: null, preseedDocs: [] }
|
|
52674
|
+
};
|
|
52675
|
+
saveBuildState(newState);
|
|
52676
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52677
|
+
success: true,
|
|
52678
|
+
message: "Build state initialized",
|
|
52679
|
+
project: newState.projectName,
|
|
52680
|
+
hint: "Create planning/TODO.md with tasks, then use action=sync to load them"
|
|
52681
|
+
}, null, 2) }] };
|
|
52682
|
+
}
|
|
52683
|
+
default:
|
|
52684
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `Unknown action: ${action}`, validActions: ["next", "current", "done", "skip", "status", "list", "init", "sync", "advance"] }, null, 2) }] };
|
|
52685
|
+
}
|
|
52686
|
+
}
|
|
52687
|
+
async function executeLocalSeed(args) {
|
|
52688
|
+
const fs3 = require("fs");
|
|
52689
|
+
const pathMod = require("path");
|
|
52690
|
+
const action = args?.action || "status";
|
|
52691
|
+
const projectRoot = getProjectRoot();
|
|
52692
|
+
const CONTEXT_DIR = ".bootspring/context";
|
|
52693
|
+
const contextDir = pathMod.join(projectRoot, CONTEXT_DIR);
|
|
52694
|
+
const CONTEXT_DOCS = {
|
|
52695
|
+
vision: { name: "VISION.md", title: "Vision", description: "Problem, solution, core values", required: true },
|
|
52696
|
+
audience: { name: "AUDIENCE.md", title: "Audience", description: "Target audience, personas, ICP", required: true },
|
|
52697
|
+
market: { name: "MARKET.md", title: "Market", description: "TAM/SAM/SOM, market opportunity", required: false },
|
|
52698
|
+
competitors: { name: "COMPETITORS.md", title: "Competitors", description: "Competitive landscape, differentiation", required: false },
|
|
52699
|
+
"business-model": { name: "BUSINESS_MODEL.md", title: "Business Model", description: "Revenue, pricing, unit economics", required: true },
|
|
52700
|
+
prd: { name: "PRD.md", title: "PRD", description: "Product requirements, user stories", required: true },
|
|
52701
|
+
"technical-spec": { name: "TECHNICAL_SPEC.md", title: "Technical Spec", description: "Architecture, tech stack, data model", required: false },
|
|
52702
|
+
roadmap: { name: "ROADMAP.md", title: "Roadmap", description: "Phases, milestones, timeline", required: false }
|
|
52703
|
+
};
|
|
52704
|
+
const PRESETS = {
|
|
52705
|
+
essential: ["vision", "audience", "business-model", "prd"],
|
|
52706
|
+
startup: ["vision", "audience", "market", "competitors", "business-model", "prd", "roadmap"],
|
|
52707
|
+
full: Object.keys(CONTEXT_DOCS),
|
|
52708
|
+
technical: ["vision", "prd", "technical-spec", "roadmap"],
|
|
52709
|
+
investor: ["vision", "audience", "market", "competitors", "business-model", "roadmap"]
|
|
52710
|
+
};
|
|
52711
|
+
switch (action) {
|
|
52712
|
+
case "setup":
|
|
52713
|
+
case "init": {
|
|
52714
|
+
const preset = args?.preset || "startup";
|
|
52715
|
+
if (!fs3.existsSync(contextDir)) fs3.mkdirSync(contextDir, { recursive: true });
|
|
52716
|
+
let projectName = pathMod.basename(projectRoot);
|
|
52717
|
+
try {
|
|
52718
|
+
const pkg = JSON.parse(fs3.readFileSync(pathMod.join(projectRoot, "package.json"), "utf-8"));
|
|
52719
|
+
projectName = pkg.name || projectName;
|
|
52720
|
+
} catch {
|
|
52721
|
+
}
|
|
52722
|
+
const docs = PRESETS[preset] || PRESETS.startup;
|
|
52723
|
+
let generated = 0;
|
|
52724
|
+
for (const docType of docs) {
|
|
52725
|
+
const meta = CONTEXT_DOCS[docType];
|
|
52726
|
+
if (!meta) continue;
|
|
52727
|
+
const filePath = pathMod.join(contextDir, meta.name);
|
|
52728
|
+
if (!fs3.existsSync(filePath)) {
|
|
52729
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
52730
|
+
fs3.writeFileSync(filePath, `# ${projectName} \u2014 ${meta.title}
|
|
52731
|
+
|
|
52732
|
+
**Generated:** ${date}
|
|
52733
|
+
**Status:** Draft
|
|
52734
|
+
|
|
52735
|
+
## ${meta.title}
|
|
52736
|
+
|
|
52737
|
+
*${meta.description}*
|
|
52738
|
+
|
|
52739
|
+
---
|
|
52740
|
+
|
|
52741
|
+
*Edit this file with your project details.*
|
|
52742
|
+
`);
|
|
52743
|
+
generated++;
|
|
52744
|
+
}
|
|
52745
|
+
}
|
|
52746
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52747
|
+
success: true,
|
|
52748
|
+
action: "init",
|
|
52749
|
+
preset,
|
|
52750
|
+
generated,
|
|
52751
|
+
contextDir: CONTEXT_DIR,
|
|
52752
|
+
docs: docs.map((d) => CONTEXT_DOCS[d]?.name).filter(Boolean),
|
|
52753
|
+
nextSteps: ["Edit the .md files in .bootspring/context/", 'Run bootspring_seed with action: "generate" or run bootspring seed merge']
|
|
52754
|
+
}, null, 2) }] };
|
|
52755
|
+
}
|
|
52756
|
+
case "status": {
|
|
52757
|
+
const seedPath = pathMod.join(projectRoot, "planning", "SEED.md");
|
|
52758
|
+
const hasSeed = fs3.existsSync(seedPath);
|
|
52759
|
+
let docs = [];
|
|
52760
|
+
if (fs3.existsSync(contextDir)) {
|
|
52761
|
+
docs = fs3.readdirSync(contextDir).filter((f) => f.endsWith(".md") && f !== "README.md");
|
|
52762
|
+
}
|
|
52763
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52764
|
+
contextDocs: docs,
|
|
52765
|
+
total: docs.length,
|
|
52766
|
+
contextDir: CONTEXT_DIR,
|
|
52767
|
+
seedMd: hasSeed,
|
|
52768
|
+
seedPath: hasSeed ? "planning/SEED.md" : null,
|
|
52769
|
+
nextStep: docs.length === 0 ? 'Run bootspring_seed with action: "init"' : !hasSeed ? "Edit docs, then merge" : "Ready to build"
|
|
52770
|
+
}, null, 2) }] };
|
|
52771
|
+
}
|
|
52772
|
+
case "generate": {
|
|
52773
|
+
if (!fs3.existsSync(contextDir)) {
|
|
52774
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No context docs found", hint: 'Run bootspring_seed with action: "init" first' }, null, 2) }] };
|
|
52775
|
+
}
|
|
52776
|
+
const validDocs = Object.values(CONTEXT_DOCS).map((d) => d.name);
|
|
52777
|
+
const files = fs3.readdirSync(contextDir).filter((f) => f.endsWith(".md") && f !== "README.md").sort((a, b) => {
|
|
52778
|
+
const ai = validDocs.indexOf(a);
|
|
52779
|
+
const bi = validDocs.indexOf(b);
|
|
52780
|
+
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
|
|
52781
|
+
});
|
|
52782
|
+
if (files.length === 0) {
|
|
52783
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No .md files in context folder" }, null, 2) }] };
|
|
52784
|
+
}
|
|
52785
|
+
const sections = files.map((f) => fs3.readFileSync(pathMod.join(contextDir, f), "utf-8"));
|
|
52786
|
+
const planDir = pathMod.join(projectRoot, "planning");
|
|
52787
|
+
if (!fs3.existsSync(planDir)) fs3.mkdirSync(planDir, { recursive: true });
|
|
52788
|
+
fs3.writeFileSync(pathMod.join(planDir, "SEED.md"), sections.join("\n\n---\n\n"));
|
|
52789
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
52790
|
+
success: true,
|
|
52791
|
+
action: "generate",
|
|
52792
|
+
merged: files.length,
|
|
52793
|
+
output: "planning/SEED.md",
|
|
52794
|
+
files,
|
|
52795
|
+
nextStep: 'Create planning/TODO.md with tasks, then use bootspring_build action: "sync"'
|
|
52796
|
+
}, null, 2) }] };
|
|
52797
|
+
}
|
|
52798
|
+
case "export": {
|
|
52799
|
+
if (!fs3.existsSync(contextDir)) {
|
|
52800
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No context docs found" }, null, 2) }] };
|
|
52801
|
+
}
|
|
52802
|
+
const docs = {};
|
|
52803
|
+
const files = fs3.readdirSync(contextDir).filter((f) => f.endsWith(".md") && f !== "README.md");
|
|
52804
|
+
for (const file of files) {
|
|
52805
|
+
docs[file.replace(".md", "")] = fs3.readFileSync(pathMod.join(contextDir, file), "utf-8");
|
|
52806
|
+
}
|
|
52807
|
+
return { content: [{ type: "text", text: JSON.stringify({ context: docs, exportedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) }] };
|
|
52808
|
+
}
|
|
52809
|
+
default:
|
|
52810
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `Unknown action: ${action}`, validActions: ["setup", "init", "generate", "status", "export"] }, null, 2) }] };
|
|
52811
|
+
}
|
|
52812
|
+
}
|
|
52813
|
+
async function executeLocalTodo(args) {
|
|
52814
|
+
const fs3 = require("fs");
|
|
52815
|
+
const pathMod = require("path");
|
|
52816
|
+
const action = args?.action || "list";
|
|
52817
|
+
const projectRoot = getProjectRoot();
|
|
52818
|
+
const todoFile = pathMod.join(projectRoot, ".bootspring", "todos.json");
|
|
52819
|
+
function loadTodos() {
|
|
52820
|
+
if (!fs3.existsSync(todoFile)) return [];
|
|
52821
|
+
try {
|
|
52822
|
+
return JSON.parse(fs3.readFileSync(todoFile, "utf-8"));
|
|
52823
|
+
} catch {
|
|
52824
|
+
return [];
|
|
52825
|
+
}
|
|
52826
|
+
}
|
|
52827
|
+
function saveTodos(todos) {
|
|
52828
|
+
const dir = pathMod.dirname(todoFile);
|
|
52829
|
+
if (!fs3.existsSync(dir)) fs3.mkdirSync(dir, { recursive: true });
|
|
52830
|
+
fs3.writeFileSync(todoFile, JSON.stringify(todos, null, 2));
|
|
52831
|
+
}
|
|
52832
|
+
switch (action) {
|
|
52833
|
+
case "list": {
|
|
52834
|
+
const todos = loadTodos();
|
|
52835
|
+
return { content: [{ type: "text", text: JSON.stringify({ todos, total: todos.length }, null, 2) }] };
|
|
52836
|
+
}
|
|
52837
|
+
case "add": {
|
|
52838
|
+
const todos = loadTodos();
|
|
52839
|
+
const newTodo = { id: `todo-${Date.now()}`, text: args?.text || "Untitled", completed: false, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
52840
|
+
todos.push(newTodo);
|
|
52841
|
+
saveTodos(todos);
|
|
52842
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, todo: newTodo }, null, 2) }] };
|
|
52843
|
+
}
|
|
52844
|
+
case "complete": {
|
|
52845
|
+
const todos = loadTodos();
|
|
52846
|
+
const todo = todos.find((t) => t.id === args?.id);
|
|
52847
|
+
if (!todo) return { content: [{ type: "text", text: JSON.stringify({ error: "Todo not found" }, null, 2) }] };
|
|
52848
|
+
todo.completed = true;
|
|
52849
|
+
todo.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
52850
|
+
saveTodos(todos);
|
|
52851
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, todo }, null, 2) }] };
|
|
52852
|
+
}
|
|
52853
|
+
case "delete": {
|
|
52854
|
+
let todos = loadTodos();
|
|
52855
|
+
const before = todos.length;
|
|
52856
|
+
todos = todos.filter((t) => t.id !== args?.id);
|
|
52857
|
+
saveTodos(todos);
|
|
52858
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: todos.length < before, remaining: todos.length }, null, 2) }] };
|
|
52859
|
+
}
|
|
52860
|
+
default:
|
|
52861
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `Unknown action: ${action}` }, null, 2) }] };
|
|
52862
|
+
}
|
|
52863
|
+
}
|
|
52864
|
+
var LOCAL_TOOL_EXECUTORS = {
|
|
52865
|
+
bootspring_build: executeLocalBuild,
|
|
52866
|
+
bootspring_seed: executeLocalSeed,
|
|
52867
|
+
bootspring_todo: executeLocalTodo
|
|
52868
|
+
};
|
|
52348
52869
|
var FALLBACK_TOOLS = [
|
|
52349
52870
|
{
|
|
52350
52871
|
name: "bootspring_agent",
|
|
@@ -52513,20 +53034,7 @@ function formatProxyError(error) {
|
|
|
52513
53034
|
}
|
|
52514
53035
|
return error?.message || "Unknown error from Bootspring API.";
|
|
52515
53036
|
}
|
|
52516
|
-
var LOCAL_TOOLS =
|
|
52517
|
-
function formatLocalToolGuidance(name, args) {
|
|
52518
|
-
const action = args && typeof args.action === "string" ? args.action : "";
|
|
52519
|
-
const base = name.replace(/^bootspring_/, "bootspring ");
|
|
52520
|
-
const cmd = action ? `${base} ${action}` : base;
|
|
52521
|
-
return [
|
|
52522
|
-
`\`${name}\` is a local-state tool \u2014 it reads and writes files in your project (.bootspring/state, planning/, etc.) and therefore cannot be executed from the hosted MCP API.`,
|
|
52523
|
-
``,
|
|
52524
|
-
`Run this in your project terminal:`,
|
|
52525
|
-
` ${cmd}`,
|
|
52526
|
-
``,
|
|
52527
|
-
`Everything else in this conversation will still work \u2014 only local-state tools need to run from your shell.`
|
|
52528
|
-
].join("\n");
|
|
52529
|
-
}
|
|
53037
|
+
var LOCAL_TOOLS = new Set(Object.keys(LOCAL_TOOL_EXECUTORS));
|
|
52530
53038
|
async function resolveTools() {
|
|
52531
53039
|
if (!auth2.isAuthenticated()) {
|
|
52532
53040
|
return FALLBACK_TOOLS;
|
|
@@ -52560,9 +53068,14 @@ async function resolveResources() {
|
|
|
52560
53068
|
}
|
|
52561
53069
|
async function proxyToolCall(name, args) {
|
|
52562
53070
|
if (LOCAL_TOOLS.has(name)) {
|
|
52563
|
-
|
|
52564
|
-
|
|
52565
|
-
}
|
|
53071
|
+
try {
|
|
53072
|
+
return await LOCAL_TOOL_EXECUTORS[name](args || {});
|
|
53073
|
+
} catch (error) {
|
|
53074
|
+
return {
|
|
53075
|
+
content: [{ type: "text", text: `Error executing ${name}: ${error?.message || error}` }],
|
|
53076
|
+
isError: true
|
|
53077
|
+
};
|
|
53078
|
+
}
|
|
52566
53079
|
}
|
|
52567
53080
|
if (!auth2.isAuthenticated()) {
|
|
52568
53081
|
return createAuthError("Authentication required. Run `bootspring auth login` first.");
|
|
@@ -52578,7 +53091,7 @@ async function proxyToolCall(name, args) {
|
|
|
52578
53091
|
const response = await api2.callMcpTool(name, args || {});
|
|
52579
53092
|
if (response && response.result && response.result.status === "local_execution_required") {
|
|
52580
53093
|
return {
|
|
52581
|
-
content: [{ type: "text", text: response.result.message ||
|
|
53094
|
+
content: [{ type: "text", text: response.result.message || `${name} requires local execution. Run: bootspring ${name.replace("bootspring_", "")} ${args?.action || ""}`.trim() }]
|
|
52582
53095
|
};
|
|
52583
53096
|
}
|
|
52584
53097
|
return {
|
|
@@ -52674,11 +53187,11 @@ module.exports = {
|
|
|
52674
53187
|
FALLBACK_TOOLS,
|
|
52675
53188
|
FALLBACK_RESOURCES,
|
|
52676
53189
|
main,
|
|
52677
|
-
// Exposed for tests
|
|
53190
|
+
// Exposed for tests
|
|
52678
53191
|
_diagnoseApiSurface: diagnoseApiSurface,
|
|
52679
53192
|
_formatProxyError: formatProxyError,
|
|
52680
53193
|
_LOCAL_TOOLS: LOCAL_TOOLS,
|
|
52681
|
-
|
|
53194
|
+
_LOCAL_TOOL_EXECUTORS: LOCAL_TOOL_EXECUTORS,
|
|
52682
53195
|
_proxyToolCall: proxyToolCall,
|
|
52683
53196
|
_diagnoseFromApi: function diagnoseFromApi(apiShim, version) {
|
|
52684
53197
|
const required = ["callMcpTool", "listMcpTools", "listMcpResources", "getMcpResource"];
|