@openontology/opencode-palantir 0.1.5-next.4 → 0.1.5
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 +8 -11
- package/dist/index.js +92 -456
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Restart OpenCode.
|
|
|
36
36
|
After enabling the plugin, OpenCode will automatically register:
|
|
37
37
|
|
|
38
38
|
- Tools: `get_doc_page`, `list_all_docs`
|
|
39
|
-
- Commands: `/refresh-docs`, `/
|
|
39
|
+
- Commands: `/refresh-docs`, `/setup-palantir-mcp`, `/rescan-palantir-mcp-tools`
|
|
40
40
|
- Agents: `foundry-librarian`, `foundry`
|
|
41
41
|
|
|
42
42
|
### Versions: how to get the latest
|
|
@@ -131,22 +131,17 @@ it’s `export`ed in the environment where OpenCode is launched.
|
|
|
131
131
|
|
|
132
132
|
## Docs tools (Palantir public docs)
|
|
133
133
|
|
|
134
|
-
The docs DB is a local file:
|
|
134
|
+
This package does **not** ship with docs bundled. The docs DB is a local file:
|
|
135
135
|
|
|
136
136
|
- `data/docs.parquet` (in your repo root)
|
|
137
137
|
|
|
138
|
-
###
|
|
138
|
+
### Fetch docs
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
prebuilt snapshot (download/copy). In most repos, docs tools should work without any manual setup.
|
|
140
|
+
In OpenCode, run:
|
|
142
141
|
|
|
143
|
-
|
|
142
|
+
- `/refresh-docs`
|
|
144
143
|
|
|
145
|
-
|
|
146
|
-
- Force refresh from a prebuilt snapshot (no live rescrape)
|
|
147
|
-
- `/refresh-docs-rescrape` (unsafe/experimental fallback)
|
|
148
|
-
- Live-rescrapes palantir.com docs and rebuilds `data/docs.parquet`
|
|
149
|
-
- Use only when snapshot download/copy is blocked
|
|
144
|
+
This downloads the docs and writes `data/docs.parquet`.
|
|
150
145
|
|
|
151
146
|
### Tools
|
|
152
147
|
|
|
@@ -155,6 +150,8 @@ prebuilt snapshot (download/copy). In most repos, docs tools should work without
|
|
|
155
150
|
- `list_all_docs`
|
|
156
151
|
- List docs with pagination and optional query/scope filtering
|
|
157
152
|
|
|
153
|
+
If `data/docs.parquet` is missing, both tools will tell you to run `/refresh-docs`.
|
|
154
|
+
|
|
158
155
|
## Foundry MCP helpers
|
|
159
156
|
|
|
160
157
|
This plugin registers Foundry commands and agents automatically at startup (config-driven).
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/index.ts
|
|
3
|
-
import
|
|
3
|
+
import path4 from "path";
|
|
4
4
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
5
5
|
|
|
6
6
|
// node_modules/hyparquet/src/constants.js
|
|
@@ -6088,9 +6088,6 @@ var BASE_DELAY_MS = 1000;
|
|
|
6088
6088
|
var BACKOFF_FACTOR = 2;
|
|
6089
6089
|
var JITTER_RANGE = 0.25;
|
|
6090
6090
|
var BATCH_SIZE = 100;
|
|
6091
|
-
function formatError(error) {
|
|
6092
|
-
return error instanceof Error ? error.toString() : String(error);
|
|
6093
|
-
}
|
|
6094
6091
|
function decompressPagefind(data) {
|
|
6095
6092
|
const decompressed = gunzipSync(Buffer.from(data));
|
|
6096
6093
|
if (decompressed.length < PAGEFIND_HEADER_SIZE) {
|
|
@@ -6205,11 +6202,8 @@ async function withConcurrencyLimit(tasks, limit) {
|
|
|
6205
6202
|
next();
|
|
6206
6203
|
});
|
|
6207
6204
|
}
|
|
6208
|
-
async function fetchAllDocs(dbPath
|
|
6205
|
+
async function fetchAllDocs(dbPath) {
|
|
6209
6206
|
const entry = await fetchEntryPoint();
|
|
6210
|
-
const onProgress = options.onProgress;
|
|
6211
|
-
const concurrency = typeof options.concurrency === "number" && options.concurrency > 0 ? options.concurrency : DEFAULT_CONCURRENCY;
|
|
6212
|
-
const progressEvery = typeof options.progressEvery === "number" && options.progressEvery > 0 ? Math.floor(options.progressEvery) : BATCH_SIZE;
|
|
6213
6207
|
const langKey = Object.keys(entry.languages)[0];
|
|
6214
6208
|
if (!langKey) {
|
|
6215
6209
|
throw new Error("No languages found in Pagefind entry");
|
|
@@ -6217,25 +6211,17 @@ async function fetchAllDocs(dbPath, options = {}) {
|
|
|
6217
6211
|
const langHash = entry.languages[langKey].hash;
|
|
6218
6212
|
const pageHashes = await fetchAndParseMeta(langHash);
|
|
6219
6213
|
const totalPages = pageHashes.length;
|
|
6220
|
-
onProgress?.({ type: "discovered", totalPages });
|
|
6221
6214
|
const fetchedRecords = [];
|
|
6222
6215
|
const failedUrls = [];
|
|
6223
|
-
let
|
|
6224
|
-
const tasks = pageHashes.map((hash) => () => fetchFragment(hash).
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
url,
|
|
6229
|
-
error: formatError(error)
|
|
6230
|
-
});
|
|
6231
|
-
throw error;
|
|
6232
|
-
}).finally(() => {
|
|
6233
|
-
processedPages += 1;
|
|
6234
|
-
if (processedPages % progressEvery === 0 || processedPages === totalPages) {
|
|
6235
|
-
onProgress?.({ type: "progress", processedPages, totalPages });
|
|
6216
|
+
let done = 0;
|
|
6217
|
+
const tasks = pageHashes.map((hash) => () => fetchFragment(hash).then((record) => {
|
|
6218
|
+
done++;
|
|
6219
|
+
if (done % BATCH_SIZE === 0 || done === totalPages) {
|
|
6220
|
+
console.log(`Fetched ${done}/${totalPages} pages...`);
|
|
6236
6221
|
}
|
|
6222
|
+
return record;
|
|
6237
6223
|
}));
|
|
6238
|
-
const results = await withConcurrencyLimit(tasks,
|
|
6224
|
+
const results = await withConcurrencyLimit(tasks, DEFAULT_CONCURRENCY);
|
|
6239
6225
|
for (let i = 0;i < results.length; i++) {
|
|
6240
6226
|
const result = results[i];
|
|
6241
6227
|
if (result.status === "fulfilled") {
|
|
@@ -6243,15 +6229,10 @@ async function fetchAllDocs(dbPath, options = {}) {
|
|
|
6243
6229
|
} else {
|
|
6244
6230
|
const url = `${PAGEFIND_BASE}/fragment/${pageHashes[i]}.pf_fragment`;
|
|
6245
6231
|
failedUrls.push(url);
|
|
6232
|
+
console.log(`[ERROR] Failed to fetch ${url}: ${result.reason.message}`);
|
|
6246
6233
|
}
|
|
6247
6234
|
}
|
|
6248
6235
|
await writeParquet(fetchedRecords, dbPath);
|
|
6249
|
-
onProgress?.({
|
|
6250
|
-
type: "completed",
|
|
6251
|
-
totalPages,
|
|
6252
|
-
fetchedPages: fetchedRecords.length,
|
|
6253
|
-
failedPages: failedUrls.length
|
|
6254
|
-
});
|
|
6255
6236
|
return {
|
|
6256
6237
|
totalPages,
|
|
6257
6238
|
fetchedPages: fetchedRecords.length,
|
|
@@ -6260,189 +6241,8 @@ async function fetchAllDocs(dbPath, options = {}) {
|
|
|
6260
6241
|
};
|
|
6261
6242
|
}
|
|
6262
6243
|
|
|
6263
|
-
// src/docs/snapshot.ts
|
|
6264
|
-
import fs from "fs/promises";
|
|
6265
|
-
import path from "path";
|
|
6266
|
-
var DEFAULT_DOCS_SNAPSHOT_URLS = [
|
|
6267
|
-
"https://raw.githubusercontent.com/anand-testcompare/opencode-palantir/main/data/docs.parquet"
|
|
6268
|
-
];
|
|
6269
|
-
var MIN_SNAPSHOT_BYTES = 64;
|
|
6270
|
-
var inFlightByPath = new Map;
|
|
6271
|
-
function formatError2(err) {
|
|
6272
|
-
return err instanceof Error ? err.toString() : String(err);
|
|
6273
|
-
}
|
|
6274
|
-
function emit(onEvent, event) {
|
|
6275
|
-
if (!onEvent)
|
|
6276
|
-
return;
|
|
6277
|
-
onEvent(event);
|
|
6278
|
-
}
|
|
6279
|
-
function normalizeSnapshotUrls(customUrls) {
|
|
6280
|
-
const envSingleRaw = process.env.OPENCODE_PALANTIR_DOCS_SNAPSHOT_URL;
|
|
6281
|
-
const envManyRaw = process.env.OPENCODE_PALANTIR_DOCS_SNAPSHOT_URLS;
|
|
6282
|
-
const envSingle = typeof envSingleRaw === "string" && envSingleRaw.trim().length > 0 ? [envSingleRaw.trim()] : [];
|
|
6283
|
-
const envMany = typeof envManyRaw === "string" && envManyRaw.trim().length > 0 ? envManyRaw.split(",").map((x) => x.trim()).filter((x) => x.length > 0) : [];
|
|
6284
|
-
const resolved = customUrls ?? [...envMany, ...envSingle, ...DEFAULT_DOCS_SNAPSHOT_URLS];
|
|
6285
|
-
return Array.from(new Set(resolved));
|
|
6286
|
-
}
|
|
6287
|
-
async function ensureDirectoryExists(dbPath) {
|
|
6288
|
-
await fs.mkdir(path.dirname(dbPath), { recursive: true });
|
|
6289
|
-
}
|
|
6290
|
-
async function statIfExists(filePath) {
|
|
6291
|
-
try {
|
|
6292
|
-
return await fs.stat(filePath);
|
|
6293
|
-
} catch (err) {
|
|
6294
|
-
if (err.code === "ENOENT")
|
|
6295
|
-
return null;
|
|
6296
|
-
throw err;
|
|
6297
|
-
}
|
|
6298
|
-
}
|
|
6299
|
-
function assertValidSnapshotSize(bytes, source) {
|
|
6300
|
-
if (bytes < MIN_SNAPSHOT_BYTES) {
|
|
6301
|
-
throw new Error(`Snapshot from ${source} is unexpectedly small (${bytes} bytes). Expected at least ${MIN_SNAPSHOT_BYTES} bytes.`);
|
|
6302
|
-
}
|
|
6303
|
-
}
|
|
6304
|
-
function tempPathFor(dbPath) {
|
|
6305
|
-
const base = path.basename(dbPath);
|
|
6306
|
-
return path.join(path.dirname(dbPath), `.${base}.tmp.${process.pid}.${Date.now()}`);
|
|
6307
|
-
}
|
|
6308
|
-
async function writeBufferAtomic(dbPath, bytes) {
|
|
6309
|
-
const tmp = tempPathFor(dbPath);
|
|
6310
|
-
await fs.writeFile(tmp, bytes);
|
|
6311
|
-
await fs.rename(tmp, dbPath);
|
|
6312
|
-
}
|
|
6313
|
-
async function copyFileAtomic(sourcePath, dbPath) {
|
|
6314
|
-
const tmp = tempPathFor(dbPath);
|
|
6315
|
-
await fs.copyFile(sourcePath, tmp);
|
|
6316
|
-
await fs.rename(tmp, dbPath);
|
|
6317
|
-
}
|
|
6318
|
-
function bundledSnapshotCandidates(dbPath, pluginDirectory) {
|
|
6319
|
-
const candidates = [];
|
|
6320
|
-
if (pluginDirectory && pluginDirectory.trim().length > 0) {
|
|
6321
|
-
candidates.push(path.resolve(pluginDirectory, "data", "docs.parquet"));
|
|
6322
|
-
} else {
|
|
6323
|
-
candidates.push(path.resolve(import.meta.dir, "..", "..", "data", "docs.parquet"));
|
|
6324
|
-
}
|
|
6325
|
-
const target = path.resolve(dbPath);
|
|
6326
|
-
const deduped = Array.from(new Set(candidates.map((x) => path.resolve(x))));
|
|
6327
|
-
return deduped.filter((candidate) => candidate !== target);
|
|
6328
|
-
}
|
|
6329
|
-
async function tryDownloadSnapshot(dbPath, urls, onEvent) {
|
|
6330
|
-
const errors = [];
|
|
6331
|
-
for (const url of urls) {
|
|
6332
|
-
emit(onEvent, { type: "download-start", url });
|
|
6333
|
-
try {
|
|
6334
|
-
const response = await fetch(url);
|
|
6335
|
-
if (!response.ok) {
|
|
6336
|
-
const reason = `HTTP ${response.status} ${response.statusText}`.trim();
|
|
6337
|
-
emit(onEvent, { type: "download-failed", url, error: reason });
|
|
6338
|
-
errors.push(`${url}: ${reason}`);
|
|
6339
|
-
continue;
|
|
6340
|
-
}
|
|
6341
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
6342
|
-
assertValidSnapshotSize(bytes.byteLength, url);
|
|
6343
|
-
await writeBufferAtomic(dbPath, bytes);
|
|
6344
|
-
emit(onEvent, { type: "download-success", url, bytes: bytes.byteLength });
|
|
6345
|
-
return {
|
|
6346
|
-
dbPath,
|
|
6347
|
-
changed: true,
|
|
6348
|
-
source: "download",
|
|
6349
|
-
bytes: bytes.byteLength,
|
|
6350
|
-
downloadUrl: url
|
|
6351
|
-
};
|
|
6352
|
-
} catch (err) {
|
|
6353
|
-
const reason = formatError2(err);
|
|
6354
|
-
emit(onEvent, { type: "download-failed", url, error: reason });
|
|
6355
|
-
errors.push(`${url}: ${reason}`);
|
|
6356
|
-
}
|
|
6357
|
-
}
|
|
6358
|
-
if (errors.length === 0)
|
|
6359
|
-
return null;
|
|
6360
|
-
throw new Error([
|
|
6361
|
-
"Unable to download prebuilt docs snapshot from configured source URLs.",
|
|
6362
|
-
...errors.map((line) => `- ${line}`)
|
|
6363
|
-
].join(`
|
|
6364
|
-
`));
|
|
6365
|
-
}
|
|
6366
|
-
async function tryCopyBundledSnapshot(dbPath, pluginDirectory, onEvent) {
|
|
6367
|
-
const candidates = bundledSnapshotCandidates(dbPath, pluginDirectory);
|
|
6368
|
-
for (const sourcePath of candidates) {
|
|
6369
|
-
const stat = await statIfExists(sourcePath);
|
|
6370
|
-
if (!stat || !stat.isFile())
|
|
6371
|
-
continue;
|
|
6372
|
-
emit(onEvent, { type: "copy-start", sourcePath });
|
|
6373
|
-
assertValidSnapshotSize(stat.size, sourcePath);
|
|
6374
|
-
await copyFileAtomic(sourcePath, dbPath);
|
|
6375
|
-
emit(onEvent, { type: "copy-success", sourcePath, bytes: stat.size });
|
|
6376
|
-
return {
|
|
6377
|
-
dbPath,
|
|
6378
|
-
changed: true,
|
|
6379
|
-
source: "bundled-copy",
|
|
6380
|
-
bytes: stat.size
|
|
6381
|
-
};
|
|
6382
|
-
}
|
|
6383
|
-
return null;
|
|
6384
|
-
}
|
|
6385
|
-
async function ensureDocsParquetInternal(options) {
|
|
6386
|
-
const dbPath = path.resolve(options.dbPath);
|
|
6387
|
-
const force = options.force === true;
|
|
6388
|
-
const onEvent = options.onEvent;
|
|
6389
|
-
emit(onEvent, { type: "start", force });
|
|
6390
|
-
await ensureDirectoryExists(dbPath);
|
|
6391
|
-
if (!force) {
|
|
6392
|
-
const existing = await statIfExists(dbPath);
|
|
6393
|
-
if (existing && existing.isFile()) {
|
|
6394
|
-
assertValidSnapshotSize(existing.size, dbPath);
|
|
6395
|
-
const result = {
|
|
6396
|
-
dbPath,
|
|
6397
|
-
changed: false,
|
|
6398
|
-
source: "existing",
|
|
6399
|
-
bytes: existing.size
|
|
6400
|
-
};
|
|
6401
|
-
emit(onEvent, { type: "skip-existing", bytes: existing.size });
|
|
6402
|
-
emit(onEvent, { type: "done", result });
|
|
6403
|
-
return result;
|
|
6404
|
-
}
|
|
6405
|
-
}
|
|
6406
|
-
const snapshotUrls = normalizeSnapshotUrls(options.snapshotUrls);
|
|
6407
|
-
let downloadError = null;
|
|
6408
|
-
try {
|
|
6409
|
-
const downloaded = await tryDownloadSnapshot(dbPath, snapshotUrls, onEvent);
|
|
6410
|
-
if (downloaded) {
|
|
6411
|
-
emit(onEvent, { type: "done", result: downloaded });
|
|
6412
|
-
return downloaded;
|
|
6413
|
-
}
|
|
6414
|
-
} catch (err) {
|
|
6415
|
-
downloadError = err instanceof Error ? err : new Error(String(err));
|
|
6416
|
-
}
|
|
6417
|
-
const copied = await tryCopyBundledSnapshot(dbPath, options.pluginDirectory, onEvent);
|
|
6418
|
-
if (copied) {
|
|
6419
|
-
emit(onEvent, { type: "done", result: copied });
|
|
6420
|
-
return copied;
|
|
6421
|
-
}
|
|
6422
|
-
const fallbackHint = "No bundled snapshot was found. You can run /refresh-docs-rescrape as a fallback.";
|
|
6423
|
-
if (downloadError) {
|
|
6424
|
-
throw new Error(`${downloadError.message}
|
|
6425
|
-
${fallbackHint}`);
|
|
6426
|
-
}
|
|
6427
|
-
throw new Error(`No docs snapshot sources were available. ${fallbackHint} ` + `Checked URLs=${snapshotUrls.length}, bundled candidates=${bundledSnapshotCandidates(dbPath, options.pluginDirectory).length}.`);
|
|
6428
|
-
}
|
|
6429
|
-
async function ensureDocsParquet(options) {
|
|
6430
|
-
const dbPath = path.resolve(options.dbPath);
|
|
6431
|
-
const existing = inFlightByPath.get(dbPath);
|
|
6432
|
-
if (existing)
|
|
6433
|
-
return existing;
|
|
6434
|
-
let promise;
|
|
6435
|
-
promise = ensureDocsParquetInternal({ ...options, dbPath }).finally(() => {
|
|
6436
|
-
if (inFlightByPath.get(dbPath) === promise) {
|
|
6437
|
-
inFlightByPath.delete(dbPath);
|
|
6438
|
-
}
|
|
6439
|
-
});
|
|
6440
|
-
inFlightByPath.set(dbPath, promise);
|
|
6441
|
-
return promise;
|
|
6442
|
-
}
|
|
6443
|
-
|
|
6444
6244
|
// src/palantir-mcp/commands.ts
|
|
6445
|
-
import
|
|
6245
|
+
import path3 from "path";
|
|
6446
6246
|
|
|
6447
6247
|
// src/palantir-mcp/allowlist.ts
|
|
6448
6248
|
function isMutatingTool(toolName) {
|
|
@@ -6504,7 +6304,7 @@ function computeAllowedTools(profile, toolNames) {
|
|
|
6504
6304
|
}
|
|
6505
6305
|
|
|
6506
6306
|
// src/palantir-mcp/mcp-client.ts
|
|
6507
|
-
function
|
|
6307
|
+
function formatError(err) {
|
|
6508
6308
|
return err instanceof Error ? err.toString() : String(err);
|
|
6509
6309
|
}
|
|
6510
6310
|
function withTimeout(p, ms, label) {
|
|
@@ -6645,7 +6445,7 @@ ${errText}`));
|
|
|
6645
6445
|
return Array.from(new Set(names)).sort((a, b) => a.localeCompare(b));
|
|
6646
6446
|
} catch (err) {
|
|
6647
6447
|
const stderrText = stderrChunks.join("");
|
|
6648
|
-
throw new Error(`[ERROR] Failed to list palantir-mcp tools: ${
|
|
6448
|
+
throw new Error(`[ERROR] Failed to list palantir-mcp tools: ${formatError(err)}
|
|
6649
6449
|
${stderrText}`);
|
|
6650
6450
|
} finally {
|
|
6651
6451
|
try {
|
|
@@ -6684,8 +6484,8 @@ function normalizeFoundryBaseUrl(raw) {
|
|
|
6684
6484
|
}
|
|
6685
6485
|
|
|
6686
6486
|
// src/palantir-mcp/opencode-config.ts
|
|
6687
|
-
import
|
|
6688
|
-
import
|
|
6487
|
+
import fs from "fs/promises";
|
|
6488
|
+
import path from "path";
|
|
6689
6489
|
|
|
6690
6490
|
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
6691
6491
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -7497,28 +7297,28 @@ var OPENCODE_JSON_FILENAME = "opencode.json";
|
|
|
7497
7297
|
function isRecord(value) {
|
|
7498
7298
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7499
7299
|
}
|
|
7500
|
-
function
|
|
7300
|
+
function formatError2(err) {
|
|
7501
7301
|
return err instanceof Error ? err.toString() : String(err);
|
|
7502
7302
|
}
|
|
7503
7303
|
async function pathExists(p) {
|
|
7504
7304
|
try {
|
|
7505
|
-
await
|
|
7305
|
+
await fs.access(p);
|
|
7506
7306
|
return true;
|
|
7507
7307
|
} catch {
|
|
7508
7308
|
return false;
|
|
7509
7309
|
}
|
|
7510
7310
|
}
|
|
7511
7311
|
async function readOpencodeJsonc(worktree) {
|
|
7512
|
-
const configPath =
|
|
7312
|
+
const configPath = path.join(worktree, OPENCODE_JSONC_FILENAME);
|
|
7513
7313
|
if (!await pathExists(configPath))
|
|
7514
7314
|
return { ok: false, missing: true };
|
|
7515
7315
|
let text;
|
|
7516
7316
|
try {
|
|
7517
|
-
text = await
|
|
7317
|
+
text = await fs.readFile(configPath, "utf8");
|
|
7518
7318
|
} catch (err) {
|
|
7519
7319
|
return {
|
|
7520
7320
|
ok: false,
|
|
7521
|
-
error: `[ERROR] Failed reading ${OPENCODE_JSONC_FILENAME}: ${
|
|
7321
|
+
error: `[ERROR] Failed reading ${OPENCODE_JSONC_FILENAME}: ${formatError2(err)}`
|
|
7522
7322
|
};
|
|
7523
7323
|
}
|
|
7524
7324
|
const errors = [];
|
|
@@ -7533,16 +7333,16 @@ async function readOpencodeJsonc(worktree) {
|
|
|
7533
7333
|
return { ok: true, path: configPath, text, data };
|
|
7534
7334
|
}
|
|
7535
7335
|
async function readLegacyOpencodeJson(worktree) {
|
|
7536
|
-
const legacyPath =
|
|
7336
|
+
const legacyPath = path.join(worktree, OPENCODE_JSON_FILENAME);
|
|
7537
7337
|
if (!await pathExists(legacyPath))
|
|
7538
7338
|
return { ok: false, missing: true };
|
|
7539
7339
|
let text;
|
|
7540
7340
|
try {
|
|
7541
|
-
text = await
|
|
7341
|
+
text = await fs.readFile(legacyPath, "utf8");
|
|
7542
7342
|
} catch (err) {
|
|
7543
7343
|
return {
|
|
7544
7344
|
ok: false,
|
|
7545
|
-
error: `[ERROR] Failed reading ${OPENCODE_JSON_FILENAME}: ${
|
|
7345
|
+
error: `[ERROR] Failed reading ${OPENCODE_JSON_FILENAME}: ${formatError2(err)}`
|
|
7546
7346
|
};
|
|
7547
7347
|
}
|
|
7548
7348
|
let data;
|
|
@@ -7551,7 +7351,7 @@ async function readLegacyOpencodeJson(worktree) {
|
|
|
7551
7351
|
} catch (err) {
|
|
7552
7352
|
return {
|
|
7553
7353
|
ok: false,
|
|
7554
|
-
error: `[ERROR] Failed parsing ${OPENCODE_JSON_FILENAME}: ${
|
|
7354
|
+
error: `[ERROR] Failed parsing ${OPENCODE_JSON_FILENAME}: ${formatError2(err)}`
|
|
7555
7355
|
};
|
|
7556
7356
|
}
|
|
7557
7357
|
return { ok: true, path: legacyPath, text, data };
|
|
@@ -7580,24 +7380,24 @@ function mergeLegacyIntoJsonc(legacyData, jsoncData) {
|
|
|
7580
7380
|
return deepMergePreferTarget(base, legacy);
|
|
7581
7381
|
}
|
|
7582
7382
|
async function writeFileAtomic(filePath, text) {
|
|
7583
|
-
const dir =
|
|
7584
|
-
const base =
|
|
7585
|
-
const tmp =
|
|
7586
|
-
await
|
|
7587
|
-
await
|
|
7383
|
+
const dir = path.dirname(filePath);
|
|
7384
|
+
const base = path.basename(filePath);
|
|
7385
|
+
const tmp = path.join(dir, `.${base}.tmp.${process.pid}.${Date.now()}`);
|
|
7386
|
+
await fs.writeFile(tmp, text, "utf8");
|
|
7387
|
+
await fs.rename(tmp, filePath);
|
|
7588
7388
|
}
|
|
7589
7389
|
async function renameLegacyToBak(worktree) {
|
|
7590
|
-
const legacyPath =
|
|
7390
|
+
const legacyPath = path.join(worktree, OPENCODE_JSON_FILENAME);
|
|
7591
7391
|
if (!await pathExists(legacyPath))
|
|
7592
7392
|
return null;
|
|
7593
|
-
const baseBak =
|
|
7393
|
+
const baseBak = path.join(worktree, `${OPENCODE_JSON_FILENAME}.bak`);
|
|
7594
7394
|
let bakPath = baseBak;
|
|
7595
7395
|
let i = 1;
|
|
7596
7396
|
while (await pathExists(bakPath)) {
|
|
7597
7397
|
bakPath = `${baseBak}.${i}`;
|
|
7598
7398
|
i += 1;
|
|
7599
7399
|
}
|
|
7600
|
-
await
|
|
7400
|
+
await fs.rename(legacyPath, bakPath);
|
|
7601
7401
|
return bakPath;
|
|
7602
7402
|
}
|
|
7603
7403
|
function toolKey(toolName) {
|
|
@@ -7837,11 +7637,11 @@ function stringifyJsonc(data) {
|
|
|
7837
7637
|
}
|
|
7838
7638
|
|
|
7839
7639
|
// src/palantir-mcp/repo-scan.ts
|
|
7840
|
-
import
|
|
7841
|
-
import
|
|
7640
|
+
import fs2 from "fs/promises";
|
|
7641
|
+
import path2 from "path";
|
|
7842
7642
|
async function pathExists2(p) {
|
|
7843
7643
|
try {
|
|
7844
|
-
await
|
|
7644
|
+
await fs2.access(p);
|
|
7845
7645
|
return true;
|
|
7846
7646
|
} catch {
|
|
7847
7647
|
return false;
|
|
@@ -7849,7 +7649,7 @@ async function pathExists2(p) {
|
|
|
7849
7649
|
}
|
|
7850
7650
|
async function readTextFileBounded(p, maxBytes) {
|
|
7851
7651
|
try {
|
|
7852
|
-
const file = await
|
|
7652
|
+
const file = await fs2.open(p, "r");
|
|
7853
7653
|
try {
|
|
7854
7654
|
const buf = Buffer.alloc(maxBytes);
|
|
7855
7655
|
const { bytesRead } = await file.read(buf, 0, maxBytes, 0);
|
|
@@ -7932,14 +7732,14 @@ async function collectSampleFiles(root, limit) {
|
|
|
7932
7732
|
visitedDirs += 1;
|
|
7933
7733
|
let entries;
|
|
7934
7734
|
try {
|
|
7935
|
-
entries = await
|
|
7735
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
7936
7736
|
} catch {
|
|
7937
7737
|
continue;
|
|
7938
7738
|
}
|
|
7939
7739
|
for (const ent of entries) {
|
|
7940
7740
|
if (results.length >= limit)
|
|
7941
7741
|
break;
|
|
7942
|
-
const full =
|
|
7742
|
+
const full = path2.join(dir, ent.name);
|
|
7943
7743
|
if (ent.isDirectory()) {
|
|
7944
7744
|
if (ignoreDirs.has(ent.name))
|
|
7945
7745
|
continue;
|
|
@@ -7948,7 +7748,7 @@ async function collectSampleFiles(root, limit) {
|
|
|
7948
7748
|
}
|
|
7949
7749
|
if (!ent.isFile())
|
|
7950
7750
|
continue;
|
|
7951
|
-
const ext =
|
|
7751
|
+
const ext = path2.extname(ent.name);
|
|
7952
7752
|
if (!allowedExts.has(ext))
|
|
7953
7753
|
continue;
|
|
7954
7754
|
results.push(full);
|
|
@@ -7971,13 +7771,13 @@ async function scanRepoForProfile(root) {
|
|
|
7971
7771
|
{ p: "lerna.json", profile: "all", score: 3, reason: "Found lerna.json" }
|
|
7972
7772
|
];
|
|
7973
7773
|
for (const c of candidates) {
|
|
7974
|
-
if (await pathExists2(
|
|
7774
|
+
if (await pathExists2(path2.join(root, c.p))) {
|
|
7975
7775
|
addScore(scores, reasons, c.profile, c.score, c.reason);
|
|
7976
7776
|
}
|
|
7977
7777
|
}
|
|
7978
|
-
const packageJsonPath =
|
|
7979
|
-
const pyprojectPath =
|
|
7980
|
-
const requirementsPath =
|
|
7778
|
+
const packageJsonPath = path2.join(root, "package.json");
|
|
7779
|
+
const pyprojectPath = path2.join(root, "pyproject.toml");
|
|
7780
|
+
const requirementsPath = path2.join(root, "requirements.txt");
|
|
7981
7781
|
const hasPackageJson = await pathExists2(packageJsonPath);
|
|
7982
7782
|
const hasPyproject = await pathExists2(pyprojectPath);
|
|
7983
7783
|
if (hasPackageJson && hasPyproject) {
|
|
@@ -8017,22 +7817,22 @@ async function scanRepoForProfile(root) {
|
|
|
8017
7817
|
}
|
|
8018
7818
|
}
|
|
8019
7819
|
}
|
|
8020
|
-
if (await pathExists2(
|
|
7820
|
+
if (await pathExists2(path2.join(root, "pipelines"))) {
|
|
8021
7821
|
addScore(scores, reasons, "pipelines_transforms", 3, "Found pipelines/ directory");
|
|
8022
7822
|
}
|
|
8023
|
-
if (await pathExists2(
|
|
7823
|
+
if (await pathExists2(path2.join(root, "transforms"))) {
|
|
8024
7824
|
addScore(scores, reasons, "pipelines_transforms", 3, "Found transforms/ directory");
|
|
8025
7825
|
}
|
|
8026
|
-
if (await pathExists2(
|
|
7826
|
+
if (await pathExists2(path2.join(root, "internal", "pipeline"))) {
|
|
8027
7827
|
addScore(scores, reasons, "pipelines_transforms", 3, "Found internal/pipeline/ directory");
|
|
8028
7828
|
}
|
|
8029
|
-
if (await pathExists2(
|
|
7829
|
+
if (await pathExists2(path2.join(root, "internal", "transforms"))) {
|
|
8030
7830
|
addScore(scores, reasons, "pipelines_transforms", 3, "Found internal/transforms/ directory");
|
|
8031
7831
|
}
|
|
8032
|
-
if (await pathExists2(
|
|
7832
|
+
if (await pathExists2(path2.join(root, "functions"))) {
|
|
8033
7833
|
addScore(scores, reasons, "osdk_functions_ts", 2, "Found functions/ directory");
|
|
8034
7834
|
}
|
|
8035
|
-
if (await pathExists2(
|
|
7835
|
+
if (await pathExists2(path2.join(root, "src", "functions"))) {
|
|
8036
7836
|
addScore(scores, reasons, "osdk_functions_ts", 2, "Found src/functions/ directory");
|
|
8037
7837
|
}
|
|
8038
7838
|
const sampleFiles = await collectSampleFiles(root, 50);
|
|
@@ -8063,7 +7863,7 @@ async function scanRepoForProfile(root) {
|
|
|
8063
7863
|
}
|
|
8064
7864
|
|
|
8065
7865
|
// src/palantir-mcp/commands.ts
|
|
8066
|
-
function
|
|
7866
|
+
function formatError3(err) {
|
|
8067
7867
|
return err instanceof Error ? err.toString() : String(err);
|
|
8068
7868
|
}
|
|
8069
7869
|
function isRecord2(value) {
|
|
@@ -8112,7 +7912,7 @@ async function resolveProfile(worktree) {
|
|
|
8112
7912
|
} catch (err) {
|
|
8113
7913
|
return {
|
|
8114
7914
|
profile: "unknown",
|
|
8115
|
-
reasons: [`Repo scan failed; falling back to unknown: ${
|
|
7915
|
+
reasons: [`Repo scan failed; falling back to unknown: ${formatError3(err)}`]
|
|
8116
7916
|
};
|
|
8117
7917
|
}
|
|
8118
7918
|
}
|
|
@@ -8176,7 +7976,7 @@ async function autoBootstrapPalantirMcpIfConfigured(worktree) {
|
|
|
8176
7976
|
const changed = needsMigration || stableJsonStringify(merged) !== stableJsonStringify(patch.data);
|
|
8177
7977
|
if (!changed)
|
|
8178
7978
|
return;
|
|
8179
|
-
const outPath =
|
|
7979
|
+
const outPath = path3.join(worktree, OPENCODE_JSONC_FILENAME);
|
|
8180
7980
|
const text = stringifyJsonc(patch.data);
|
|
8181
7981
|
await writeFileAtomic(outPath, text);
|
|
8182
7982
|
if (readLegacy.ok) {
|
|
@@ -8237,7 +8037,7 @@ async function setupPalantirMcp(worktree, rawArgs) {
|
|
|
8237
8037
|
try {
|
|
8238
8038
|
toolNames = await listPalantirMcpTools(discoveryUrl);
|
|
8239
8039
|
} catch (err) {
|
|
8240
|
-
return `[ERROR] ${
|
|
8040
|
+
return `[ERROR] ${formatError3(err)}`;
|
|
8241
8041
|
}
|
|
8242
8042
|
if (toolNames.length === 0)
|
|
8243
8043
|
return "[ERROR] palantir-mcp tool discovery returned no tools.";
|
|
@@ -8248,12 +8048,12 @@ async function setupPalantirMcp(worktree, rawArgs) {
|
|
|
8248
8048
|
profile,
|
|
8249
8049
|
allowlist
|
|
8250
8050
|
});
|
|
8251
|
-
const outPath =
|
|
8051
|
+
const outPath = path3.join(worktree, OPENCODE_JSONC_FILENAME);
|
|
8252
8052
|
const text = stringifyJsonc(patch.data);
|
|
8253
8053
|
try {
|
|
8254
8054
|
await writeFileAtomic(outPath, text);
|
|
8255
8055
|
} catch (err) {
|
|
8256
|
-
return `[ERROR] Failed writing ${OPENCODE_JSONC_FILENAME}: ${
|
|
8056
|
+
return `[ERROR] Failed writing ${OPENCODE_JSONC_FILENAME}: ${formatError3(err)}`;
|
|
8257
8057
|
}
|
|
8258
8058
|
let bakInfo = "";
|
|
8259
8059
|
if (readLegacy.ok) {
|
|
@@ -8264,7 +8064,7 @@ async function setupPalantirMcp(worktree, rawArgs) {
|
|
|
8264
8064
|
Migrated legacy ${readLegacy.path} -> ${bakPath}`;
|
|
8265
8065
|
} catch (err) {
|
|
8266
8066
|
bakInfo = `
|
|
8267
|
-
[ERROR] Wrote ${OPENCODE_JSONC_FILENAME}, but failed to rename legacy ${readLegacy.path}: ${
|
|
8067
|
+
[ERROR] Wrote ${OPENCODE_JSONC_FILENAME}, but failed to rename legacy ${readLegacy.path}: ${formatError3(err)}`;
|
|
8268
8068
|
}
|
|
8269
8069
|
}
|
|
8270
8070
|
const warnings = [...normalized.warnings, ...patch.warnings];
|
|
@@ -8318,18 +8118,18 @@ async function rescanPalantirMcpTools(worktree) {
|
|
|
8318
8118
|
try {
|
|
8319
8119
|
toolNames = await listPalantirMcpTools(normalized.url);
|
|
8320
8120
|
} catch (err) {
|
|
8321
|
-
return `[ERROR] ${
|
|
8121
|
+
return `[ERROR] ${formatError3(err)}`;
|
|
8322
8122
|
}
|
|
8323
8123
|
if (toolNames.length === 0)
|
|
8324
8124
|
return "[ERROR] palantir-mcp tool discovery returned no tools.";
|
|
8325
8125
|
const allowlist = computeAllowedTools(profile, toolNames);
|
|
8326
8126
|
const patch = patchConfigForRescan(baseData, { toolNames, profile, allowlist });
|
|
8327
|
-
const outPath =
|
|
8127
|
+
const outPath = path3.join(worktree, OPENCODE_JSONC_FILENAME);
|
|
8328
8128
|
const text = stringifyJsonc(patch.data);
|
|
8329
8129
|
try {
|
|
8330
8130
|
await writeFileAtomic(outPath, text);
|
|
8331
8131
|
} catch (err) {
|
|
8332
|
-
return `[ERROR] Failed writing ${OPENCODE_JSONC_FILENAME}: ${
|
|
8132
|
+
return `[ERROR] Failed writing ${OPENCODE_JSONC_FILENAME}: ${formatError3(err)}`;
|
|
8333
8133
|
}
|
|
8334
8134
|
const warnings = [...normalized.warnings, ...patch.warnings];
|
|
8335
8135
|
return [
|
|
@@ -8342,41 +8142,18 @@ async function rescanPalantirMcpTools(worktree) {
|
|
|
8342
8142
|
}
|
|
8343
8143
|
|
|
8344
8144
|
// src/index.ts
|
|
8145
|
+
var NO_DB_MESSAGE = "Documentation database not found. Run /refresh-docs to download Palantir Foundry documentation.";
|
|
8345
8146
|
var plugin = async (input) => {
|
|
8346
|
-
const dbPath =
|
|
8147
|
+
const dbPath = path4.join(input.worktree, "data", "docs.parquet");
|
|
8347
8148
|
let dbInstance = null;
|
|
8348
|
-
let
|
|
8349
|
-
let autoBootstrapMcpStarted = false;
|
|
8350
|
-
let autoBootstrapDocsStarted = false;
|
|
8351
|
-
function formatError6(err) {
|
|
8352
|
-
return err instanceof Error ? err.toString() : String(err);
|
|
8353
|
-
}
|
|
8354
|
-
function formatBytes(bytes) {
|
|
8355
|
-
if (!Number.isFinite(bytes) || bytes < 0)
|
|
8356
|
-
return "0 B";
|
|
8357
|
-
const units = ["B", "KB", "MB", "GB"];
|
|
8358
|
-
let value = bytes;
|
|
8359
|
-
let index = 0;
|
|
8360
|
-
while (value >= 1024 && index < units.length - 1) {
|
|
8361
|
-
value /= 1024;
|
|
8362
|
-
index += 1;
|
|
8363
|
-
}
|
|
8364
|
-
const decimals = value >= 10 || index === 0 ? 0 : 1;
|
|
8365
|
-
return `${value.toFixed(decimals)} ${units[index]}`;
|
|
8366
|
-
}
|
|
8149
|
+
let autoBootstrapStarted = false;
|
|
8367
8150
|
function ensureCommandDefinitions(cfg) {
|
|
8368
8151
|
if (!cfg.command)
|
|
8369
8152
|
cfg.command = {};
|
|
8370
8153
|
if (!cfg.command["refresh-docs"]) {
|
|
8371
8154
|
cfg.command["refresh-docs"] = {
|
|
8372
|
-
template: "Refresh Palantir
|
|
8373
|
-
description: "
|
|
8374
|
-
};
|
|
8375
|
-
}
|
|
8376
|
-
if (!cfg.command["refresh-docs-rescrape"]) {
|
|
8377
|
-
cfg.command["refresh-docs-rescrape"] = {
|
|
8378
|
-
template: "Refresh docs by live rescrape (unsafe/experimental).",
|
|
8379
|
-
description: "Explicit fallback: rescrape palantir.com docs and rebuild data/docs.parquet. Slower and less reliable than /refresh-docs."
|
|
8155
|
+
template: "Refresh Palantir documentation database.",
|
|
8156
|
+
description: "Download Palantir docs and write data/docs.parquet (local)."
|
|
8380
8157
|
};
|
|
8381
8158
|
}
|
|
8382
8159
|
if (!cfg.command["setup-palantir-mcp"]) {
|
|
@@ -8443,8 +8220,8 @@ var plugin = async (input) => {
|
|
|
8443
8220
|
ensureAgentDefaults2(foundry, "foundry");
|
|
8444
8221
|
cfg.agent.foundry = foundry;
|
|
8445
8222
|
}
|
|
8446
|
-
function
|
|
8447
|
-
if (
|
|
8223
|
+
function maybeStartAutoBootstrap() {
|
|
8224
|
+
if (autoBootstrapStarted)
|
|
8448
8225
|
return;
|
|
8449
8226
|
const token = process.env.FOUNDRY_TOKEN;
|
|
8450
8227
|
const url = process.env.FOUNDRY_URL;
|
|
@@ -8452,105 +8229,20 @@ var plugin = async (input) => {
|
|
|
8452
8229
|
return;
|
|
8453
8230
|
if (!url || url.trim().length === 0)
|
|
8454
8231
|
return;
|
|
8455
|
-
|
|
8232
|
+
autoBootstrapStarted = true;
|
|
8456
8233
|
autoBootstrapPalantirMcpIfConfigured(input.worktree);
|
|
8457
8234
|
}
|
|
8458
|
-
function maybeStartAutoBootstrapDocs() {
|
|
8459
|
-
if (autoBootstrapDocsStarted)
|
|
8460
|
-
return;
|
|
8461
|
-
autoBootstrapDocsStarted = true;
|
|
8462
|
-
ensureDocsAvailable().catch(() => {});
|
|
8463
|
-
}
|
|
8464
|
-
function resetDb() {
|
|
8465
|
-
if (dbInstance)
|
|
8466
|
-
closeDatabase(dbInstance);
|
|
8467
|
-
dbInstance = null;
|
|
8468
|
-
dbInitPromise = null;
|
|
8469
|
-
}
|
|
8470
8235
|
async function getDb() {
|
|
8471
|
-
if (dbInstance)
|
|
8472
|
-
|
|
8473
|
-
if (dbInitPromise)
|
|
8474
|
-
return dbInitPromise;
|
|
8475
|
-
dbInitPromise = createDatabase(dbPath).then((created) => {
|
|
8476
|
-
dbInstance = created;
|
|
8477
|
-
return created;
|
|
8478
|
-
}).finally(() => {
|
|
8479
|
-
dbInitPromise = null;
|
|
8480
|
-
});
|
|
8481
|
-
return dbInitPromise;
|
|
8482
|
-
}
|
|
8483
|
-
function pushText(output, text) {
|
|
8484
|
-
output.parts.push({ type: "text", text });
|
|
8485
|
-
}
|
|
8486
|
-
function formatSnapshotFailure(err) {
|
|
8487
|
-
return [
|
|
8488
|
-
"[ERROR] Unable to obtain Palantir docs snapshot.",
|
|
8489
|
-
"",
|
|
8490
|
-
`Reason: ${formatError6(err)}`,
|
|
8491
|
-
"",
|
|
8492
|
-
"Next steps:",
|
|
8493
|
-
"- Retry /refresh-docs (recommended prebuilt snapshot path).",
|
|
8494
|
-
"- If snapshot download is blocked, run /refresh-docs-rescrape (unsafe/experimental)."
|
|
8495
|
-
].join(`
|
|
8496
|
-
`);
|
|
8497
|
-
}
|
|
8498
|
-
function formatSnapshotRefreshEvent(event) {
|
|
8499
|
-
if (event.type === "skip-existing")
|
|
8500
|
-
return `snapshot_status=already_present bytes=${event.bytes}`;
|
|
8501
|
-
if (event.type === "download-start")
|
|
8502
|
-
return `download_attempt url=${event.url}`;
|
|
8503
|
-
if (event.type === "download-failed")
|
|
8504
|
-
return `download_failed url=${event.url} error=${event.error}`;
|
|
8505
|
-
if (event.type === "download-success")
|
|
8506
|
-
return `download_succeeded url=${event.url} bytes=${event.bytes}`;
|
|
8507
|
-
if (event.type === "copy-start")
|
|
8508
|
-
return `copy_attempt source=${event.sourcePath}`;
|
|
8509
|
-
if (event.type === "copy-success")
|
|
8510
|
-
return `copy_succeeded source=${event.sourcePath} bytes=${event.bytes}`;
|
|
8511
|
-
return null;
|
|
8512
|
-
}
|
|
8513
|
-
async function ensureDocsAvailable(options = {}) {
|
|
8514
|
-
return ensureDocsParquet({
|
|
8515
|
-
dbPath,
|
|
8516
|
-
force: options.force === true,
|
|
8517
|
-
pluginDirectory: input.directory,
|
|
8518
|
-
onEvent: options.onEvent
|
|
8519
|
-
});
|
|
8520
|
-
}
|
|
8521
|
-
async function ensureDocsReadyForTool() {
|
|
8522
|
-
try {
|
|
8523
|
-
await ensureDocsAvailable();
|
|
8524
|
-
return null;
|
|
8525
|
-
} catch (err) {
|
|
8526
|
-
return formatSnapshotFailure(err);
|
|
8236
|
+
if (!dbInstance) {
|
|
8237
|
+
dbInstance = await createDatabase(dbPath);
|
|
8527
8238
|
}
|
|
8239
|
+
return dbInstance;
|
|
8528
8240
|
}
|
|
8529
|
-
function
|
|
8530
|
-
return
|
|
8531
|
-
"[ERROR] /refresh-docs-rescrape failed.",
|
|
8532
|
-
"",
|
|
8533
|
-
`Reason: ${formatError6(err)}`,
|
|
8534
|
-
"",
|
|
8535
|
-
"Try /refresh-docs for the recommended prebuilt snapshot flow."
|
|
8536
|
-
].join(`
|
|
8537
|
-
`);
|
|
8241
|
+
async function dbExists() {
|
|
8242
|
+
return Bun.file(dbPath).exists();
|
|
8538
8243
|
}
|
|
8539
|
-
function
|
|
8540
|
-
|
|
8541
|
-
progressLines.push(`discovered_pages=${event.totalPages}`);
|
|
8542
|
-
return;
|
|
8543
|
-
}
|
|
8544
|
-
if (event.type === "progress") {
|
|
8545
|
-
progressLines.push(`processed_pages=${event.processedPages}/${event.totalPages}`);
|
|
8546
|
-
return;
|
|
8547
|
-
}
|
|
8548
|
-
if (event.type === "page-failed") {
|
|
8549
|
-
if (failureSamples.length < 5) {
|
|
8550
|
-
failureSamples.push(`url=${event.url} error=${event.error}`);
|
|
8551
|
-
}
|
|
8552
|
-
return;
|
|
8553
|
-
}
|
|
8244
|
+
function pushText(output, text) {
|
|
8245
|
+
output.parts.push({ type: "text", text });
|
|
8554
8246
|
}
|
|
8555
8247
|
function toPathname(inputUrl) {
|
|
8556
8248
|
const trimmed = inputUrl.trim();
|
|
@@ -8605,8 +8297,8 @@ var plugin = async (input) => {
|
|
|
8605
8297
|
function isInScope(pageUrl, scope) {
|
|
8606
8298
|
if (scope === "all")
|
|
8607
8299
|
return true;
|
|
8608
|
-
const
|
|
8609
|
-
return
|
|
8300
|
+
const path5 = toPathname(pageUrl);
|
|
8301
|
+
return path5.startsWith(`/${scope}/`) || path5.startsWith(`/docs/${scope}/`);
|
|
8610
8302
|
}
|
|
8611
8303
|
function tokenizeQuery(query) {
|
|
8612
8304
|
const tokens = query.toLowerCase().trim().split(/[\s/._-]+/g).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
@@ -8616,13 +8308,13 @@ var plugin = async (input) => {
|
|
|
8616
8308
|
const q = query.toLowerCase().trim();
|
|
8617
8309
|
if (q.length === 0)
|
|
8618
8310
|
return 0;
|
|
8619
|
-
const
|
|
8311
|
+
const path5 = toPathname(page.url).toLowerCase();
|
|
8620
8312
|
const title = page.title.toLowerCase();
|
|
8621
|
-
if (
|
|
8313
|
+
if (path5 === q)
|
|
8622
8314
|
return 2000;
|
|
8623
|
-
if (
|
|
8315
|
+
if (path5 === toPathname(q).toLowerCase())
|
|
8624
8316
|
return 2000;
|
|
8625
|
-
if (
|
|
8317
|
+
if (path5.includes(q))
|
|
8626
8318
|
return 1200;
|
|
8627
8319
|
if (title.includes(q))
|
|
8628
8320
|
return 1000;
|
|
@@ -8633,10 +8325,10 @@ var plugin = async (input) => {
|
|
|
8633
8325
|
for (const t of tokens) {
|
|
8634
8326
|
if (title.includes(t))
|
|
8635
8327
|
score += 40;
|
|
8636
|
-
if (
|
|
8328
|
+
if (path5.includes(t))
|
|
8637
8329
|
score += 30;
|
|
8638
8330
|
}
|
|
8639
|
-
if (
|
|
8331
|
+
if (path5.startsWith(q))
|
|
8640
8332
|
score += 100;
|
|
8641
8333
|
if (title.startsWith(q))
|
|
8642
8334
|
score += 100;
|
|
@@ -8646,8 +8338,7 @@ var plugin = async (input) => {
|
|
|
8646
8338
|
config: async (cfg) => {
|
|
8647
8339
|
ensureCommandDefinitions(cfg);
|
|
8648
8340
|
ensureAgentDefinitions(cfg);
|
|
8649
|
-
|
|
8650
|
-
maybeStartAutoBootstrapDocs();
|
|
8341
|
+
maybeStartAutoBootstrap();
|
|
8651
8342
|
},
|
|
8652
8343
|
tool: {
|
|
8653
8344
|
get_doc_page: tool({
|
|
@@ -8658,9 +8349,8 @@ var plugin = async (input) => {
|
|
|
8658
8349
|
scope: tool.schema.enum(["foundry", "apollo", "gotham", "all"]).optional().describe("Scope to search within when using query or fuzzy matching (default: foundry).")
|
|
8659
8350
|
},
|
|
8660
8351
|
async execute(args) {
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
return docsError;
|
|
8352
|
+
if (!await dbExists())
|
|
8353
|
+
return NO_DB_MESSAGE;
|
|
8664
8354
|
const scope = parseScope(args.scope);
|
|
8665
8355
|
if (!scope) {
|
|
8666
8356
|
return [
|
|
@@ -8732,9 +8422,8 @@ ${bestPage.content}`;
|
|
|
8732
8422
|
query: tool.schema.string().optional().describe("Optional query to filter/rank results by title/URL (case-insensitive).")
|
|
8733
8423
|
},
|
|
8734
8424
|
async execute(args) {
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
return docsError;
|
|
8425
|
+
if (!await dbExists())
|
|
8426
|
+
return NO_DB_MESSAGE;
|
|
8738
8427
|
const scope = parseScope(args.scope);
|
|
8739
8428
|
if (!scope) {
|
|
8740
8429
|
return [
|
|
@@ -8813,65 +8502,12 @@ ${bestPage.content}`;
|
|
|
8813
8502
|
},
|
|
8814
8503
|
"command.execute.before": async (hookInput, output) => {
|
|
8815
8504
|
if (hookInput.command === "refresh-docs") {
|
|
8816
|
-
const
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
onEvent: (event) => {
|
|
8821
|
-
const line = formatSnapshotRefreshEvent(event);
|
|
8822
|
-
if (line)
|
|
8823
|
-
progressLines.push(line);
|
|
8824
|
-
}
|
|
8825
|
-
});
|
|
8826
|
-
resetDb();
|
|
8827
|
-
const db = await getDb();
|
|
8828
|
-
const indexedPages = getAllPages(db).length;
|
|
8829
|
-
pushText(output, [
|
|
8830
|
-
"refresh-docs complete (recommended snapshot path).",
|
|
8831
|
-
"",
|
|
8832
|
-
...progressLines.map((line) => `- ${line}`),
|
|
8833
|
-
...progressLines.length > 0 ? [""] : [],
|
|
8834
|
-
`snapshot_source=${result.source}`,
|
|
8835
|
-
result.downloadUrl ? `snapshot_url=${result.downloadUrl}` : null,
|
|
8836
|
-
`snapshot_bytes=${result.bytes} (${formatBytes(result.bytes)})`,
|
|
8837
|
-
`indexed_pages=${indexedPages}`
|
|
8838
|
-
].filter((line) => !!line).join(`
|
|
8839
|
-
`));
|
|
8840
|
-
} catch (err) {
|
|
8841
|
-
pushText(output, formatSnapshotFailure(err));
|
|
8842
|
-
}
|
|
8843
|
-
return;
|
|
8844
|
-
}
|
|
8845
|
-
if (hookInput.command === "refresh-docs-rescrape") {
|
|
8846
|
-
const progressLines = [];
|
|
8847
|
-
const failureSamples = [];
|
|
8848
|
-
try {
|
|
8849
|
-
const result = await fetchAllDocs(dbPath, {
|
|
8850
|
-
progressEvery: 250,
|
|
8851
|
-
onProgress: (event) => {
|
|
8852
|
-
formatRescrapeProgressEvent(event, progressLines, failureSamples);
|
|
8853
|
-
}
|
|
8854
|
-
});
|
|
8855
|
-
resetDb();
|
|
8856
|
-
const db = await getDb();
|
|
8857
|
-
const indexedPages = getAllPages(db).length;
|
|
8858
|
-
pushText(output, [
|
|
8859
|
-
"refresh-docs-rescrape complete (unsafe/experimental).",
|
|
8860
|
-
"",
|
|
8861
|
-
"Warning: this command live-scrapes palantir.com and is slower/less reliable than /refresh-docs.",
|
|
8862
|
-
"",
|
|
8863
|
-
...progressLines.map((line) => `- ${line}`),
|
|
8864
|
-
...progressLines.length > 0 ? [""] : [],
|
|
8865
|
-
`total_pages=${result.totalPages}`,
|
|
8866
|
-
`fetched_pages=${result.fetchedPages}`,
|
|
8867
|
-
`failed_pages=${result.failedUrls.length}`,
|
|
8868
|
-
`indexed_pages=${indexedPages}`,
|
|
8869
|
-
...failureSamples.length > 0 ? ["", "failure_samples:", ...failureSamples.map((line) => `- ${line}`)] : []
|
|
8870
|
-
].join(`
|
|
8871
|
-
`));
|
|
8872
|
-
} catch (err) {
|
|
8873
|
-
pushText(output, formatRescrapeFailure(err));
|
|
8505
|
+
const result = await fetchAllDocs(dbPath);
|
|
8506
|
+
if (dbInstance) {
|
|
8507
|
+
closeDatabase(dbInstance);
|
|
8508
|
+
dbInstance = null;
|
|
8874
8509
|
}
|
|
8510
|
+
pushText(output, `Refreshed documentation: ${result.fetchedPages}/${result.totalPages} pages fetched. ${result.failedUrls.length} failures.`);
|
|
8875
8511
|
return;
|
|
8876
8512
|
}
|
|
8877
8513
|
if (hookInput.command === "setup-palantir-mcp") {
|