@imenam/mcp-documentor 1.0.85 → 1.0.86
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 +323 -329
- package/dist/shared/read-only.js +1 -0
- package/dist/src/cli/setup-mcp.js +2 -5
- package/dist/src/core/code-source.d.ts +33 -22
- package/dist/src/core/code-source.js +92 -69
- package/dist/src/core/github-code-api.d.ts +28 -0
- package/dist/src/core/github-code-api.js +92 -31
- package/dist/src/core/local-state.d.ts +49 -0
- package/dist/src/core/local-state.js +78 -0
- package/dist/src/core/migrate-code-sources.d.ts +17 -0
- package/dist/src/core/migrate-code-sources.js +157 -0
- package/dist/src/core/path-resolver.d.ts +29 -9
- package/dist/src/core/path-resolver.js +18 -42
- package/dist/src/core/project-settings.d.ts +21 -0
- package/dist/src/core/project-settings.js +63 -0
- package/dist/src/gui-worker.js +70 -2
- package/dist/src/index.js +160 -7
- package/dist/src/tools/check-freshness.d.ts +2 -1
- package/dist/src/tools/check-freshness.js +1 -2
- package/dist/src/tools/code-sources.d.ts +102 -0
- package/dist/src/tools/code-sources.js +260 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +16 -2
- package/gui/dist/assets/{arc-CdXRzMBT.js → arc-DBgCQU2O.js} +1 -1
- package/gui/dist/assets/{architectureDiagram-3BPJPVTR-CGUnwe_I.js → architectureDiagram-3BPJPVTR-BIPBAzZr.js} +1 -1
- package/gui/dist/assets/{blockDiagram-GPEHLZMM-BCI5_vQE.js → blockDiagram-GPEHLZMM-BE9WwuyF.js} +1 -1
- package/gui/dist/assets/{c4Diagram-AAUBKEIU-Bm9a6HGg.js → c4Diagram-AAUBKEIU-V1_4ktIt.js} +1 -1
- package/gui/dist/assets/channel-B_cci0fU.js +1 -0
- package/gui/dist/assets/{chunk-2J33WTMH-c7xNwExL.js → chunk-2J33WTMH-DskG-Kmf.js} +1 -1
- package/gui/dist/assets/{chunk-4BX2VUAB-CmcU4Q0o.js → chunk-4BX2VUAB-DaPhidEI.js} +1 -1
- package/gui/dist/assets/{chunk-55IACEB6-K8rRjNKN.js → chunk-55IACEB6-C9r6WDFM.js} +1 -1
- package/gui/dist/assets/{chunk-727SXJPM-DMPnL1bv.js → chunk-727SXJPM-BA-aC8US.js} +1 -1
- package/gui/dist/assets/{chunk-AQP2D5EJ-BeRIIft8.js → chunk-AQP2D5EJ-ByBp3whV.js} +1 -1
- package/gui/dist/assets/{chunk-FMBD7UC4-CN4It-fp.js → chunk-FMBD7UC4-CzqUByx7.js} +1 -1
- package/gui/dist/assets/{chunk-ND2GUHAM-Dy-ZqPua.js → chunk-ND2GUHAM-C0_B2ryQ.js} +1 -1
- package/gui/dist/assets/{chunk-QZHKN3VN-CaV4seJw.js → chunk-QZHKN3VN-B3eEwjDh.js} +1 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-BqlcJ5BP.js +1 -0
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-BqlcJ5BP.js +1 -0
- package/gui/dist/assets/{cose-bilkent-S5V4N54A-B1qt_j1Z.js → cose-bilkent-S5V4N54A-D_i0I2pU.js} +1 -1
- package/gui/dist/assets/{dagre-BM42HDAG-4x5JEBQG.js → dagre-BM42HDAG-DtEVPK_z.js} +1 -1
- package/gui/dist/assets/{diagram-2AECGRRQ-Q7TvjRjv.js → diagram-2AECGRRQ-BftnXa5w.js} +1 -1
- package/gui/dist/assets/{diagram-5GNKFQAL-Dguj26bf.js → diagram-5GNKFQAL-Dh9-kl_u.js} +1 -1
- package/gui/dist/assets/{diagram-KO2AKTUF-DBv6MoZF.js → diagram-KO2AKTUF-CvB0BWCa.js} +1 -1
- package/gui/dist/assets/{diagram-LMA3HP47-mfVXwL5q.js → diagram-LMA3HP47-CDy-J1uy.js} +1 -1
- package/gui/dist/assets/{diagram-OG6HWLK6-Bn3_yQWG.js → diagram-OG6HWLK6-iY9FwkIF.js} +1 -1
- package/gui/dist/assets/{erDiagram-TEJ5UH35-Bp4wnBw7.js → erDiagram-TEJ5UH35-BT2_IX8D.js} +1 -1
- package/gui/dist/assets/{flowDiagram-I6XJVG4X-CGk72E1Q.js → flowDiagram-I6XJVG4X-DaixWyMx.js} +1 -1
- package/gui/dist/assets/{ganttDiagram-6RSMTGT7-C1WQIgpu.js → ganttDiagram-6RSMTGT7-DSo3cQPH.js} +1 -1
- package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-DSIs1ZqI.js → gitGraphDiagram-PVQCEYII-BfFT7Lds.js} +1 -1
- package/gui/dist/assets/index-CxCC1gxu.css +1 -0
- package/gui/dist/assets/{index-T7ZLHNgs.js → index-DPGmAc6z.js} +107 -105
- package/gui/dist/assets/{infoDiagram-5YYISTIA-DzHdfk5Y.js → infoDiagram-5YYISTIA-BxbxNxe2.js} +1 -1
- package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DLxqTmDU.js → ishikawaDiagram-YF4QCWOH-DpopzqZr.js} +1 -1
- package/gui/dist/assets/{journeyDiagram-JHISSGLW-DYUPmZnf.js → journeyDiagram-JHISSGLW-DrOlFzIk.js} +1 -1
- package/gui/dist/assets/{kanban-definition-UN3LZRKU-f6km2MDT.js → kanban-definition-UN3LZRKU-vH81lYXy.js} +1 -1
- package/gui/dist/assets/{linear-DHhAaAXH.js → linear-BipFpFah.js} +1 -1
- package/gui/dist/assets/{mindmap-definition-RKZ34NQL-nergVxV3.js → mindmap-definition-RKZ34NQL-DsTxCLgj.js} +1 -1
- package/gui/dist/assets/{pieDiagram-4H26LBE5-D4IJAdGz.js → pieDiagram-4H26LBE5-Bbq2ZWQU.js} +1 -1
- package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-B5yYdOsV.js → quadrantDiagram-W4KKPZXB-BdTSaEo4.js} +1 -1
- package/gui/dist/assets/{requirementDiagram-4Y6WPE33-BrhJp07G.js → requirementDiagram-4Y6WPE33-BndI8YZy.js} +1 -1
- package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-CUBA4e7F.js → sankeyDiagram-5OEKKPKP-ofBkVITf.js} +1 -1
- package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-DP0-rUie.js → sequenceDiagram-3UESZ5HK-Cjj2UrN9.js} +1 -1
- package/gui/dist/assets/{stateDiagram-AJRCARHV-_BnVcpam.js → stateDiagram-AJRCARHV-CzPrXUuz.js} +1 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DAq4ObN9.js +1 -0
- package/gui/dist/assets/{timeline-definition-PNZ67QCA-crbYjsrq.js → timeline-definition-PNZ67QCA-Cz4X-IOO.js} +1 -1
- package/gui/dist/assets/{vennDiagram-CIIHVFJN-EOqENtE1.js → vennDiagram-CIIHVFJN-BwIgXJcZ.js} +1 -1
- package/gui/dist/assets/{wardley-L42UT6IY-B1FVq-_Y.js → wardley-L42UT6IY-keQS0aNZ.js} +1 -1
- package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-D1DZwIH_.js → wardleyDiagram-YWT4CUSO-DhykSgbD.js} +1 -1
- package/gui/dist/assets/{xychartDiagram-2RQKCTM6-B6Wr8_OJ.js → xychartDiagram-2RQKCTM6-DFTqc4cj.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/scripts/migrate-paths-to-relative.js +196 -192
- package/gui/dist/assets/channel-f0BgDIcS.js +0 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-tGvt4n1B.js +0 -1
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-tGvt4n1B.js +0 -1
- package/gui/dist/assets/index-QkCDSQ2A.css +0 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-B5Vwq56z.js +0 -1
- package/scripts/generate-version.js +0 -20
package/dist/src/index.js
CHANGED
|
@@ -20,7 +20,12 @@ import { searchDocs } from "./tools/search-docs.js";
|
|
|
20
20
|
import { checkFreshness, computeFreshness, DOC_CONCURRENCY } from "./tools/check-freshness.js";
|
|
21
21
|
import { mapWithConcurrency } from "./core/concurrency.js";
|
|
22
22
|
import { findAndHashSection } from "./core/section-tracker.js";
|
|
23
|
-
import { getCodeSource } from "./core/code-source.js";
|
|
23
|
+
import { getCodeSource, invalidateCodeSources, setCodeSourceProvider } from "./core/code-source.js";
|
|
24
|
+
import { readCodeSourceConfig } from "./core/project-settings.js";
|
|
25
|
+
import { LocalState } from "./core/local-state.js";
|
|
26
|
+
import { migrateCodeSources } from "./core/migrate-code-sources.js";
|
|
27
|
+
import { setCodeRootProvider } from "./core/path-resolver.js";
|
|
28
|
+
import { getCodeSourceTool, getGitHubTokenStatus, GITHUB_TOKEN_VAR, inspectCodeRoot, inspectRepository, listCodeSources, previewCodeSource, saveCodeSource, searchRepositoriesForInput, setCodeRoot, setCodeRootTool, setCodeSourceTool, } from "./tools/code-sources.js";
|
|
24
29
|
import { runInCodeOperation } from "./core/code-operation.js";
|
|
25
30
|
import { listProjects } from "./tools/list-projects.js";
|
|
26
31
|
import { refreshFileSnapshot } from "./tools/refresh-file-snapshot.js";
|
|
@@ -98,6 +103,12 @@ const historyStore = new HistoryStore();
|
|
|
98
103
|
historyStore.init(storageEngine.getDataDir());
|
|
99
104
|
storageEngine.setHistoryStore(historyStore);
|
|
100
105
|
const syncEngine = new SyncEngine(storageEngine);
|
|
106
|
+
const localState = new LocalState(storageEngine.getDataDir());
|
|
107
|
+
setCodeRootProvider((project) => localState.getCodeRoot(project));
|
|
108
|
+
setCodeSourceProvider({
|
|
109
|
+
codeSourceConfig: (project) => readCodeSourceConfig(storageEngine.getProjectDir(project)),
|
|
110
|
+
githubToken: () => process.env[GITHUB_TOKEN_VAR]?.trim() || undefined,
|
|
111
|
+
});
|
|
101
112
|
const server = new Server({ name: "mcp-documentor", version: APP_VERSION }, { capabilities: { tools: {} } });
|
|
102
113
|
let guiLauncher;
|
|
103
114
|
let guiStatus = "disabled";
|
|
@@ -189,6 +200,44 @@ function dispatchIPCMessage(msg) {
|
|
|
189
200
|
else if (msg.type === "SYNC_DOCS") {
|
|
190
201
|
handleSyncDocsIPC(msg);
|
|
191
202
|
}
|
|
203
|
+
else if (msg.type === "LIST_CODE_SOURCES") {
|
|
204
|
+
replyToGUI(msg, "LIST_CODE_SOURCES_RESPONSE", async () => ({ projects: await listCodeSources(storageEngine, localState) }));
|
|
205
|
+
}
|
|
206
|
+
else if (msg.type === "INSPECT_REPOSITORY") {
|
|
207
|
+
replyToGUI(msg, "INSPECT_REPOSITORY_RESPONSE", () => inspectRepository(msg.data ?? {}));
|
|
208
|
+
}
|
|
209
|
+
else if (msg.type === "SEARCH_REPOSITORIES") {
|
|
210
|
+
replyToGUI(msg, "SEARCH_REPOSITORIES_RESPONSE", async () => ({
|
|
211
|
+
results: await searchRepositoriesForInput(String(msg.data?.query ?? "")),
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
else if (msg.type === "INSPECT_CODE_ROOT") {
|
|
215
|
+
replyToGUI(msg, "INSPECT_CODE_ROOT_RESPONSE", () => inspectCodeRoot(String(msg.data?.path ?? "")));
|
|
216
|
+
}
|
|
217
|
+
else if (msg.type === "PREVIEW_CODE_SOURCE") {
|
|
218
|
+
replyToGUI(msg, "PREVIEW_CODE_SOURCE_RESPONSE", () => previewCodeSource(storageEngine, localState, requireProjectFromIPC(msg), msg.data?.config, msg.data?.codeRoot));
|
|
219
|
+
}
|
|
220
|
+
else if (msg.type === "SAVE_CODE_SOURCE") {
|
|
221
|
+
handleSaveCodeSource(msg);
|
|
222
|
+
}
|
|
223
|
+
else if (msg.type === "GET_CODE_ROOTS") {
|
|
224
|
+
replyToGUI(msg, "GET_CODE_ROOTS_RESPONSE", () => localState.getCodeRoots());
|
|
225
|
+
}
|
|
226
|
+
else if (msg.type === "SET_CODE_ROOT") {
|
|
227
|
+
handleSetCodeRoot(msg);
|
|
228
|
+
}
|
|
229
|
+
else if (msg.type === "GET_GITHUB_TOKEN") {
|
|
230
|
+
replyToGUI(msg, "GET_GITHUB_TOKEN_RESPONSE", () => getGitHubTokenStatus());
|
|
231
|
+
}
|
|
232
|
+
else if (msg.type === "GET_CODE_SOURCE_MIGRATION") {
|
|
233
|
+
replyToGUI(msg, "GET_CODE_SOURCE_MIGRATION_RESPONSE", () => ({ report: localState.getMigrationReport() ?? null }));
|
|
234
|
+
}
|
|
235
|
+
else if (msg.type === "ACK_CODE_SOURCE_MIGRATION") {
|
|
236
|
+
replyToGUI(msg, "ACK_CODE_SOURCE_MIGRATION_RESPONSE", () => {
|
|
237
|
+
localState.markMigrationReportSeen();
|
|
238
|
+
return { success: true };
|
|
239
|
+
});
|
|
240
|
+
}
|
|
192
241
|
else {
|
|
193
242
|
logger.debug(`Unhandled message from GUI: ${msg.type}`);
|
|
194
243
|
}
|
|
@@ -203,6 +252,54 @@ function requireProjectFromIPC(msg) {
|
|
|
203
252
|
throw new Error("project is required for this operation");
|
|
204
253
|
return project;
|
|
205
254
|
}
|
|
255
|
+
/** Answers a GUI request with what `compute` returns, or with the error it throws. */
|
|
256
|
+
async function replyToGUI(msg, type, compute) {
|
|
257
|
+
try {
|
|
258
|
+
const data = await compute();
|
|
259
|
+
broadcastToGUI({ type, correlationId: msg.correlationId, data, timestamp: new Date().toISOString() });
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
broadcastToGUI({ type, correlationId: msg.correlationId, error: err.message, timestamp: new Date().toISOString() });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** Tells every connected GUI that code sources changed, so selectors and freshness are re-read. */
|
|
266
|
+
function broadcastCodeSourcesChanged(project) {
|
|
267
|
+
broadcastToGUI({
|
|
268
|
+
type: "CODE_SOURCES_CHANGED",
|
|
269
|
+
data: { project: project ?? null },
|
|
270
|
+
timestamp: new Date().toISOString(),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function handleSaveCodeSource(msg) {
|
|
274
|
+
if (isReadOnly()) {
|
|
275
|
+
replyToGUI(msg, "SAVE_CODE_SOURCE_RESPONSE", () => {
|
|
276
|
+
throw new Error("READ_ONLY mode: configuration changes disabled");
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
return replyToGUI(msg, "SAVE_CODE_SOURCE_RESPONSE", () => {
|
|
281
|
+
const project = requireProjectFromIPC(msg);
|
|
282
|
+
const config = saveCodeSource(storageEngine, localState, project, msg.data?.config, msg.data?.codeRoot);
|
|
283
|
+
logger.info(`Code source of project "${project}" set to ${getCodeSource(project).label}`);
|
|
284
|
+
broadcastCodeSourcesChanged(project);
|
|
285
|
+
return { config };
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/** Sets the machine default code root, or a project's own when the message names one. */
|
|
289
|
+
function handleSetCodeRoot(msg) {
|
|
290
|
+
if (isReadOnly()) {
|
|
291
|
+
replyToGUI(msg, "SET_CODE_ROOT_RESPONSE", () => {
|
|
292
|
+
throw new Error("READ_ONLY mode: configuration changes disabled");
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
return replyToGUI(msg, "SET_CODE_ROOT_RESPONSE", () => {
|
|
297
|
+
const project = msg.data?.project || undefined;
|
|
298
|
+
const root = setCodeRoot(storageEngine, localState, String(msg.data?.path ?? ""), project);
|
|
299
|
+
broadcastCodeSourcesChanged(project);
|
|
300
|
+
return { path: root ?? null };
|
|
301
|
+
});
|
|
302
|
+
}
|
|
206
303
|
/** Loads one project's summaries and flags each entry as stale/broken. */
|
|
207
304
|
async function listDocsWithFreshness(project) {
|
|
208
305
|
const listings = await storageEngine.listDocsDetailed(project);
|
|
@@ -245,6 +342,7 @@ async function handleGetInitialState(msg) {
|
|
|
245
342
|
bundles,
|
|
246
343
|
readOnly: isReadOnly(),
|
|
247
344
|
syncEnabled: syncEngine.isConfigured(),
|
|
345
|
+
codeSourceMigrationPending: localState.getMigrationReport()?.seen === false,
|
|
248
346
|
},
|
|
249
347
|
timestamp: new Date().toISOString(),
|
|
250
348
|
});
|
|
@@ -684,9 +782,9 @@ async function handleSyncDocsIPC(msg) {
|
|
|
684
782
|
}
|
|
685
783
|
try {
|
|
686
784
|
// Reuse the tool so the GUI and the agent get identical pull/push semantics.
|
|
687
|
-
const result = await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, {
|
|
785
|
+
const result = afterSync(await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, {
|
|
688
786
|
message: msg.data?.message,
|
|
689
|
-
});
|
|
787
|
+
}));
|
|
690
788
|
const text = result.content.map((c) => c.text).join("\n");
|
|
691
789
|
if (result.isError) {
|
|
692
790
|
broadcastToGUI({
|
|
@@ -1370,6 +1468,42 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1370
1468
|
required: ["path", "decisions"],
|
|
1371
1469
|
},
|
|
1372
1470
|
},
|
|
1471
|
+
{
|
|
1472
|
+
name: "get_code_source",
|
|
1473
|
+
description: "Show where each project's source code is read from when checking freshness: its local working copy or one branch of a GitHub repository, and the code root that applies on this machine. GitHub sources are checked against their branch, and a project whose repository or branch cannot be found is flagged.",
|
|
1474
|
+
inputSchema: {
|
|
1475
|
+
type: "object",
|
|
1476
|
+
properties: {
|
|
1477
|
+
project: { type: "string", description: "Project to show (defaults to every project)" },
|
|
1478
|
+
},
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
name: "set_code_source",
|
|
1483
|
+
description: "Set where a project's source code is read from: 'local' (the working copy on this machine, under its code root) or 'github' (one branch of a repository, read over the GitHub API). The repository and branch are checked before saving. The setting is stored with the docs, so every machine reads the same source after a sync. The GitHub token is not settable here: it is read from the MCP_DOCUMENTOR_CODE_TOKEN environment variable only.",
|
|
1484
|
+
inputSchema: {
|
|
1485
|
+
type: "object",
|
|
1486
|
+
properties: {
|
|
1487
|
+
project: { type: "string", description: "Project to configure. REQUIRED." },
|
|
1488
|
+
kind: { type: "string", enum: ["local", "github"], description: "'local' reads the working copy, 'github' reads a repository branch" },
|
|
1489
|
+
repo: { type: "string", description: "Repository when kind is 'github': 'owner/repo' or a GitHub URL" },
|
|
1490
|
+
branch: { type: "string", description: "Branch when kind is 'github' (defaults to the repository's default branch)" },
|
|
1491
|
+
},
|
|
1492
|
+
required: ["project", "kind"],
|
|
1493
|
+
},
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
name: "set_code_root",
|
|
1497
|
+
description: "Set the code root on THIS machine: the folder tracked-file paths are stored relative to, and that the local working copy is read from. With a project, sets that project's own root; without, sets the machine default used by every project that has none. An empty path removes the root (a project then uses the machine default). The folder must exist. Code roots are machine-local and never synced.",
|
|
1498
|
+
inputSchema: {
|
|
1499
|
+
type: "object",
|
|
1500
|
+
properties: {
|
|
1501
|
+
path: { type: "string", description: "Absolute path of the folder, or an empty string to remove the root" },
|
|
1502
|
+
project: { type: "string", description: "Project whose own root is set (omit to set the machine default)" },
|
|
1503
|
+
},
|
|
1504
|
+
required: ["path"],
|
|
1505
|
+
},
|
|
1506
|
+
},
|
|
1373
1507
|
{
|
|
1374
1508
|
name: "reconnect_gui",
|
|
1375
1509
|
description: "Relaunch the GUI worker and re-register it with the proxy server. Use this when the proxy server was not running when the MCP server started (so the GUI registration failed), to redeploy the GUI without restarting the MCP server. Does nothing if the GUI is already registered, unless force=true.",
|
|
@@ -1479,19 +1613,31 @@ async function handleToolCall(request) {
|
|
|
1479
1613
|
case "sync_status":
|
|
1480
1614
|
return await syncStatus(syncEngine);
|
|
1481
1615
|
case "pull_docs":
|
|
1482
|
-
return await pullDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore);
|
|
1616
|
+
return afterSync(await pullDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore));
|
|
1483
1617
|
case "push_docs":
|
|
1484
1618
|
return await pushDocs(syncEngine, args);
|
|
1485
1619
|
case "sync_docs":
|
|
1486
|
-
return await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, args);
|
|
1620
|
+
return afterSync(await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, args));
|
|
1487
1621
|
case "resolve_sync_conflict":
|
|
1488
|
-
return await resolveSyncConflict(syncEngine, args);
|
|
1622
|
+
return afterSync(await resolveSyncConflict(syncEngine, args));
|
|
1489
1623
|
case "list_conflicts":
|
|
1490
1624
|
return await listConflicts(syncEngine);
|
|
1491
1625
|
case "get_conflict_hunks":
|
|
1492
1626
|
return await getConflictHunks(syncEngine, args);
|
|
1493
1627
|
case "resolve_conflict_hunks":
|
|
1494
|
-
return await resolveConflictHunks(syncEngine, args);
|
|
1628
|
+
return afterSync(await resolveConflictHunks(syncEngine, args));
|
|
1629
|
+
case "get_code_source":
|
|
1630
|
+
return await getCodeSourceTool(storageEngine, localState, args);
|
|
1631
|
+
case "set_code_source": {
|
|
1632
|
+
const result = await setCodeSourceTool(storageEngine, localState, args);
|
|
1633
|
+
broadcastCodeSourcesChanged(args?.project);
|
|
1634
|
+
return result;
|
|
1635
|
+
}
|
|
1636
|
+
case "set_code_root": {
|
|
1637
|
+
const result = setCodeRootTool(storageEngine, localState, args);
|
|
1638
|
+
broadcastCodeSourcesChanged(args?.project);
|
|
1639
|
+
return result;
|
|
1640
|
+
}
|
|
1495
1641
|
case "reconnect_gui":
|
|
1496
1642
|
return await reconnectGui(args);
|
|
1497
1643
|
default:
|
|
@@ -1505,6 +1651,12 @@ async function handleToolCall(request) {
|
|
|
1505
1651
|
};
|
|
1506
1652
|
}
|
|
1507
1653
|
}
|
|
1654
|
+
/** A sync can bring other machines' project settings: code sources are re-read on next use. */
|
|
1655
|
+
function afterSync(result) {
|
|
1656
|
+
invalidateCodeSources();
|
|
1657
|
+
broadcastCodeSourcesChanged();
|
|
1658
|
+
return result;
|
|
1659
|
+
}
|
|
1508
1660
|
async function syncPullOnStartup() {
|
|
1509
1661
|
if (!syncEngine.isConfigured())
|
|
1510
1662
|
return;
|
|
@@ -1533,6 +1685,7 @@ async function main() {
|
|
|
1533
1685
|
await migrateFolders(storageEngine);
|
|
1534
1686
|
await migrateEmbeddings(embeddingStore, storageEngine);
|
|
1535
1687
|
await syncPullOnStartup();
|
|
1688
|
+
migrateCodeSources(storageEngine, localState);
|
|
1536
1689
|
const transport = new StdioServerTransport();
|
|
1537
1690
|
await server.connect(transport);
|
|
1538
1691
|
logger.info("MCP Server connected and running on stdio");
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { TrackedFile, FreshnessReport } from "../../shared/schemas/doc.schema.js";
|
|
2
2
|
import type { StorageEngine } from "../core/storage-engine.js";
|
|
3
|
+
import { type CodeSource } from "../core/code-source.js";
|
|
3
4
|
/**
|
|
4
5
|
* Docs checked at once during a whole-project scan. With FILE_CONCURRENCY this caps in-flight
|
|
5
6
|
* source reads at 32 — enough to hide network latency on a 160-doc project, far below anything
|
|
6
7
|
* GitHub objects to.
|
|
7
8
|
*/
|
|
8
9
|
export declare const DOC_CONCURRENCY = 8;
|
|
9
|
-
export declare function computeFreshness(trackedFiles: TrackedFile[], project: string): Promise<FreshnessReport>;
|
|
10
|
+
export declare function computeFreshness(trackedFiles: TrackedFile[], project: string, source?: CodeSource): Promise<FreshnessReport>;
|
|
10
11
|
export declare function formatFreshnessReport(title: string, report: FreshnessReport): string;
|
|
11
12
|
export declare function checkFreshness(storageEngine: StorageEngine, args: unknown): Promise<{
|
|
12
13
|
content: {
|
|
@@ -48,8 +48,7 @@ async function checkTrackedFile(tracked, source) {
|
|
|
48
48
|
return { kind: "source_error", message: err instanceof Error ? err.message : String(err) };
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
export async function computeFreshness(trackedFiles, project) {
|
|
52
|
-
const source = getCodeSource(project);
|
|
51
|
+
export async function computeFreshness(trackedFiles, project, source = getCodeSource(project)) {
|
|
53
52
|
const empty = { modified: [], missing: [], unchanged: [], anchors_lost: [], broken: [], source: source.label };
|
|
54
53
|
if (!trackedFiles || trackedFiles.length === 0) {
|
|
55
54
|
return { status: "no_tracking", ...empty, total: 0 };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
|
+
import type { LocalState } from "../core/local-state.js";
|
|
3
|
+
import { type BranchHead, type RepositoryInfo, type RepositorySearchResult } from "../core/github-code-api.js";
|
|
4
|
+
import { type CodeSourceConfig } from "../core/project-settings.js";
|
|
5
|
+
/** The only variable the GitHub token is read from. It is never stored nor settable elsewhere. */
|
|
6
|
+
export declare const GITHUB_TOKEN_VAR = "MCP_DOCUMENTOR_CODE_TOKEN";
|
|
7
|
+
/** One project as the project selector shows it. */
|
|
8
|
+
export interface ProjectCodeSource {
|
|
9
|
+
project: string;
|
|
10
|
+
/** Undefined when the project's settings cannot be read — `error` then says why. */
|
|
11
|
+
config?: CodeSourceConfig;
|
|
12
|
+
/** The code root that applies to the project on this machine. */
|
|
13
|
+
codeRoot?: string;
|
|
14
|
+
/** Whether that root is the project's own, or the machine default. */
|
|
15
|
+
codeRootOrigin?: "project" | "default";
|
|
16
|
+
/** The machine default root, shown as what an empty project root falls back to. */
|
|
17
|
+
defaultCodeRoot?: string;
|
|
18
|
+
/** Why the source cannot be read right now: unreadable settings, unknown repository or branch… */
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Every project's code source and code root, with GitHub ones checked against their branch. */
|
|
22
|
+
export declare function listCodeSources(storageEngine: StorageEngine, localState: LocalState): Promise<ProjectCodeSource[]>;
|
|
23
|
+
export type RepositoryInspection = {
|
|
24
|
+
ok: true;
|
|
25
|
+
repo: RepositoryInfo;
|
|
26
|
+
branches: string[];
|
|
27
|
+
branch: string;
|
|
28
|
+
head: BranchHead;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
field: "repo" | "branch";
|
|
32
|
+
error: string;
|
|
33
|
+
repo?: RepositoryInfo;
|
|
34
|
+
branches?: string[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Checks a repository: whether it exists for the configured token, its branches, and the commit
|
|
38
|
+
* the chosen branch — or the default one — points at.
|
|
39
|
+
*/
|
|
40
|
+
export declare function inspectRepository(input: {
|
|
41
|
+
repo: string;
|
|
42
|
+
branch?: string;
|
|
43
|
+
}): Promise<RepositoryInspection>;
|
|
44
|
+
/** Repositories matching what is typed in the repository field — empty below two characters. */
|
|
45
|
+
export declare function searchRepositoriesForInput(term: string): Promise<RepositorySearchResult[]>;
|
|
46
|
+
/** Checks that a code root is an existing folder given as an absolute path. */
|
|
47
|
+
export declare function inspectCodeRoot(root: string): {
|
|
48
|
+
ok: true;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
53
|
+
/** How the project's docs would read against a code source that is not saved yet. */
|
|
54
|
+
export interface CodeSourcePreview {
|
|
55
|
+
label: string;
|
|
56
|
+
docs: number;
|
|
57
|
+
upToDate: number;
|
|
58
|
+
stale: number;
|
|
59
|
+
unverifiable: number;
|
|
60
|
+
broken: number;
|
|
61
|
+
/** Set when the source itself could not be consulted. */
|
|
62
|
+
sourceError?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function previewCodeSource(storageEngine: StorageEngine, localState: LocalState, project: string, rawConfig: unknown, codeRoot?: string): Promise<CodeSourcePreview>;
|
|
65
|
+
/**
|
|
66
|
+
* Sets a code root on this machine: the project's own one, or the machine default when `project`
|
|
67
|
+
* is undefined. An empty root removes it — a project then uses the default.
|
|
68
|
+
*/
|
|
69
|
+
export declare function setCodeRoot(storageEngine: StorageEngine, localState: LocalState, root: string, project?: string): string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Saves a project's code source and, when `codeRoot` is given, its code root on this machine (see
|
|
72
|
+
* `setCodeRoot`). The next operation on that project reads them.
|
|
73
|
+
*/
|
|
74
|
+
export declare function saveCodeSource(storageEngine: StorageEngine, localState: LocalState, project: string, rawConfig: unknown, codeRoot?: string): CodeSourceConfig;
|
|
75
|
+
export interface GitHubTokenStatus {
|
|
76
|
+
/** The variable the token is read from. */
|
|
77
|
+
variable: string;
|
|
78
|
+
configured: boolean;
|
|
79
|
+
/** Account the token belongs to. */
|
|
80
|
+
login?: string;
|
|
81
|
+
/** Why the token was rejected. */
|
|
82
|
+
error?: string;
|
|
83
|
+
}
|
|
84
|
+
export declare function getGitHubTokenStatus(): Promise<GitHubTokenStatus>;
|
|
85
|
+
export declare function getCodeSourceTool(storageEngine: StorageEngine, localState: LocalState, args: unknown): Promise<{
|
|
86
|
+
content: {
|
|
87
|
+
type: string;
|
|
88
|
+
text: string;
|
|
89
|
+
}[];
|
|
90
|
+
}>;
|
|
91
|
+
export declare function setCodeSourceTool(storageEngine: StorageEngine, localState: LocalState, args: unknown): Promise<{
|
|
92
|
+
content: {
|
|
93
|
+
type: string;
|
|
94
|
+
text: string;
|
|
95
|
+
}[];
|
|
96
|
+
}>;
|
|
97
|
+
export declare function setCodeRootTool(storageEngine: StorageEngine, localState: LocalState, args: unknown): {
|
|
98
|
+
content: {
|
|
99
|
+
type: string;
|
|
100
|
+
text: string;
|
|
101
|
+
}[];
|
|
102
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { mapWithConcurrency } from "../core/concurrency.js";
|
|
4
|
+
import { createCodeSource, getGitHubApi, getGitHubToken, invalidateCodeSources } from "../core/code-source.js";
|
|
5
|
+
import { getBranchHead, getRepositoryInfo, getTokenLogin, GitHubApiError, listBranches, parseRepoSpec, searchRepositories, } from "../core/github-code-api.js";
|
|
6
|
+
import { CodeSourceConfigSchema, readCodeSourceConfig, writeCodeSourceConfig, } from "../core/project-settings.js";
|
|
7
|
+
import { computeFreshness, DOC_CONCURRENCY } from "./check-freshness.js";
|
|
8
|
+
/** The only variable the GitHub token is read from. It is never stored nor settable elsewhere. */
|
|
9
|
+
export const GITHUB_TOKEN_VAR = "MCP_DOCUMENTOR_CODE_TOKEN";
|
|
10
|
+
const errorMessage = (err) => (err instanceof Error ? err.message : String(err));
|
|
11
|
+
function assertKnownProject(storageEngine, project) {
|
|
12
|
+
if (!storageEngine.getConfig().projects.includes(project)) {
|
|
13
|
+
throw new Error(`Project "${project}" does not exist.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function describeRoot(localState, project) {
|
|
17
|
+
const roots = localState.getCodeRoots();
|
|
18
|
+
const own = roots.byProject[project];
|
|
19
|
+
if (own !== undefined)
|
|
20
|
+
return { codeRoot: own, codeRootOrigin: "project", defaultCodeRoot: roots.default };
|
|
21
|
+
if (roots.default !== undefined)
|
|
22
|
+
return { codeRoot: roots.default, codeRootOrigin: "default", defaultCodeRoot: roots.default };
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Turns a failed branch lookup into what the user has to fix: the branch when the repository
|
|
27
|
+
* answers, the repository otherwise. Other failures (network, rate limit) keep their message.
|
|
28
|
+
*/
|
|
29
|
+
async function describeUnreachableSource(config, err) {
|
|
30
|
+
if (!(err instanceof GitHubApiError) || (err.status !== 404 && err.status !== 422))
|
|
31
|
+
return errorMessage(err);
|
|
32
|
+
const [owner, repo] = config.repo.split("/");
|
|
33
|
+
try {
|
|
34
|
+
const info = await getRepositoryInfo(owner, repo, getGitHubToken());
|
|
35
|
+
return `Branch “${config.branch ?? info.defaultBranch}” not found — freshness unavailable`;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return `Repository ${config.repo} not found, or not visible with ${GITHUB_TOKEN_VAR}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Every project's code source and code root, with GitHub ones checked against their branch. */
|
|
42
|
+
export async function listCodeSources(storageEngine, localState) {
|
|
43
|
+
const projects = storageEngine.getConfig().projects;
|
|
44
|
+
return mapWithConcurrency(projects, DOC_CONCURRENCY, async (project) => {
|
|
45
|
+
const root = describeRoot(localState, project);
|
|
46
|
+
let config;
|
|
47
|
+
try {
|
|
48
|
+
config = readCodeSourceConfig(storageEngine.getProjectDir(project));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return { project, ...root, error: errorMessage(err) };
|
|
52
|
+
}
|
|
53
|
+
if (config.kind === "local")
|
|
54
|
+
return { project, config, ...root };
|
|
55
|
+
try {
|
|
56
|
+
await getGitHubApi(project)?.resolveRef();
|
|
57
|
+
return { project, config, ...root };
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return { project, config, ...root, error: await describeUnreachableSource(config, err) };
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Checks a repository: whether it exists for the configured token, its branches, and the commit
|
|
66
|
+
* the chosen branch — or the default one — points at.
|
|
67
|
+
*/
|
|
68
|
+
export async function inspectRepository(input) {
|
|
69
|
+
let owner;
|
|
70
|
+
let name;
|
|
71
|
+
let suffixBranch;
|
|
72
|
+
try {
|
|
73
|
+
({ owner, repo: name, ref: suffixBranch } = parseRepoSpec(input.repo));
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { ok: false, field: "repo", error: errorMessage(err) };
|
|
77
|
+
}
|
|
78
|
+
const token = getGitHubToken();
|
|
79
|
+
let repo;
|
|
80
|
+
let branches;
|
|
81
|
+
try {
|
|
82
|
+
[repo, branches] = await Promise.all([getRepositoryInfo(owner, name, token), listBranches(owner, name, token)]);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return { ok: false, field: "repo", error: errorMessage(err) };
|
|
86
|
+
}
|
|
87
|
+
const branch = input.branch || suffixBranch || repo.defaultBranch;
|
|
88
|
+
try {
|
|
89
|
+
const head = await getBranchHead(owner, name, branch, token);
|
|
90
|
+
return { ok: true, repo, branches, branch, head };
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const missing = err instanceof GitHubApiError && (err.status === 404 || err.status === 422);
|
|
94
|
+
const error = missing ? `Branch “${branch}” does not exist in ${repo.fullName}` : errorMessage(err);
|
|
95
|
+
return { ok: false, field: "branch", error, repo, branches };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Suggestions shown while a repository is typed. */
|
|
99
|
+
const SEARCH_LIMIT = 8;
|
|
100
|
+
/** Repositories matching what is typed in the repository field — empty below two characters. */
|
|
101
|
+
export async function searchRepositoriesForInput(term) {
|
|
102
|
+
const trimmed = term.trim().replace(/^https?:\/\/github\.com\//i, "").replace(/\.git$/i, "");
|
|
103
|
+
if (trimmed.replace("/", "").length < 2)
|
|
104
|
+
return [];
|
|
105
|
+
return searchRepositories(trimmed, getGitHubToken(), SEARCH_LIMIT);
|
|
106
|
+
}
|
|
107
|
+
/** Checks that a code root is an existing folder given as an absolute path. */
|
|
108
|
+
export function inspectCodeRoot(root) {
|
|
109
|
+
if (!path.isAbsolute(root))
|
|
110
|
+
return { ok: false, error: "Expected an absolute path" };
|
|
111
|
+
try {
|
|
112
|
+
if (!fs.statSync(root).isDirectory())
|
|
113
|
+
return { ok: false, error: "Not a folder" };
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { ok: false, error: "Folder not found on this machine" };
|
|
117
|
+
}
|
|
118
|
+
return { ok: true };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The root a project would use with `codeRoot`: undefined keeps the current one, an empty string
|
|
122
|
+
* means the machine default, anything else is the project's own root.
|
|
123
|
+
*/
|
|
124
|
+
function rootFor(localState, project, codeRoot) {
|
|
125
|
+
if (codeRoot === undefined)
|
|
126
|
+
return localState.getCodeRoot(project);
|
|
127
|
+
return codeRoot.trim() === "" ? localState.getCodeRoots().default : codeRoot.trim();
|
|
128
|
+
}
|
|
129
|
+
export async function previewCodeSource(storageEngine, localState, project, rawConfig, codeRoot) {
|
|
130
|
+
assertKnownProject(storageEngine, project);
|
|
131
|
+
const config = CodeSourceConfigSchema.parse(rawConfig);
|
|
132
|
+
const source = createCodeSource(project, config, { root: rootFor(localState, project, codeRoot) });
|
|
133
|
+
const listings = (await storageEngine.listDocsDetailed(project)).filter((l) => l.tracked_files.length > 0);
|
|
134
|
+
const preview = { label: source.label, docs: listings.length, upToDate: 0, stale: 0, unverifiable: 0, broken: 0 };
|
|
135
|
+
const reports = await mapWithConcurrency(listings, DOC_CONCURRENCY, (listing) => computeFreshness(listing.tracked_files, project, source));
|
|
136
|
+
for (const report of reports) {
|
|
137
|
+
if (report.sourceError)
|
|
138
|
+
preview.sourceError = report.sourceError;
|
|
139
|
+
if (report.status === "up_to_date")
|
|
140
|
+
preview.upToDate++;
|
|
141
|
+
else if (report.status === "stale")
|
|
142
|
+
preview.stale++;
|
|
143
|
+
else if (report.status === "broken")
|
|
144
|
+
preview.broken++;
|
|
145
|
+
else if (report.status === "unverifiable")
|
|
146
|
+
preview.unverifiable++;
|
|
147
|
+
}
|
|
148
|
+
return preview;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Sets a code root on this machine: the project's own one, or the machine default when `project`
|
|
152
|
+
* is undefined. An empty root removes it — a project then uses the default.
|
|
153
|
+
*/
|
|
154
|
+
export function setCodeRoot(storageEngine, localState, root, project) {
|
|
155
|
+
const trimmed = root.trim();
|
|
156
|
+
if (trimmed !== "") {
|
|
157
|
+
const check = inspectCodeRoot(trimmed);
|
|
158
|
+
if (!check.ok)
|
|
159
|
+
throw new Error(`Invalid code root "${trimmed}": ${check.error}`);
|
|
160
|
+
}
|
|
161
|
+
const value = trimmed === "" ? undefined : path.resolve(trimmed);
|
|
162
|
+
if (project === undefined) {
|
|
163
|
+
localState.setDefaultCodeRoot(value);
|
|
164
|
+
invalidateCodeSources();
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
assertKnownProject(storageEngine, project);
|
|
168
|
+
localState.setProjectCodeRoot(project, value);
|
|
169
|
+
invalidateCodeSources(project);
|
|
170
|
+
}
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Saves a project's code source and, when `codeRoot` is given, its code root on this machine (see
|
|
175
|
+
* `setCodeRoot`). The next operation on that project reads them.
|
|
176
|
+
*/
|
|
177
|
+
export function saveCodeSource(storageEngine, localState, project, rawConfig, codeRoot) {
|
|
178
|
+
assertKnownProject(storageEngine, project);
|
|
179
|
+
const config = CodeSourceConfigSchema.parse(rawConfig);
|
|
180
|
+
if (codeRoot !== undefined)
|
|
181
|
+
setCodeRoot(storageEngine, localState, codeRoot, project);
|
|
182
|
+
writeCodeSourceConfig(storageEngine.getProjectDir(project), config);
|
|
183
|
+
invalidateCodeSources(project);
|
|
184
|
+
return config;
|
|
185
|
+
}
|
|
186
|
+
export async function getGitHubTokenStatus() {
|
|
187
|
+
const token = getGitHubToken();
|
|
188
|
+
if (!token)
|
|
189
|
+
return { variable: GITHUB_TOKEN_VAR, configured: false };
|
|
190
|
+
try {
|
|
191
|
+
return { variable: GITHUB_TOKEN_VAR, configured: true, login: await getTokenLogin(token) };
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return { variable: GITHUB_TOKEN_VAR, configured: true, error: errorMessage(err) };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// --- Agent tools ---
|
|
198
|
+
function describeSourceLine(source) {
|
|
199
|
+
const config = source.config;
|
|
200
|
+
const head = !config
|
|
201
|
+
? "settings unreadable"
|
|
202
|
+
: config.kind === "github"
|
|
203
|
+
? `GitHub ${config.repo} · ${config.branch ?? "default branch"}`
|
|
204
|
+
: "local working copy";
|
|
205
|
+
const root = source.codeRoot
|
|
206
|
+
? `code root ${source.codeRoot} (${source.codeRootOrigin === "project" ? "project setting" : "machine default"})`
|
|
207
|
+
: "no code root on this machine";
|
|
208
|
+
const status = source.error ? ` — ⚠ ${source.error}` : "";
|
|
209
|
+
return `- **${source.project}**: ${head}; ${root}${status}`;
|
|
210
|
+
}
|
|
211
|
+
export async function getCodeSourceTool(storageEngine, localState, args) {
|
|
212
|
+
const project = args?.project;
|
|
213
|
+
if (project)
|
|
214
|
+
assertKnownProject(storageEngine, project);
|
|
215
|
+
const all = await listCodeSources(storageEngine, localState);
|
|
216
|
+
const shown = project ? all.filter((s) => s.project === project) : all;
|
|
217
|
+
const defaultRoot = localState.getCodeRoots().default;
|
|
218
|
+
const token = getGitHubToken() ? `${GITHUB_TOKEN_VAR} is set` : `${GITHUB_TOKEN_VAR} is not set (public repositories only)`;
|
|
219
|
+
const text = `Code sources${project ? ` — project "${project}"` : ""}:\n\n${shown.map(describeSourceLine).join("\n")}\n\n` +
|
|
220
|
+
`Machine default code root: ${defaultRoot ?? "none"}. GitHub token: ${token}.\n` +
|
|
221
|
+
`The code source is shared by every machine (synced with the docs); code roots belong to this machine.`;
|
|
222
|
+
return { content: [{ type: "text", text }] };
|
|
223
|
+
}
|
|
224
|
+
export async function setCodeSourceTool(storageEngine, localState, args) {
|
|
225
|
+
const { project, kind, repo, branch } = (args ?? {});
|
|
226
|
+
if (!project)
|
|
227
|
+
throw new Error("project is required");
|
|
228
|
+
assertKnownProject(storageEngine, project);
|
|
229
|
+
let config;
|
|
230
|
+
if (kind === "local") {
|
|
231
|
+
config = { kind: "local" };
|
|
232
|
+
}
|
|
233
|
+
else if (kind === "github") {
|
|
234
|
+
if (!repo)
|
|
235
|
+
throw new Error('repo is required when kind is "github"');
|
|
236
|
+
const inspection = await inspectRepository({ repo, branch });
|
|
237
|
+
if (!inspection.ok)
|
|
238
|
+
throw new Error(inspection.error);
|
|
239
|
+
config = { kind: "github", repo: inspection.repo.fullName, ...(branch && { branch: inspection.branch }) };
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
throw new Error('kind must be "local" or "github"');
|
|
243
|
+
}
|
|
244
|
+
saveCodeSource(storageEngine, localState, project, config);
|
|
245
|
+
const where = config.kind === "github" ? `GitHub ${config.repo} · ${config.branch ?? "default branch"}` : "its local working copy";
|
|
246
|
+
return { content: [{ type: "text", text: `Project "${project}" now reads its code from ${where}.` }] };
|
|
247
|
+
}
|
|
248
|
+
export function setCodeRootTool(storageEngine, localState, args) {
|
|
249
|
+
const { path: root, project } = (args ?? {});
|
|
250
|
+
if (root === undefined)
|
|
251
|
+
throw new Error("path is required (an empty string removes the root)");
|
|
252
|
+
const value = setCodeRoot(storageEngine, localState, root, project);
|
|
253
|
+
const target = project ? `project "${project}"` : "this machine (default)";
|
|
254
|
+
const text = value
|
|
255
|
+
? `Code root of ${target} set to ${value}.`
|
|
256
|
+
: project
|
|
257
|
+
? `Project "${project}" now uses the machine default code root (${localState.getCodeRoots().default ?? "none"}).`
|
|
258
|
+
: "Default code root removed on this machine.";
|
|
259
|
+
return { content: [{ type: "text", text }] };
|
|
260
|
+
}
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const APP_VERSION
|
|
1
|
+
export declare const APP_VERSION: string;
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
// Lit la version dans le premier package.json trouvé en remontant depuis ce module.
|
|
5
|
+
function readPackageVersion() {
|
|
6
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
while (!existsSync(path.join(dir, "package.json"))) {
|
|
8
|
+
const parent = path.dirname(dir);
|
|
9
|
+
if (parent === dir)
|
|
10
|
+
throw new Error("package.json introuvable");
|
|
11
|
+
dir = parent;
|
|
12
|
+
}
|
|
13
|
+
const pkg = JSON.parse(readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
14
|
+
return pkg.version;
|
|
15
|
+
}
|
|
16
|
+
export const APP_VERSION = readPackageVersion();
|