@codebyplan/cli 2.0.2 → 2.1.0
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/README.md +11 -0
- package/dist/cli.js +144 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,17 @@ claude mcp add codebyplan -e CODEBYPLAN_API_KEY=your_key_here -- npx -y @codebyp
|
|
|
71
71
|
|------|-------------|
|
|
72
72
|
| `health_check` | Check server version, API connectivity, and latency |
|
|
73
73
|
|
|
74
|
+
### Settings File Model
|
|
75
|
+
|
|
76
|
+
The `sync_claude_files` tool writes a `settings.json` file to the project's `.claude/` directory. This file contains shared settings (hooks, statusLine, attribution, permissions.deny/ask/additionalDirectories) merged from global and repo-specific scopes in the database.
|
|
77
|
+
|
|
78
|
+
A separate `settings.local.json` file (not managed by sync) holds local-only configuration: `permissions.allow` and MCP server definitions. Claude Code merges both files at runtime, with `settings.local.json` taking precedence.
|
|
79
|
+
|
|
80
|
+
| File | Managed By | Contains |
|
|
81
|
+
|------|------------|----------|
|
|
82
|
+
| `settings.json` | `sync_claude_files` (synced from DB) | Shared settings: hooks, statusLine, attribution, permissions.deny/ask/additionalDirectories |
|
|
83
|
+
| `settings.local.json` | User (local-only) | Machine-specific: permissions.allow, MCP server config |
|
|
84
|
+
|
|
74
85
|
## Environment Variables
|
|
75
86
|
|
|
76
87
|
| Variable | Required | Description |
|
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
37
37
|
var init_version = __esm({
|
|
38
38
|
"src/lib/version.ts"() {
|
|
39
39
|
"use strict";
|
|
40
|
-
VERSION = "2.0
|
|
40
|
+
VERSION = "2.1.0";
|
|
41
41
|
PACKAGE_NAME = "@codebyplan/cli";
|
|
42
42
|
}
|
|
43
43
|
});
|
|
@@ -384,7 +384,36 @@ function mergeSettings(template, local) {
|
|
|
384
384
|
}
|
|
385
385
|
return merged;
|
|
386
386
|
}
|
|
387
|
-
|
|
387
|
+
function mergeGlobalAndRepoSettings(global, repo) {
|
|
388
|
+
const merged = { ...global, ...repo };
|
|
389
|
+
const globalPerms = global.permissions && typeof global.permissions === "object" ? global.permissions : {};
|
|
390
|
+
const repoPerms = repo.permissions && typeof repo.permissions === "object" ? repo.permissions : {};
|
|
391
|
+
if (Object.keys(globalPerms).length > 0 || Object.keys(repoPerms).length > 0) {
|
|
392
|
+
const mergedPerms = { ...globalPerms, ...repoPerms };
|
|
393
|
+
for (const key of ARRAY_PERMISSION_KEYS) {
|
|
394
|
+
const globalArr = Array.isArray(globalPerms[key]) ? globalPerms[key] : [];
|
|
395
|
+
const repoArr = Array.isArray(repoPerms[key]) ? repoPerms[key] : [];
|
|
396
|
+
if (globalArr.length > 0 || repoArr.length > 0) {
|
|
397
|
+
mergedPerms[key] = [.../* @__PURE__ */ new Set([...globalArr, ...repoArr])];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
merged.permissions = mergedPerms;
|
|
401
|
+
}
|
|
402
|
+
return merged;
|
|
403
|
+
}
|
|
404
|
+
function stripPermissionsAllow(settings) {
|
|
405
|
+
if (!settings.permissions || typeof settings.permissions !== "object") {
|
|
406
|
+
return settings;
|
|
407
|
+
}
|
|
408
|
+
const perms = { ...settings.permissions };
|
|
409
|
+
delete perms.allow;
|
|
410
|
+
if (Object.keys(perms).length === 0) {
|
|
411
|
+
const { permissions: _, ...rest } = settings;
|
|
412
|
+
return rest;
|
|
413
|
+
}
|
|
414
|
+
return { ...settings, permissions: perms };
|
|
415
|
+
}
|
|
416
|
+
var TEMPLATE_MANAGED_KEYS, TEMPLATE_MANAGED_PERMISSION_KEYS, ARRAY_PERMISSION_KEYS;
|
|
388
417
|
var init_settings_merge = __esm({
|
|
389
418
|
"src/lib/settings-merge.ts"() {
|
|
390
419
|
"use strict";
|
|
@@ -398,6 +427,7 @@ var init_settings_merge = __esm({
|
|
|
398
427
|
"ask",
|
|
399
428
|
"additionalDirectories"
|
|
400
429
|
];
|
|
430
|
+
ARRAY_PERMISSION_KEYS = ["deny", "ask"];
|
|
401
431
|
}
|
|
402
432
|
});
|
|
403
433
|
|
|
@@ -460,6 +490,25 @@ function mergeDiscoveredHooks(existing, discovered, hooksRelPath = ".claude/hook
|
|
|
460
490
|
}
|
|
461
491
|
return merged;
|
|
462
492
|
}
|
|
493
|
+
function stripDiscoveredHooks(config2, hooksRelPath = ".claude/hooks") {
|
|
494
|
+
const prefix = `bash ${hooksRelPath}/`;
|
|
495
|
+
const stripped = {};
|
|
496
|
+
for (const [event, matchers] of Object.entries(config2)) {
|
|
497
|
+
const filteredMatchers = [];
|
|
498
|
+
for (const matcher of matchers) {
|
|
499
|
+
const filteredHooks = matcher.hooks.filter(
|
|
500
|
+
(h) => !(h.command && h.command.startsWith(prefix) && h.command.endsWith(".sh"))
|
|
501
|
+
);
|
|
502
|
+
if (filteredHooks.length > 0) {
|
|
503
|
+
filteredMatchers.push({ matcher: matcher.matcher, hooks: filteredHooks });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (filteredMatchers.length > 0) {
|
|
507
|
+
stripped[event] = filteredMatchers;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return stripped;
|
|
511
|
+
}
|
|
463
512
|
var init_hook_registry = __esm({
|
|
464
513
|
"src/lib/hook-registry.ts"() {
|
|
465
514
|
"use strict";
|
|
@@ -687,9 +736,15 @@ async function executeSyncToLocal(options) {
|
|
|
687
736
|
}
|
|
688
737
|
byType[typeName] = result;
|
|
689
738
|
}
|
|
739
|
+
const globalSettingsFiles = syncData.global_settings ?? [];
|
|
740
|
+
let globalSettings = {};
|
|
741
|
+
for (const gf of globalSettingsFiles) {
|
|
742
|
+
const parsed = JSON.parse(substituteVariables(gf.content, repoData));
|
|
743
|
+
globalSettings = { ...globalSettings, ...parsed };
|
|
744
|
+
}
|
|
690
745
|
const specialTypes = {
|
|
691
746
|
claude_md: () => join4(projectPath, "CLAUDE.md"),
|
|
692
|
-
settings: () => join4(projectPath, ".claude", "settings.
|
|
747
|
+
settings: () => join4(projectPath, ".claude", "settings.json")
|
|
693
748
|
};
|
|
694
749
|
for (const [typeName, getPath] of Object.entries(specialTypes)) {
|
|
695
750
|
const remoteFiles = syncData[typeName] ?? [];
|
|
@@ -703,25 +758,28 @@ async function executeSyncToLocal(options) {
|
|
|
703
758
|
} catch {
|
|
704
759
|
}
|
|
705
760
|
if (typeName === "settings") {
|
|
706
|
-
const
|
|
761
|
+
const repoSettings = JSON.parse(remoteContent);
|
|
762
|
+
const combinedTemplate = mergeGlobalAndRepoSettings(globalSettings, repoSettings);
|
|
707
763
|
const hooksDir = join4(projectPath, ".claude", "hooks");
|
|
708
764
|
const discovered = await discoverHooks(hooksDir);
|
|
709
765
|
if (localContent === void 0) {
|
|
766
|
+
let finalSettings = stripPermissionsAllow(combinedTemplate);
|
|
710
767
|
if (discovered.size > 0) {
|
|
711
|
-
|
|
712
|
-
|
|
768
|
+
finalSettings.hooks = mergeDiscoveredHooks(
|
|
769
|
+
finalSettings.hooks ?? {},
|
|
713
770
|
discovered
|
|
714
771
|
);
|
|
715
772
|
}
|
|
716
773
|
if (!dryRun) {
|
|
717
774
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
718
|
-
await writeFile2(targetPath, JSON.stringify(
|
|
775
|
+
await writeFile2(targetPath, JSON.stringify(finalSettings, null, 2) + "\n", "utf-8");
|
|
719
776
|
}
|
|
720
777
|
result.created.push(remote.name);
|
|
721
778
|
totals.created++;
|
|
722
779
|
} else {
|
|
723
780
|
const localSettings = JSON.parse(localContent);
|
|
724
|
-
|
|
781
|
+
let merged = mergeSettings(combinedTemplate, localSettings);
|
|
782
|
+
merged = stripPermissionsAllow(merged);
|
|
725
783
|
if (discovered.size > 0) {
|
|
726
784
|
merged.hooks = mergeDiscoveredHooks(
|
|
727
785
|
merged.hooks ?? {},
|
|
@@ -1107,7 +1165,7 @@ import { join as join6, extname } from "node:path";
|
|
|
1107
1165
|
function compositeKey(type, name, category) {
|
|
1108
1166
|
return category ? `${type}:${category}/${name}` : `${type}:${name}`;
|
|
1109
1167
|
}
|
|
1110
|
-
async function scanLocalFiles(claudeDir) {
|
|
1168
|
+
async function scanLocalFiles(claudeDir, projectPath) {
|
|
1111
1169
|
const result = /* @__PURE__ */ new Map();
|
|
1112
1170
|
await scanCommands(join6(claudeDir, "commands", "cbp"), result);
|
|
1113
1171
|
await scanSubfolderType(join6(claudeDir, "agents"), "agent", "AGENT.md", result);
|
|
@@ -1115,6 +1173,7 @@ async function scanLocalFiles(claudeDir) {
|
|
|
1115
1173
|
await scanFlatType(join6(claudeDir, "rules"), "rule", ".md", result);
|
|
1116
1174
|
await scanFlatType(join6(claudeDir, "hooks"), "hook", ".sh", result);
|
|
1117
1175
|
await scanTemplates(join6(claudeDir, "templates"), result);
|
|
1176
|
+
await scanSettings(claudeDir, projectPath, result);
|
|
1118
1177
|
return result;
|
|
1119
1178
|
}
|
|
1120
1179
|
async function scanCommands(dir, result) {
|
|
@@ -1190,9 +1249,43 @@ async function scanTemplates(dir, result) {
|
|
|
1190
1249
|
}
|
|
1191
1250
|
}
|
|
1192
1251
|
}
|
|
1252
|
+
async function scanSettings(claudeDir, projectPath, result) {
|
|
1253
|
+
const settingsPath = join6(claudeDir, "settings.json");
|
|
1254
|
+
let raw;
|
|
1255
|
+
try {
|
|
1256
|
+
raw = await readFile6(settingsPath, "utf-8");
|
|
1257
|
+
} catch {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
let parsed;
|
|
1261
|
+
try {
|
|
1262
|
+
parsed = JSON.parse(raw);
|
|
1263
|
+
} catch {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
parsed = stripPermissionsAllow(parsed);
|
|
1267
|
+
if (parsed.hooks && typeof parsed.hooks === "object") {
|
|
1268
|
+
const hooksDir = projectPath ? join6(projectPath, ".claude", "hooks") : join6(claudeDir, "hooks");
|
|
1269
|
+
const discovered = await discoverHooks(hooksDir);
|
|
1270
|
+
if (discovered.size > 0) {
|
|
1271
|
+
parsed.hooks = stripDiscoveredHooks(
|
|
1272
|
+
parsed.hooks,
|
|
1273
|
+
".claude/hooks"
|
|
1274
|
+
);
|
|
1275
|
+
if (Object.keys(parsed.hooks).length === 0) {
|
|
1276
|
+
delete parsed.hooks;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const content = JSON.stringify(parsed, null, 2) + "\n";
|
|
1281
|
+
const key = compositeKey("settings", "settings", null);
|
|
1282
|
+
result.set(key, { type: "settings", name: "settings", category: null, content });
|
|
1283
|
+
}
|
|
1193
1284
|
var init_fileMapper = __esm({
|
|
1194
1285
|
"src/cli/fileMapper.ts"() {
|
|
1195
1286
|
"use strict";
|
|
1287
|
+
init_settings_merge();
|
|
1288
|
+
init_hook_registry();
|
|
1196
1289
|
}
|
|
1197
1290
|
});
|
|
1198
1291
|
|
|
@@ -1275,6 +1368,7 @@ async function runPush() {
|
|
|
1275
1368
|
const flags = parseFlags(3);
|
|
1276
1369
|
const dryRun = hasFlag("dry-run", 3);
|
|
1277
1370
|
const force = hasFlag("force", 3);
|
|
1371
|
+
const isGlobal = hasFlag("global", 3);
|
|
1278
1372
|
validateApiKey();
|
|
1279
1373
|
const config2 = await resolveConfig(flags);
|
|
1280
1374
|
const { repoId, projectPath } = config2;
|
|
@@ -1293,7 +1387,7 @@ async function runPush() {
|
|
|
1293
1387
|
return;
|
|
1294
1388
|
}
|
|
1295
1389
|
console.log(" Scanning local files...");
|
|
1296
|
-
const localFiles = await scanLocalFiles(claudeDir);
|
|
1390
|
+
const localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
1297
1391
|
console.log(` Found ${localFiles.size} local files.`);
|
|
1298
1392
|
console.log(" Fetching remote state...");
|
|
1299
1393
|
const [syncRes, repoRes] = await Promise.all([
|
|
@@ -1382,7 +1476,8 @@ async function runPush() {
|
|
|
1382
1476
|
type: f.type,
|
|
1383
1477
|
name: f.name,
|
|
1384
1478
|
category: f.category,
|
|
1385
|
-
content: f.content
|
|
1479
|
+
content: f.content,
|
|
1480
|
+
...f.type === "settings" ? { scope: isGlobal ? "global" : "repo" } : {}
|
|
1386
1481
|
})),
|
|
1387
1482
|
delete_keys: toDelete
|
|
1388
1483
|
});
|
|
@@ -1416,7 +1511,8 @@ function flattenSyncData(data) {
|
|
|
1416
1511
|
skills: "skill",
|
|
1417
1512
|
rules: "rule",
|
|
1418
1513
|
hooks: "hook",
|
|
1419
|
-
templates: "template"
|
|
1514
|
+
templates: "template",
|
|
1515
|
+
settings: "settings"
|
|
1420
1516
|
};
|
|
1421
1517
|
for (const [syncKey, typeName] of Object.entries(typeMap)) {
|
|
1422
1518
|
const files = data[syncKey] ?? [];
|
|
@@ -1596,7 +1692,7 @@ async function runInit() {
|
|
|
1596
1692
|
allFiles.push({ type: "claude_md", name: file.name, content: file.content });
|
|
1597
1693
|
}
|
|
1598
1694
|
for (const file of settingsFiles) {
|
|
1599
|
-
allFiles.push({ type: "settings", name: file.name, content: file.content });
|
|
1695
|
+
allFiles.push({ type: "settings", name: file.name, content: file.content, scope: file.scope ?? "repo" });
|
|
1600
1696
|
}
|
|
1601
1697
|
if (allFiles.length > 0) {
|
|
1602
1698
|
await apiPost("/sync/files", {
|
|
@@ -23380,21 +23476,6 @@ function registerReadTools(server) {
|
|
|
23380
23476
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23381
23477
|
}
|
|
23382
23478
|
});
|
|
23383
|
-
server.registerTool("get_handoff", {
|
|
23384
|
-
description: "Get handoff state for a repo (status, summary, resume command/context).",
|
|
23385
|
-
inputSchema: {
|
|
23386
|
-
repo_id: external_exports.string().uuid().describe("The repo UUID")
|
|
23387
|
-
}
|
|
23388
|
-
}, async ({ repo_id }) => {
|
|
23389
|
-
try {
|
|
23390
|
-
const res = await apiGet(`/repos/${repo_id}`);
|
|
23391
|
-
const { id, name, handoff_status, handoff_summary, handoff_resume_command, handoff_resume_context, handoff_updated_at } = res.data;
|
|
23392
|
-
const data = { id, name, handoff_status, handoff_summary, handoff_resume_command, handoff_resume_context, handoff_updated_at };
|
|
23393
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
23394
|
-
} catch (err) {
|
|
23395
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23396
|
-
}
|
|
23397
|
-
});
|
|
23398
23479
|
server.registerTool("get_sync_status", {
|
|
23399
23480
|
description: "Get cross-repo sync status. Shows which repos need a claude files sync based on latest updates.",
|
|
23400
23481
|
inputSchema: {}
|
|
@@ -23406,6 +23487,22 @@ function registerReadTools(server) {
|
|
|
23406
23487
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23407
23488
|
}
|
|
23408
23489
|
});
|
|
23490
|
+
server.registerTool("get_next_action", {
|
|
23491
|
+
description: "Compute the next action for a repo based on current workflow state. Returns command, instructions, state, and context.",
|
|
23492
|
+
inputSchema: {
|
|
23493
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23494
|
+
worktree_id: external_exports.string().uuid().optional().describe("Optional worktree UUID to filter by assignment")
|
|
23495
|
+
}
|
|
23496
|
+
}, async ({ repo_id, worktree_id }) => {
|
|
23497
|
+
try {
|
|
23498
|
+
const params = {};
|
|
23499
|
+
if (worktree_id) params.worktree_id = worktree_id;
|
|
23500
|
+
const res = await apiGet(`/repos/${repo_id}/next-action`, params);
|
|
23501
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23502
|
+
} catch (err) {
|
|
23503
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23504
|
+
}
|
|
23505
|
+
});
|
|
23409
23506
|
server.registerTool("get_worktrees", {
|
|
23410
23507
|
description: "List worktrees for a repo. Optionally filter by status.",
|
|
23411
23508
|
inputSchema: {
|
|
@@ -23514,27 +23611,33 @@ function registerWriteTools(server) {
|
|
|
23514
23611
|
description: "Create a new checkpoint for a repo. Optionally connect it to a launch via launch_id.",
|
|
23515
23612
|
inputSchema: {
|
|
23516
23613
|
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23517
|
-
title: external_exports.string().describe("Checkpoint title"),
|
|
23614
|
+
title: external_exports.string().optional().describe("Checkpoint title (optional \u2014 Claude can generate if missing)"),
|
|
23518
23615
|
number: external_exports.number().int().describe("Checkpoint number (e.g. 1 for CHK-001)"),
|
|
23519
|
-
goal: external_exports.string().optional().describe("Checkpoint goal description"),
|
|
23616
|
+
goal: external_exports.string().optional().describe("Checkpoint goal description (max 300 chars, brief overview)"),
|
|
23520
23617
|
deadline: external_exports.string().optional().describe("Deadline date (ISO format)"),
|
|
23521
23618
|
status: external_exports.string().optional().describe("Initial status (default: pending). Use 'draft' for checkpoints not ready for development."),
|
|
23522
23619
|
launch_id: external_exports.string().uuid().optional().describe("Optional launch UUID to connect this checkpoint to"),
|
|
23620
|
+
ideas: external_exports.array(external_exports.object({
|
|
23621
|
+
description: external_exports.string().describe("Idea description"),
|
|
23622
|
+
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
23623
|
+
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
23624
|
+
})).optional().describe("Ideas array \u2014 each idea has description, requirements[], images[]"),
|
|
23523
23625
|
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"),
|
|
23524
23626
|
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
23525
23627
|
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
23526
23628
|
}
|
|
23527
|
-
}, async ({ repo_id, title, number: number3, goal, deadline, status, launch_id, context, research, qa }) => {
|
|
23629
|
+
}, async ({ repo_id, title, number: number3, goal, deadline, status, launch_id, ideas, context, research, qa }) => {
|
|
23528
23630
|
try {
|
|
23529
23631
|
const body = {
|
|
23530
23632
|
repo_id,
|
|
23531
|
-
title,
|
|
23633
|
+
title: title ?? null,
|
|
23532
23634
|
number: number3,
|
|
23533
23635
|
goal: goal ?? null,
|
|
23534
23636
|
deadline: deadline ?? null,
|
|
23535
23637
|
status: status ?? "pending",
|
|
23536
23638
|
launch_id: launch_id ?? null
|
|
23537
23639
|
};
|
|
23640
|
+
if (ideas !== void 0) body.ideas = ideas;
|
|
23538
23641
|
if (context !== void 0) body.context = context;
|
|
23539
23642
|
if (research !== void 0) body.research = research;
|
|
23540
23643
|
if (qa !== void 0) body.qa = qa;
|
|
@@ -23548,19 +23651,24 @@ function registerWriteTools(server) {
|
|
|
23548
23651
|
description: "Update an existing checkpoint. Can connect or disconnect a launch via launch_id.",
|
|
23549
23652
|
inputSchema: {
|
|
23550
23653
|
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
23551
|
-
title: external_exports.string().optional().describe("New title"),
|
|
23552
|
-
goal: external_exports.string().optional().describe("New goal"),
|
|
23654
|
+
title: external_exports.string().nullable().optional().describe("New title (or null to clear)"),
|
|
23655
|
+
goal: external_exports.string().optional().describe("New goal (max 300 chars, brief overview)"),
|
|
23553
23656
|
status: external_exports.string().optional().describe("New status (draft, pending, active, completed)"),
|
|
23554
23657
|
deadline: external_exports.string().optional().describe("New deadline (ISO format)"),
|
|
23555
23658
|
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
23556
23659
|
launch_id: external_exports.string().uuid().nullable().optional().describe("Launch UUID to connect (or null to disconnect)"),
|
|
23557
23660
|
worktree_id: external_exports.string().uuid().nullable().optional().describe("Worktree UUID to assign (or null to unassign)"),
|
|
23558
23661
|
assigned_to: external_exports.string().nullable().optional().describe("Who/what claimed this checkpoint"),
|
|
23662
|
+
ideas: external_exports.array(external_exports.object({
|
|
23663
|
+
description: external_exports.string().describe("Idea description"),
|
|
23664
|
+
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
23665
|
+
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
23666
|
+
})).optional().describe("Ideas array \u2014 each idea has description, requirements[], images[]"),
|
|
23559
23667
|
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"),
|
|
23560
23668
|
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
23561
23669
|
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
23562
23670
|
}
|
|
23563
|
-
}, async ({ checkpoint_id, title, goal, status, deadline, completed_at, launch_id, worktree_id, assigned_to, context, research, qa }) => {
|
|
23671
|
+
}, async ({ checkpoint_id, title, goal, status, deadline, completed_at, launch_id, worktree_id, assigned_to, ideas, context, research, qa }) => {
|
|
23564
23672
|
const update = {};
|
|
23565
23673
|
if (title !== void 0) update.title = title;
|
|
23566
23674
|
if (goal !== void 0) update.goal = goal;
|
|
@@ -23570,6 +23678,7 @@ function registerWriteTools(server) {
|
|
|
23570
23678
|
if (launch_id !== void 0) update.launch_id = launch_id;
|
|
23571
23679
|
if (worktree_id !== void 0) update.worktree_id = worktree_id;
|
|
23572
23680
|
if (assigned_to !== void 0) update.assigned_to = assigned_to;
|
|
23681
|
+
if (ideas !== void 0) update.ideas = ideas;
|
|
23573
23682
|
if (context !== void 0) update.context = context;
|
|
23574
23683
|
if (research !== void 0) update.research = research;
|
|
23575
23684
|
if (qa !== void 0) update.qa = qa;
|
|
@@ -23958,31 +24067,6 @@ function registerWriteTools(server) {
|
|
|
23958
24067
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23959
24068
|
}
|
|
23960
24069
|
});
|
|
23961
|
-
server.registerTool("update_handoff", {
|
|
23962
|
-
description: "Update handoff state for a repo (status, summary, resume command/context). Automatically sets handoff_updated_at.",
|
|
23963
|
-
inputSchema: {
|
|
23964
|
-
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23965
|
-
status: external_exports.string().optional().describe("Handoff status"),
|
|
23966
|
-
summary: external_exports.string().optional().describe("Handoff summary"),
|
|
23967
|
-
resume_command: external_exports.string().optional().describe("Resume command"),
|
|
23968
|
-
resume_context: external_exports.string().optional().describe("Resume context")
|
|
23969
|
-
}
|
|
23970
|
-
}, async ({ repo_id, status, summary, resume_command, resume_context }) => {
|
|
23971
|
-
const body = {};
|
|
23972
|
-
if (status !== void 0) body.status = status;
|
|
23973
|
-
if (summary !== void 0) body.summary = summary;
|
|
23974
|
-
if (resume_command !== void 0) body.resume_command = resume_command;
|
|
23975
|
-
if (resume_context !== void 0) body.resume_context = resume_context;
|
|
23976
|
-
if (Object.keys(body).length === 0) {
|
|
23977
|
-
return { content: [{ type: "text", text: "Error: No fields to update" }], isError: true };
|
|
23978
|
-
}
|
|
23979
|
-
try {
|
|
23980
|
-
const res = await apiPatch(`/repos/${repo_id}/handoff`, body);
|
|
23981
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23982
|
-
} catch (err) {
|
|
23983
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23984
|
-
}
|
|
23985
|
-
});
|
|
23986
24070
|
server.registerTool("create_worktree", {
|
|
23987
24071
|
description: "Create a new worktree for a repo.",
|
|
23988
24072
|
inputSchema: {
|