@openontology/opencode-palantir 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/index.js +232 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8068,36 +8068,254 @@ var plugin = async (input) => {
8068
8068
  function pushText(output, text) {
8069
8069
  output.parts.push({ type: "text", text });
8070
8070
  }
8071
+ function toPathname(inputUrl) {
8072
+ const trimmed = inputUrl.trim();
8073
+ if (trimmed.length === 0)
8074
+ return "";
8075
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
8076
+ try {
8077
+ return new URL(trimmed).pathname;
8078
+ } catch {}
8079
+ }
8080
+ if (trimmed.startsWith("www.")) {
8081
+ try {
8082
+ return new URL(`https://${trimmed}`).pathname;
8083
+ } catch {}
8084
+ }
8085
+ const noQueryOrHash = trimmed.split("#")[0].split("?")[0];
8086
+ if (noQueryOrHash.startsWith("/"))
8087
+ return noQueryOrHash;
8088
+ return `/${noQueryOrHash}`;
8089
+ }
8090
+ function makeUrlCandidates(inputUrl) {
8091
+ const raw = inputUrl.trim();
8092
+ const pathOnly = toPathname(raw);
8093
+ const candidates = new Set;
8094
+ function addVariant(u) {
8095
+ const v = u.trim();
8096
+ if (v.length === 0)
8097
+ return;
8098
+ candidates.add(v);
8099
+ if (v.endsWith("/") && v.length > 1)
8100
+ candidates.add(v.slice(0, -1));
8101
+ else
8102
+ candidates.add(`${v}/`);
8103
+ }
8104
+ addVariant(raw);
8105
+ addVariant(pathOnly);
8106
+ if (pathOnly.startsWith("/docs/")) {
8107
+ addVariant(pathOnly.replace(/^\/docs(?=\/)/, ""));
8108
+ } else if (pathOnly.startsWith("/foundry/") || pathOnly.startsWith("/apollo/") || pathOnly.startsWith("/gotham/")) {
8109
+ addVariant(`/docs${pathOnly}`);
8110
+ }
8111
+ return Array.from(candidates);
8112
+ }
8113
+ function parseScope(rawScope) {
8114
+ if (rawScope === undefined)
8115
+ return "foundry";
8116
+ if (rawScope === "foundry" || rawScope === "apollo" || rawScope === "gotham" || rawScope === "all") {
8117
+ return rawScope;
8118
+ }
8119
+ return null;
8120
+ }
8121
+ function isInScope(pageUrl, scope) {
8122
+ if (scope === "all")
8123
+ return true;
8124
+ const path5 = toPathname(pageUrl);
8125
+ return path5.startsWith(`/${scope}/`) || path5.startsWith(`/docs/${scope}/`);
8126
+ }
8127
+ function tokenizeQuery(query) {
8128
+ const tokens = query.toLowerCase().trim().split(/[\s/._-]+/g).map((t) => t.trim()).filter((t) => t.length > 0);
8129
+ return Array.from(new Set(tokens));
8130
+ }
8131
+ function scorePageMatch(page, query) {
8132
+ const q = query.toLowerCase().trim();
8133
+ if (q.length === 0)
8134
+ return 0;
8135
+ const path5 = toPathname(page.url).toLowerCase();
8136
+ const title = page.title.toLowerCase();
8137
+ if (path5 === q)
8138
+ return 2000;
8139
+ if (path5 === toPathname(q).toLowerCase())
8140
+ return 2000;
8141
+ if (path5.includes(q))
8142
+ return 1200;
8143
+ if (title.includes(q))
8144
+ return 1000;
8145
+ const tokens = tokenizeQuery(q);
8146
+ if (tokens.length === 0)
8147
+ return 0;
8148
+ let score = 0;
8149
+ for (const t of tokens) {
8150
+ if (title.includes(t))
8151
+ score += 40;
8152
+ if (path5.includes(t))
8153
+ score += 30;
8154
+ }
8155
+ if (path5.startsWith(q))
8156
+ score += 100;
8157
+ if (title.startsWith(q))
8158
+ score += 100;
8159
+ return score;
8160
+ }
8071
8161
  return {
8072
8162
  tool: {
8073
8163
  get_doc_page: tool({
8074
- description: "Retrieve a Palantir Foundry documentation page by its URL path. Use this when you need the full content of a specific documentation page.",
8164
+ description: "Retrieve a Palantir documentation page. Provide either a URL path (preferred) or a free-text query; the tool will handle common URL variants (full URLs, missing /docs prefix, trailing slashes).",
8075
8165
  args: {
8076
- url: tool.schema.string().describe("The URL path of the doc page, e.g. /docs/foundry/ontology/overview/")
8166
+ url: tool.schema.string().optional().describe("Doc URL path or full URL, e.g. /foundry/compute-modules/overview/"),
8167
+ query: tool.schema.string().optional().describe('Free-text query to find the most relevant page, e.g. "compute modules".'),
8168
+ scope: tool.schema.enum(["foundry", "apollo", "gotham", "all"]).optional().describe("Scope to search within when using query or fuzzy matching (default: foundry).")
8077
8169
  },
8078
8170
  async execute(args) {
8079
8171
  if (!await dbExists())
8080
8172
  return NO_DB_MESSAGE;
8173
+ const scope = parseScope(args.scope);
8174
+ if (!scope) {
8175
+ return [
8176
+ "[ERROR] Invalid scope. Must be one of: foundry, apollo, gotham, all.",
8177
+ 'Example: get_doc_page with { "query": "compute modules", "scope": "foundry" }'
8178
+ ].join(`
8179
+ `);
8180
+ }
8181
+ const rawUrl = args.url;
8182
+ const rawQuery = args.query;
8183
+ const urlInput = typeof rawUrl === "string" ? rawUrl.trim() : null;
8184
+ const queryInput = typeof rawQuery === "string" ? rawQuery.trim() : null;
8185
+ if ((!urlInput || urlInput.length === 0) && (!queryInput || queryInput.length === 0)) {
8186
+ return [
8187
+ '[ERROR] Missing input. Provide either "url" or "query".',
8188
+ 'Example: get_doc_page with { "url": "/foundry/compute-modules/overview/" }',
8189
+ 'Example: get_doc_page with { "query": "compute modules", "scope": "foundry" }'
8190
+ ].join(`
8191
+ `);
8192
+ }
8081
8193
  const db = await getDb();
8082
- const page = await getPage(db, args.url);
8083
- if (!page)
8084
- return `Page not found: ${args.url}`;
8085
- return page.content;
8194
+ if (urlInput && urlInput.length > 0) {
8195
+ const candidates = makeUrlCandidates(urlInput);
8196
+ for (const c of candidates) {
8197
+ const page = await getPage(db, c);
8198
+ if (page)
8199
+ return page.content;
8200
+ }
8201
+ }
8202
+ const q = (queryInput && queryInput.length > 0 ? queryInput : urlInput) ?? "";
8203
+ const pages = getAllPages(db);
8204
+ const ranked = pages.filter((p) => isInScope(p.url, scope)).map((p) => ({ page: p, score: scorePageMatch(p, q) })).filter((r) => r.score > 0).sort((a, b) => {
8205
+ if (b.score !== a.score)
8206
+ return b.score - a.score;
8207
+ return toPathname(a.page.url).localeCompare(toPathname(b.page.url));
8208
+ }).slice(0, 5);
8209
+ if (ranked.length === 0) {
8210
+ return `Page not found: ${urlInput ?? q}`;
8211
+ }
8212
+ const best = ranked[0];
8213
+ const bestPage = await getPage(db, best.page.url);
8214
+ if (bestPage) {
8215
+ const strong = best.score >= 200 || toPathname(best.page.url).toLowerCase() === toPathname(q).toLowerCase() || tokenizeQuery(q).every((t) => (best.page.title + " " + toPathname(best.page.url)).toLowerCase().includes(t));
8216
+ if (strong) {
8217
+ return `Matched: ${best.page.title} (${best.page.url})
8218
+
8219
+ ${bestPage.content}`;
8220
+ }
8221
+ }
8222
+ const suggestions = ranked.map((r) => `- ${r.page.title} (${r.page.url})`).join(`
8223
+ `);
8224
+ return [
8225
+ `Page not found: ${urlInput ?? q}`,
8226
+ "",
8227
+ "Top matches:",
8228
+ suggestions,
8229
+ "",
8230
+ "Tip: call get_doc_page with an exact URL from the list above."
8231
+ ].join(`
8232
+ `);
8086
8233
  }
8087
8234
  }),
8088
8235
  list_all_docs: tool({
8089
- description: "List all available Palantir Foundry documentation pages with their URLs and titles. Use this to discover what documentation is available.",
8090
- args: {},
8091
- async execute() {
8236
+ description: "List available Palantir documentation pages with their URLs and titles. Supports pagination and optional scope filtering (default: foundry). Use this to discover what documentation is available.",
8237
+ args: {
8238
+ limit: tool.schema.number().int().min(1).max(200).optional().describe("Max results to return (default: 50, max: 200)."),
8239
+ offset: tool.schema.number().int().min(0).optional().describe("Zero-based offset into the filtered, deterministic listing (default: 0)."),
8240
+ scope: tool.schema.enum(["foundry", "apollo", "gotham", "all"]).optional().describe("Doc scope filter by URL prefix /<scope>/ (default: foundry)."),
8241
+ query: tool.schema.string().optional().describe("Optional query to filter/rank results by title/URL (case-insensitive).")
8242
+ },
8243
+ async execute(args) {
8092
8244
  if (!await dbExists())
8093
8245
  return NO_DB_MESSAGE;
8246
+ const scope = parseScope(args.scope);
8247
+ if (!scope) {
8248
+ return [
8249
+ "[ERROR] Invalid scope. Must be one of: foundry, apollo, gotham, all.",
8250
+ 'Example: list_all_docs with { "scope": "foundry", "offset": 0, "limit": 50 }'
8251
+ ].join(`
8252
+ `);
8253
+ }
8254
+ const rawLimit = args.limit;
8255
+ const limit = rawLimit === undefined ? 50 : rawLimit;
8256
+ if (!Number.isFinite(limit) || !Number.isInteger(limit) || limit < 1 || limit > 200) {
8257
+ return [
8258
+ "[ERROR] Invalid limit. Must be an integer between 1 and 200.",
8259
+ 'Example: list_all_docs with { "scope": "foundry", "offset": 0, "limit": 50 }'
8260
+ ].join(`
8261
+ `);
8262
+ }
8263
+ const rawOffset = args.offset;
8264
+ const offset = rawOffset === undefined ? 0 : rawOffset;
8265
+ if (!Number.isFinite(offset) || !Number.isInteger(offset) || offset < 0) {
8266
+ return [
8267
+ "[ERROR] Invalid offset. Must be an integer >= 0.",
8268
+ 'Example: list_all_docs with { "scope": "foundry", "offset": 0, "limit": 50 }'
8269
+ ].join(`
8270
+ `);
8271
+ }
8272
+ const rawQuery = args.query;
8273
+ const query = typeof rawQuery === "string" ? rawQuery.trim() : null;
8274
+ if (query && query.length > 200) {
8275
+ return "[ERROR] Query is too long. Please use 200 characters or fewer.";
8276
+ }
8094
8277
  const db = await getDb();
8095
- const pages = getAllPages(db);
8096
- const lines = pages.map((p) => `- ${p.title} (${p.url})`);
8097
- return `Available Palantir Foundry Documentation (${pages.length} pages):
8098
-
8099
- ${lines.join(`
8278
+ const pages = getAllPages(db).slice();
8279
+ const scoped = pages.filter((p) => isInScope(p.url, scope));
8280
+ const filteredWithScores = query && query.length > 0 ? scoped.map((p) => ({ page: p, score: scorePageMatch(p, query) })).filter((r) => r.score > 0).sort((a, b) => {
8281
+ if (b.score !== a.score)
8282
+ return b.score - a.score;
8283
+ return toPathname(a.page.url).localeCompare(toPathname(b.page.url));
8284
+ }) : scoped.map((p) => ({ page: p, score: 0 })).sort((a, b) => toPathname(a.page.url).localeCompare(toPathname(b.page.url)));
8285
+ const total = filteredWithScores.length;
8286
+ if (offset >= total) {
8287
+ const safeOffset = Math.max(0, total - limit);
8288
+ return [
8289
+ `Available Palantir Documentation Pages`,
8290
+ `scope=${scope} query=${query ?? ""} total=${total} returned=0 offset=${offset} limit=${limit}`,
8291
+ "",
8292
+ `Offset ${offset} is beyond total ${total}.`,
8293
+ `Try: list_all_docs with { "scope": "${scope}", "offset": ${safeOffset}, "limit": ${limit} }`
8294
+ ].join(`
8295
+ `);
8296
+ }
8297
+ const page = filteredWithScores.slice(offset, offset + limit).map((r) => r.page);
8298
+ const lines = page.map((p) => `- ${p.title} (${p.url})`);
8299
+ const returned = page.length;
8300
+ const nextOffset = offset + returned;
8301
+ const hasMore = nextOffset < total;
8302
+ const header = [
8303
+ `Available Palantir Documentation Pages`,
8304
+ `scope=${scope} query=${query ?? ""} total=${total} returned=${returned} offset=${offset} limit=${limit}`,
8305
+ ""
8306
+ ].join(`
8307
+ `);
8308
+ if (!hasMore) {
8309
+ return `${header}${lines.join(`
8100
8310
  `)}`;
8311
+ }
8312
+ return [
8313
+ header + lines.join(`
8314
+ `),
8315
+ "",
8316
+ `Next: call list_all_docs with { "scope": "${scope}", "offset": ${nextOffset}, "limit": ${limit} }`
8317
+ ].join(`
8318
+ `);
8101
8319
  }
8102
8320
  })
8103
8321
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openontology/opencode-palantir",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "collection of tools, agents, hooks to supercharge development in foundry",
5
5
  "license": "MIT",
6
6
  "author": {