@sheetpane/mcp 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -2
- package/dist/ERRORS.md +811 -0
- package/dist/api.d.ts +7 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +67 -0
- package/dist/api.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +34 -0
- package/dist/bin.js.map +1 -0
- package/dist/custom-elements.json +1246 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins.json +50 -0
- package/dist/protocol.d.ts +29 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +52 -0
- package/dist/protocol.js.map +1 -0
- package/dist/tools.d.ts +36 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +413 -0
- package/dist/tools.js.map +1 -0
- package/package.json +40 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { handle, LATEST, SUPPORTED, SERVER_INFO, type Request, type Response } from './protocol.js';
|
|
2
|
+
export { TOOLS, callTool, inspectWorkbook, draftRules, checkRules, explainCode, findSkill, guessHeaderRow, type Tool, type ToolResult } from './tools.js';
|
|
3
|
+
export { describeApi } from './api.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1J,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @sheetpane/mcp (W-39): the server's message handler and its tools, for embedding or testing;
|
|
2
|
+
// `sheetpane-mcp` (bin.ts) runs it over stdio.
|
|
3
|
+
export { handle, LATEST, SUPPORTED, SERVER_INFO } from './protocol.js';
|
|
4
|
+
export { TOOLS, callTool, inspectWorkbook, draftRules, checkRules, explainCode, findSkill, guessHeaderRow } from './tools.js';
|
|
5
|
+
export { describeApi } from './api.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,+CAA+C;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAA+B,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAA8B,MAAM,YAAY,CAAC;AAC1J,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "charts",
|
|
4
|
+
"package": "@sheetpane/charts",
|
|
5
|
+
"description": "Charts for sheetpane — a frame at the chart's anchor with its title and Show source cells; later, own SVG for bar, column, line, pie and scatter — through the `overlay` hook.",
|
|
6
|
+
"record": "records/0113_CHARTS.md"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "comments",
|
|
10
|
+
"package": "@sheetpane/comments",
|
|
11
|
+
"description": "Comments and notes for sheetpane — the file's notes and threaded comments read into the error pane; every sheetpane error written back as a threaded comment on export.",
|
|
12
|
+
"record": "records/0112_COMMENTS.md"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "condfmt",
|
|
16
|
+
"package": "@sheetpane/condfmt",
|
|
17
|
+
"description": "Conditional formatting for sheetpane — cellIs, containsText, duplicates, top10, colour scales, data bars — rendered through the `decorate` hook after @sheetpane/styles.",
|
|
18
|
+
"record": "records/0114_CONDFMT.md"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "formulas",
|
|
22
|
+
"package": "@sheetpane/formulas",
|
|
23
|
+
"description": "Formula recalculation for sheetpane — edit a cell and every dependent formula updates — as a plugin on the `derive` hook, backed by IronCalc (WASM, lazy-loaded).",
|
|
24
|
+
"record": "records/0111_FORMULAS.md"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "images",
|
|
28
|
+
"package": "@sheetpane/images",
|
|
29
|
+
"description": "Images for sheetpane — pictures anchored in the sheet drawn in sheet space through the `overlay` hook; view only, kept on export.",
|
|
30
|
+
"record": "records/0107_IMAGES.md"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "numfmt",
|
|
34
|
+
"package": "@sheetpane/numfmt",
|
|
35
|
+
"description": "Excel number formats for sheetpane — currency, dates, fractions, elapsed time, conditional sections, locales — as a plugin on the `format` hook.",
|
|
36
|
+
"record": "records/0109_NUMFMT.md"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "styles",
|
|
40
|
+
"package": "@sheetpane/styles",
|
|
41
|
+
"description": "Cell styles for sheetpane — fill, bold, italic, underline, colour, borders, alignment, wrap — as a plugin on the `decorate` hook. Never a font family (SR-LAW-5).",
|
|
42
|
+
"record": "records/0110_STYLES.md"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "validation",
|
|
46
|
+
"package": "@sheetpane/validation",
|
|
47
|
+
"description": "Data validation for sheetpane — the workbook's own `<dataValidations>` rules checked on load and on every edit, landing in <sheetpane-errors> — as a plugin on the `validate` hook.",
|
|
48
|
+
"record": "records/0108_VALIDATION.md"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const LATEST = "2026-07-28";
|
|
2
|
+
export declare const SUPPORTED: readonly ["2026-07-28", '2025-11-25', '2025-06-18'];
|
|
3
|
+
export declare const SERVER_INFO: {
|
|
4
|
+
name: string;
|
|
5
|
+
title: string;
|
|
6
|
+
version: string;
|
|
7
|
+
};
|
|
8
|
+
export interface Request {
|
|
9
|
+
jsonrpc: '2.0';
|
|
10
|
+
id?: string | number | null;
|
|
11
|
+
method: string;
|
|
12
|
+
params?: {
|
|
13
|
+
_meta?: Record<string, unknown>;
|
|
14
|
+
[k: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface Response {
|
|
18
|
+
jsonrpc: '2.0';
|
|
19
|
+
id: string | number | null;
|
|
20
|
+
result?: unknown;
|
|
21
|
+
error?: {
|
|
22
|
+
code: number;
|
|
23
|
+
message: string;
|
|
24
|
+
data?: unknown;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Answers one message; a notification (no `id`) gets no answer. */
|
|
28
|
+
export declare function handle(msg: unknown): Promise<Response | undefined>;
|
|
29
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,MAAM,eAAe,CAAC;AACnC,eAAO,MAAM,SAAS,0BAAY,YAAY,EAAE,YAAY,CAAU,CAAC;AACvE,eAAO,MAAM,WAAW;IAAK,IAAI;IAAmB,KAAK;IAAe,OAAO;CAAW,CAAC;AAI3F,MAAM,WAAW,OAAO;IAAG,OAAO,EAAE,KAAK,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAAE;AAC5J,MAAM,WAAW,QAAQ;IAAG,OAAO,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE;AAMrJ,oEAAoE;AACpE,wBAAsB,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAiCxE"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// A Model Context Protocol server over stdio, hand-written (W-39): JSON-RPC 2.0, one message per
|
|
2
|
+
// line. It speaks the 2026-07-28 revision — stateless: no handshake, each request carries
|
|
3
|
+
// `io.modelcontextprotocol/protocolVersion` in `_meta`, `server/discover` advertises versions and
|
|
4
|
+
// capabilities, every result has `resultType`, `tools/list` is cacheable (`ttlMs`, `cacheScope`).
|
|
5
|
+
// Clients on 2025-11-25 still work: `initialize` is answered and a request without the version
|
|
6
|
+
// meta is taken as that revision. Tools only: no resources, prompts, sampling or roots.
|
|
7
|
+
import { TOOLS, callTool } from './tools.js';
|
|
8
|
+
export const LATEST = '2026-07-28';
|
|
9
|
+
export const SUPPORTED = [LATEST, '2025-11-25', '2025-06-18'];
|
|
10
|
+
export const SERVER_INFO = { name: 'sheetpane-mcp', title: 'sheetpane', version: '0.0.0' };
|
|
11
|
+
const UNSUPPORTED_PROTOCOL_VERSION = -32022;
|
|
12
|
+
const ok = (id, result, legacy) => ({ jsonrpc: '2.0', id, result: legacy ? result : { resultType: 'complete', ...result, _meta: { 'io.modelcontextprotocol/serverInfo': SERVER_INFO } } });
|
|
13
|
+
const fail = (id, code, message, data) => ({ jsonrpc: '2.0', id, error: { code, message, ...(data === undefined ? {} : { data }) } });
|
|
14
|
+
/** Answers one message; a notification (no `id`) gets no answer. */
|
|
15
|
+
export async function handle(msg) {
|
|
16
|
+
if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0' || typeof msg.method !== 'string')
|
|
17
|
+
return fail(null, -32600, 'Invalid Request');
|
|
18
|
+
const req = msg;
|
|
19
|
+
if (req.id === undefined)
|
|
20
|
+
return undefined; // notifications (e.g. notifications/initialized): nothing to say
|
|
21
|
+
const id = req.id;
|
|
22
|
+
// the revision: from the request's _meta (2026-07-28), else an older client
|
|
23
|
+
const asked = req.params?._meta?.['io.modelcontextprotocol/protocolVersion'];
|
|
24
|
+
const legacy = asked === undefined || asked !== LATEST;
|
|
25
|
+
if (typeof asked === 'string' && !SUPPORTED.includes(asked)) {
|
|
26
|
+
return fail(id, UNSUPPORTED_PROTOCOL_VERSION, `Unsupported protocol version ${asked}`, { supported: [...SUPPORTED], requested: asked });
|
|
27
|
+
}
|
|
28
|
+
const capabilities = { tools: { listChanged: false } };
|
|
29
|
+
switch (req.method) {
|
|
30
|
+
case 'server/discover':
|
|
31
|
+
return ok(id, { supportedVersions: [...SUPPORTED], capabilities, ttlMs: 3_600_000, cacheScope: 'public' }, false);
|
|
32
|
+
case 'initialize': { // 2025 clients
|
|
33
|
+
const want = String(req.params?.protocolVersion ?? '2025-11-25');
|
|
34
|
+
const version = SUPPORTED.includes(want) && want !== LATEST ? want : '2025-11-25';
|
|
35
|
+
return ok(id, { protocolVersion: version, capabilities, serverInfo: SERVER_INFO, instructions: 'sheetpane tools: describe its API, inspect a workbook, draft or check a rules file, explain an SP code, find the right skill. Read-only.' }, true);
|
|
36
|
+
}
|
|
37
|
+
case 'ping':
|
|
38
|
+
return ok(id, {}, legacy);
|
|
39
|
+
case 'tools/list':
|
|
40
|
+
return ok(id, { tools: TOOLS.map(({ name, title, description, inputSchema }) => ({ name, title, description, inputSchema })), ...(legacy ? {} : { ttlMs: 3_600_000, cacheScope: 'public' }) }, legacy);
|
|
41
|
+
case 'tools/call': {
|
|
42
|
+
const p = req.params;
|
|
43
|
+
if (typeof p?.name !== 'string' || !TOOLS.some((t) => t.name === p.name))
|
|
44
|
+
return fail(id, -32602, `Unknown tool: ${String(p?.name)}`);
|
|
45
|
+
const r = await callTool(p.name, p.arguments ?? {});
|
|
46
|
+
return ok(id, r, legacy);
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
return fail(id, -32601, `Method not found: ${req.method}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,0FAA0F;AAC1F,kGAAkG;AAClG,kGAAkG;AAClG,+FAA+F;AAC/F,wFAAwF;AACxF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,CAAC,MAAM,MAAM,GAAG,YAAY,CAAC;AACnC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,CAAU,CAAC;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC3F,MAAM,4BAA4B,GAAG,CAAC,KAAK,CAAC;AAM5C,MAAM,EAAE,GAAG,CAAC,EAAkB,EAAE,MAA+B,EAAE,MAAe,EAAY,EAAE,CAC5F,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,oCAAoC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;AAC1J,MAAM,IAAI,GAAG,CAAC,EAAkB,EAAE,IAAY,EAAE,OAAe,EAAE,IAAc,EAAY,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAE1L,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,GAAY;IACvC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAK,GAAe,CAAC,OAAO,KAAK,KAAK,IAAI,OAAQ,GAAe,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IACvK,MAAM,GAAG,GAAG,GAAc,CAAC;IAC3B,IAAI,GAAG,CAAC,EAAE,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC,CAAC,iEAAiE;IAC7G,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC;IAClB,4EAA4E;IAC5E,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,yCAAyC,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAE,SAA+B,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACnF,OAAO,IAAI,CAAC,EAAE,EAAE,4BAA4B,EAAE,gCAAgC,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1I,CAAC;IACD,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC;IACvD,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,iBAAiB;YACpB,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;QACpH,KAAK,YAAY,EAAE,CAAC,CAAC,eAAe;YAClC,MAAM,IAAI,GAAG,MAAM,CAAE,GAAG,CAAC,MAAmD,EAAE,eAAe,IAAI,YAAY,CAAC,CAAC;YAC/G,MAAM,OAAO,GAAI,SAA+B,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC;YACzG,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,0IAA0I,EAAE,EAAE,IAAI,CAAC,CAAC;QACrP,CAAC;QACD,KAAK,MAAM;YACT,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QAC5B,KAAK,YAAY;YACf,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACzM,KAAK,YAAY,EAAE,CAAC;YAClB,MAAM,CAAC,GAAG,GAAG,CAAC,MAA6E,CAAC;YAC5F,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,iBAAiB,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACtI,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YACpD,OAAO,EAAE,CAAC,EAAE,EAAE,CAAoC,EAAE,MAAM,CAAC,CAAC;QAC9D,CAAC;QACD;YACE,OAAO,IAAI,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,qBAAqB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ImportedSheet, type ImportedWorkbook } from '@sheetpane/import';
|
|
2
|
+
/** A tool as `tools/list` shows it, plus the function that answers it. */
|
|
3
|
+
export interface Tool {
|
|
4
|
+
name: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: Record<string, unknown>;
|
|
8
|
+
run: (args: Record<string, unknown>) => Promise<string>;
|
|
9
|
+
}
|
|
10
|
+
/** What `tools/call` returns: text content, and `isError` for a tool error. */
|
|
11
|
+
export interface ToolResult {
|
|
12
|
+
content: {
|
|
13
|
+
type: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
isError?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Reads a workbook from a path the agent gives — `.xlsx` or `.csv`, nothing else (SR-LAW-3). */
|
|
19
|
+
export declare function loadWorkbook(path: string): Promise<ImportedWorkbook>;
|
|
20
|
+
/** The header row guess: the first of the top ten rows whose filled cells are all text, two or more of them, with a row below it. */
|
|
21
|
+
export declare function guessHeaderRow(s: ImportedSheet): number;
|
|
22
|
+
/** Sheets, sizes, header-row guess, titles, column types, merges and the plugins it would use. */
|
|
23
|
+
export declare function inspectWorkbook(path: string): Promise<Record<string, unknown>>;
|
|
24
|
+
/** A rules file drafted from a sheet's headers and inferred types — always a draft to review. */
|
|
25
|
+
export declare function draftRules(path: string, sheet?: string): Promise<Record<string, unknown>>;
|
|
26
|
+
/** A rules file checked the way the grid checks it, and against a workbook when one is given. */
|
|
27
|
+
export declare function checkRules(json: unknown, path?: string): Promise<Record<string, unknown>>;
|
|
28
|
+
/** The docs entry for an SP code: title, severity, meaning, likely cause, fix and link. */
|
|
29
|
+
export declare function explainCode(code: string): Record<string, unknown>;
|
|
30
|
+
/** The skills whose names and descriptions share the most words with the task — no model. */
|
|
31
|
+
export declare function findSkill(task: string): Record<string, unknown>;
|
|
32
|
+
/** The tools, in the order `tools/list` gives them. */
|
|
33
|
+
export declare const TOOLS: Tool[];
|
|
34
|
+
/** Runs one tool. A ToolError or a failed read is the tool's error, never the server's. */
|
|
35
|
+
export declare function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
36
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AASA,OAAO,EAAyB,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAOrG,0EAA0E;AAC1E,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACzD;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IAAG,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;AAmB5F,iGAAiG;AACjG,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAY1E;AAyBD,qIAAqI;AACrI,wBAAgB,cAAc,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAQvD;AA2CD,kGAAkG;AAClG,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA8BpF;AAID,iGAAiG;AACjG,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA6B/F;AAED,iGAAiG;AACjG,wBAAsB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoC/F;AAYD,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBjE;AAoBD,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgB/D;AAMD,uDAAuD;AACvD,eAAO,MAAM,KAAK,EAAE,IAAI,EA8CvB,CAAC;AAEF,2FAA2F;AAC3F,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAO/F"}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// The tools (W-39). Each is deterministic — the same input gives the same output — and computed
|
|
2
|
+
// from what ships: the import package, the grid's rules checker, docs/ERRORS.md, the skills and
|
|
3
|
+
// the custom-elements manifest. Read-only: paths come from the agent, nothing is written, no
|
|
4
|
+
// network. A bad input is a tool error (`isError: true`), never a crash.
|
|
5
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
6
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { dirname, join, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { read, BUILTIN_FORMATS } from '@sheetpane/import';
|
|
11
|
+
import { checkDocument, describeRule, CODES, codeUrl, spMessage } from '@sheetpane/grid';
|
|
12
|
+
import { describeApi } from './api.js';
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
/** A tool error: its coded message goes back to the agent, and the server keeps running. */
|
|
16
|
+
class ToolError extends Error {
|
|
17
|
+
spCode;
|
|
18
|
+
constructor(spCode, detail) {
|
|
19
|
+
super(spMessage(spCode, detail));
|
|
20
|
+
this.spCode = spCode;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const SCHEMA = 'https://json-schema.org/draft/2020-12/schema';
|
|
24
|
+
const MAX_BYTES = 256 * 1024 * 1024;
|
|
25
|
+
const str = (args, k, required = true) => {
|
|
26
|
+
const v = args[k];
|
|
27
|
+
if (v === undefined && !required)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (typeof v !== 'string' || !v.trim())
|
|
30
|
+
throw new ToolError('SP-7001', `“${k}” must be a non-empty string`);
|
|
31
|
+
return v;
|
|
32
|
+
};
|
|
33
|
+
// ── workbooks ──────────────────────────────────────────────────────────────
|
|
34
|
+
/** Reads a workbook from a path the agent gives — `.xlsx` or `.csv`, nothing else (SR-LAW-3). */
|
|
35
|
+
export async function loadWorkbook(path) {
|
|
36
|
+
const abs = resolve(path);
|
|
37
|
+
let size;
|
|
38
|
+
try {
|
|
39
|
+
size = (await stat(abs)).size;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new ToolError('SP-7002', `No file at ${abs}`);
|
|
43
|
+
}
|
|
44
|
+
if (size > MAX_BYTES)
|
|
45
|
+
throw new ToolError('SP-7003', `${abs} is ${size} bytes — past the 256 MB this tool reads`);
|
|
46
|
+
const bytes = new Uint8Array(await readFile(abs));
|
|
47
|
+
try {
|
|
48
|
+
return await read(bytes, { name: abs.split(/[\\/]/).pop() });
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
// the reader's refusal keeps its own code (SP-10xx); anything else is this tool's SP-7004
|
|
52
|
+
const err = e;
|
|
53
|
+
if (err.spCode)
|
|
54
|
+
throw new ToolError(err.spCode, err.spDetail ?? err.message);
|
|
55
|
+
throw new ToolError('SP-7004', `${abs}: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const DATE_CODE = /[dmyhs]/i;
|
|
59
|
+
function isDateCode(code) {
|
|
60
|
+
if (!code || code === 'General')
|
|
61
|
+
return false;
|
|
62
|
+
const bare = code.replace(/"[^"]*"/g, '').replace(/\\./g, '').replace(/\[[^\]]*\]/g, '').replace(/_./g, '').replace(/\*./g, '');
|
|
63
|
+
return DATE_CODE.test(bare);
|
|
64
|
+
}
|
|
65
|
+
const a1 = (r, c) => { let s = '', n = c + 1; while (n) {
|
|
66
|
+
const m = (n - 1) % 26;
|
|
67
|
+
s = String.fromCharCode(65 + m) + s;
|
|
68
|
+
n = (n - 1 - m) / 26;
|
|
69
|
+
} return `${s}${r + 1}`; };
|
|
70
|
+
function kindOf(wb, s, r, c) {
|
|
71
|
+
const v = s.rows[r]?.[c];
|
|
72
|
+
if (v === null || v === undefined || v === '')
|
|
73
|
+
return 'empty';
|
|
74
|
+
if (typeof v === 'boolean')
|
|
75
|
+
return 'boolean';
|
|
76
|
+
if (typeof v === 'string')
|
|
77
|
+
return 'text';
|
|
78
|
+
const xf = s.styleIndex[a1(r, c)];
|
|
79
|
+
if (xf !== undefined) {
|
|
80
|
+
const id = wb.styles.cellXfs[xf]?.numFmtId ?? 0;
|
|
81
|
+
if (isDateCode(wb.styles.numFmts[id] ?? BUILTIN_FORMATS[id]))
|
|
82
|
+
return 'date';
|
|
83
|
+
}
|
|
84
|
+
return Number.isInteger(v) ? 'integer' : 'number';
|
|
85
|
+
}
|
|
86
|
+
/** The header row guess: the first of the top ten rows whose filled cells are all text, two or more of them, with a row below it. */
|
|
87
|
+
export function guessHeaderRow(s) {
|
|
88
|
+
const top = Math.min(10, s.rows.length - 1);
|
|
89
|
+
for (let r = 0; r < top; r++) {
|
|
90
|
+
const row = s.rows[r] ?? [];
|
|
91
|
+
const filled = row.filter((v) => v !== null && v !== undefined && v !== '');
|
|
92
|
+
if (filled.length >= 2 && filled.every((v) => typeof v === 'string'))
|
|
93
|
+
return r + 1;
|
|
94
|
+
}
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
/** One column below the header row: its title, inferred type, blanks, and up to 12 distinct values. */
|
|
98
|
+
function columnInfo(wb, s, headerRow, c) {
|
|
99
|
+
const title = (s.text[headerRow - 1]?.[c] ?? String(s.rows[headerRow - 1]?.[c] ?? '')).trim();
|
|
100
|
+
const kinds = new Set();
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
let blanks = 0;
|
|
103
|
+
for (let r = headerRow; r < s.rows.length; r++) {
|
|
104
|
+
const k = kindOf(wb, s, r, c);
|
|
105
|
+
if (k === 'empty') {
|
|
106
|
+
blanks++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
kinds.add(k);
|
|
110
|
+
if (seen.size <= 12)
|
|
111
|
+
seen.add(s.text[r]?.[c] ?? String(s.rows[r]?.[c]));
|
|
112
|
+
}
|
|
113
|
+
if (kinds.has('integer') && kinds.has('number'))
|
|
114
|
+
kinds.delete('integer');
|
|
115
|
+
const type = kinds.size === 0 ? 'empty' : kinds.size === 1 ? [...kinds][0] : 'mixed';
|
|
116
|
+
return { column: a1(0, c).replace(/\d+$/, ''), title, type, blanks, distinct: seen.size > 12 ? 13 : seen.size, ...(seen.size <= 12 ? { values: [...seen].sort() } : {}) };
|
|
117
|
+
}
|
|
118
|
+
/** The plugins a workbook would use: the feature is in the file, so the plugin shows it. */
|
|
119
|
+
function pluginsFor(wb) {
|
|
120
|
+
const out = new Set();
|
|
121
|
+
const ids = new Set();
|
|
122
|
+
for (const s of wb.sheets)
|
|
123
|
+
for (const xf of Object.values(s.styleIndex))
|
|
124
|
+
ids.add(xf);
|
|
125
|
+
for (const xf of ids) {
|
|
126
|
+
const x = wb.styles.cellXfs[xf];
|
|
127
|
+
if (!x)
|
|
128
|
+
continue;
|
|
129
|
+
if (x.numFmtId !== 0)
|
|
130
|
+
out.add('numfmt');
|
|
131
|
+
if (x.fontId !== 0 || x.fillId !== 0 || x.borderId !== 0 || x.alignment)
|
|
132
|
+
out.add('styles');
|
|
133
|
+
}
|
|
134
|
+
for (const s of wb.sheets) {
|
|
135
|
+
if (Object.keys(s.formulas).length)
|
|
136
|
+
out.add('formulas');
|
|
137
|
+
if (s.dataValidations.length)
|
|
138
|
+
out.add('validation');
|
|
139
|
+
if (s.conditionalFormats.length)
|
|
140
|
+
out.add('condfmt');
|
|
141
|
+
if (s.drawings.some((d) => d.kind === 'picture'))
|
|
142
|
+
out.add('images');
|
|
143
|
+
if (s.drawings.some((d) => d.kind === 'chart'))
|
|
144
|
+
out.add('charts');
|
|
145
|
+
if (s.notes.length || s.threadedComments.length)
|
|
146
|
+
out.add('comments');
|
|
147
|
+
}
|
|
148
|
+
return [...out].sort().map((p) => `@sheetpane/${p}`);
|
|
149
|
+
}
|
|
150
|
+
/** Sheets, sizes, header-row guess, titles, column types, merges and the plugins it would use. */
|
|
151
|
+
export async function inspectWorkbook(path) {
|
|
152
|
+
const wb = await loadWorkbook(path);
|
|
153
|
+
return {
|
|
154
|
+
file: path,
|
|
155
|
+
format: wb.source,
|
|
156
|
+
...(wb.delimiter ? { delimiter: wb.delimiter } : {}),
|
|
157
|
+
activeSheet: wb.sheets[wb.activeSheet]?.name,
|
|
158
|
+
plugins: pluginsFor(wb),
|
|
159
|
+
sheets: wb.sheets.map((s) => {
|
|
160
|
+
const cols = s.rows.reduce((m, r) => Math.max(m, r.length), 0);
|
|
161
|
+
const headerRow = guessHeaderRow(s);
|
|
162
|
+
return {
|
|
163
|
+
name: s.name,
|
|
164
|
+
...(s.hidden ? { hidden: true } : {}),
|
|
165
|
+
rows: s.rows.length,
|
|
166
|
+
columns: cols,
|
|
167
|
+
...(s.rows.length && cols ? { dimension: `A1:${a1(s.rows.length - 1, cols - 1)}` } : {}),
|
|
168
|
+
headerRow,
|
|
169
|
+
columnsInfo: Array.from({ length: cols }, (_, c) => columnInfo(wb, s, headerRow, c)),
|
|
170
|
+
merges: s.merges ?? [],
|
|
171
|
+
...(s.freeze.rows || s.freeze.cols ? { freeze: s.freeze } : {}),
|
|
172
|
+
formulas: Object.keys(s.formulas).length,
|
|
173
|
+
dataValidations: s.dataValidations.length,
|
|
174
|
+
conditionalFormats: s.conditionalFormats.length,
|
|
175
|
+
pictures: s.drawings.filter((d) => d.kind === 'picture').length,
|
|
176
|
+
charts: s.drawings.filter((d) => d.kind === 'chart').length,
|
|
177
|
+
notes: s.notes.length + s.threadedComments.length,
|
|
178
|
+
};
|
|
179
|
+
}),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// ── rules files (W-33) ─────────────────────────────────────────────────────
|
|
183
|
+
/** A rules file drafted from a sheet's headers and inferred types — always a draft to review. */
|
|
184
|
+
export async function draftRules(path, sheet) {
|
|
185
|
+
const wb = await loadWorkbook(path);
|
|
186
|
+
const pick = sheet === undefined ? wb.sheets : wb.sheets.filter((s) => s.name === sheet);
|
|
187
|
+
if (!pick.length)
|
|
188
|
+
throw new ToolError('SP-7005', `No sheet is named “${sheet}” — the sheets are ${wb.sheets.map((s) => `“${s.name}”`).join(', ')}`);
|
|
189
|
+
const sheets = {};
|
|
190
|
+
const notes = [];
|
|
191
|
+
for (const s of pick) {
|
|
192
|
+
const headerRow = guessHeaderRow(s);
|
|
193
|
+
const cols = s.rows.reduce((m, r) => Math.max(m, r.length), 0);
|
|
194
|
+
const columns = {};
|
|
195
|
+
for (let c = 0; c < cols; c++) {
|
|
196
|
+
const info = columnInfo(wb, s, headerRow, c);
|
|
197
|
+
if (!info.title) {
|
|
198
|
+
if (info.type !== 'empty')
|
|
199
|
+
notes.push(`“${s.name}” column ${info.column} has no title in row ${headerRow} — no rule drafted`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (columns[info.title]) {
|
|
203
|
+
notes.push(`“${s.name}” has two columns titled “${info.title}” — only the first gets a rule`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const r = {};
|
|
207
|
+
if (info.blanks === 0 && info.type !== 'empty')
|
|
208
|
+
r.required = true;
|
|
209
|
+
if (info.type !== 'empty' && info.type !== 'mixed')
|
|
210
|
+
r.type = info.type;
|
|
211
|
+
if (info.type === 'text' && info.values && info.values.length >= 2 && info.values.length <= 12 && s.rows.length - headerRow > info.values.length)
|
|
212
|
+
r.oneOf = info.values;
|
|
213
|
+
if (info.type === 'mixed')
|
|
214
|
+
notes.push(`“${s.name}” column “${info.title}” mixes types — no type drafted`);
|
|
215
|
+
columns[info.title] = r;
|
|
216
|
+
}
|
|
217
|
+
sheets[s.name] = { ...(headerRow !== 1 ? { headerRow } : {}), columns };
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
draft: true,
|
|
221
|
+
note: 'A draft from the file as it is — review every rule before using it: a column with no blanks today may allow them, and oneOf lists only the values seen.',
|
|
222
|
+
rules: { $schema: 'https://unpkg.com/@sheetpane/grid/rules.schema.json', version: 1, sheets },
|
|
223
|
+
...(notes.length ? { notes } : {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** A rules file checked the way the grid checks it, and against a workbook when one is given. */
|
|
227
|
+
export async function checkRules(json, path) {
|
|
228
|
+
let doc = json;
|
|
229
|
+
if (typeof json === 'string') {
|
|
230
|
+
try {
|
|
231
|
+
doc = JSON.parse(json);
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
return { ok: false, problems: [{ code: 'SP-6011', message: `The rules file is not JSON — ${e.message}` }] };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const { sheets, problems } = checkDocument(doc);
|
|
238
|
+
const out = problems.map((p) => ({ ...p }));
|
|
239
|
+
const rules = [];
|
|
240
|
+
for (const [name, d] of sheets) {
|
|
241
|
+
for (const [title, r] of Object.entries(d.columns))
|
|
242
|
+
rules.push({ sheet: name, target: `column “${title}”`, rule: describeRule(r) });
|
|
243
|
+
for (const [ref, r] of Object.entries(d.cells))
|
|
244
|
+
rules.push({ sheet: name, target: `cell ${ref}`, rule: describeRule(r) });
|
|
245
|
+
}
|
|
246
|
+
if (path !== undefined) {
|
|
247
|
+
const wb = await loadWorkbook(path);
|
|
248
|
+
const star = sheets.get('*');
|
|
249
|
+
const headerRowOf = (name) => sheets.get(name)?.headerRow ?? star?.headerRow ?? 1;
|
|
250
|
+
const found = new Map();
|
|
251
|
+
for (const s of wb.sheets) {
|
|
252
|
+
const hr = headerRowOf(s.name), seen = new Set();
|
|
253
|
+
for (const [c, t] of (s.text[hr - 1] ?? []).entries()) {
|
|
254
|
+
const k = t.trim().toLowerCase();
|
|
255
|
+
if (!k)
|
|
256
|
+
continue;
|
|
257
|
+
if (seen.has(k)) {
|
|
258
|
+
out.push({ code: 'SP-6024', sheet: s.name, found: t.trim(), message: `Two columns are titled “${t.trim()}” in “${s.name}” — the rules check column ${a1(0, c).replace(/\d+$/, '')}'s first match only` });
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
seen.add(k);
|
|
262
|
+
if (!found.has(k))
|
|
263
|
+
found.set(k, new Set());
|
|
264
|
+
found.get(k).add(s.name);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const name of sheets.keys())
|
|
268
|
+
if (name !== '*' && !wb.sheets.some((s) => s.name === name))
|
|
269
|
+
out.push({ code: 'SP-6022', sheet: name, found: name, message: `No sheet is named “${name}” — its rules are not checked` });
|
|
270
|
+
for (const title of Object.keys(star?.columns ?? {}))
|
|
271
|
+
if (!found.has(title.trim().toLowerCase()))
|
|
272
|
+
out.push({ code: 'SP-6023', sheet: '*', found: title, message: `No column is titled “${title}” in any sheet — its rule is not checked` });
|
|
273
|
+
for (const [name, d] of sheets) {
|
|
274
|
+
if (name === '*' || !wb.sheets.some((s) => s.name === name))
|
|
275
|
+
continue;
|
|
276
|
+
for (const title of Object.keys(d.columns))
|
|
277
|
+
if (!found.get(title.trim().toLowerCase())?.has(name))
|
|
278
|
+
out.push({ code: 'SP-6023', sheet: name, found: title, message: `No column is titled “${title}” in row ${headerRowOf(name)} of “${name}” — its rule is not checked` });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { ok: out.length === 0, problems: out.map((p) => ({ ...p, url: codeUrl(p.code) })), rules };
|
|
282
|
+
}
|
|
283
|
+
// ── error codes (W-40) ─────────────────────────────────────────────────────
|
|
284
|
+
let errorsDoc;
|
|
285
|
+
/** docs/ERRORS.md as shipped in this package's dist (copied at build), else the repo's. */
|
|
286
|
+
function errorsText() {
|
|
287
|
+
if (errorsDoc !== undefined)
|
|
288
|
+
return errorsDoc;
|
|
289
|
+
for (const p of [join(here, 'ERRORS.md'), join(here, '../../../docs/ERRORS.md')])
|
|
290
|
+
if (existsSync(p))
|
|
291
|
+
return (errorsDoc = readFileSync(p, 'utf8'));
|
|
292
|
+
throw new ToolError('SP-7007', 'docs/ERRORS.md is missing from this install');
|
|
293
|
+
}
|
|
294
|
+
/** The docs entry for an SP code: title, severity, meaning, likely cause, fix and link. */
|
|
295
|
+
export function explainCode(code) {
|
|
296
|
+
const c = code.trim().toUpperCase().replace(/^SP(\d)/, 'SP-$1');
|
|
297
|
+
if (!(c in CODES)) {
|
|
298
|
+
const near = Object.keys(CODES).filter((k) => k.slice(0, 5) === c.slice(0, 5)).slice(0, 8);
|
|
299
|
+
throw new ToolError('SP-7006', `${code} is not a sheetpane code. Codes look like SP-1001${near.length ? `; nearby: ${near.join(', ')}` : ''}`);
|
|
300
|
+
}
|
|
301
|
+
const info = CODES[c];
|
|
302
|
+
const text = errorsText();
|
|
303
|
+
const start = text.indexOf(`\n### ${c}\n`);
|
|
304
|
+
const body = start < 0 ? '' : text.slice(start + 1, text.indexOf('\n### ', start + 5) < 0 ? text.indexOf('\n---', start) : Math.min(...[text.indexOf('\n### ', start + 5), text.indexOf('\n---', start)].filter((i) => i > 0)));
|
|
305
|
+
const field = (label) => new RegExp(String.raw `- \*\*${label}:\*\* (.*)`).exec(body)?.[1]?.trim();
|
|
306
|
+
return {
|
|
307
|
+
code: c,
|
|
308
|
+
title: info.title,
|
|
309
|
+
severity: info.severity,
|
|
310
|
+
...(info.retired ? { retired: true } : {}),
|
|
311
|
+
meaning: field('Meaning'),
|
|
312
|
+
cause: field('Likely cause'),
|
|
313
|
+
fix: field('Fix'),
|
|
314
|
+
url: codeUrl(c),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
let skillList;
|
|
318
|
+
function skills() {
|
|
319
|
+
if (skillList)
|
|
320
|
+
return skillList;
|
|
321
|
+
const root = join(dirname(require.resolve('@sheetpane/skills/package.json')), 'skills');
|
|
322
|
+
skillList = readdirSync(root).sort().map((name) => {
|
|
323
|
+
const text = readFileSync(join(root, name, 'SKILL.md'), 'utf8');
|
|
324
|
+
const description = /^description:\s*(.*)$/m.exec(text)?.[1]?.replace(/^["']|["']$/g, '').trim() ?? '';
|
|
325
|
+
return { name, description, path: `node_modules/@sheetpane/skills/skills/${name}/SKILL.md` };
|
|
326
|
+
});
|
|
327
|
+
return skillList;
|
|
328
|
+
}
|
|
329
|
+
const STOP = new Set(['a', 'an', 'the', 'to', 'of', 'in', 'on', 'for', 'and', 'or', 'with', 'how', 'do', 'i', 'my', 'is', 'it', 'use', 'using', 'sheetpane', 'not', 'when', 'what', 'into', 'from', 'this', 'that', 'want', 'need', 'can']);
|
|
330
|
+
const words = (s) => s.toLowerCase().split(/[^a-z0-9.]+/).filter((w) => w.length > 1 && !STOP.has(w)).map((w) => w.replace(/(ing|es|s)$/, ''));
|
|
331
|
+
/** The skills whose names and descriptions share the most words with the task — no model. */
|
|
332
|
+
export function findSkill(task) {
|
|
333
|
+
const want = new Set(words(task));
|
|
334
|
+
if (!want.size)
|
|
335
|
+
throw new ToolError('SP-7001', 'Describe the task in a few words, e.g. "validate columns from a JSON file"');
|
|
336
|
+
const scored = skills().map((s) => {
|
|
337
|
+
// a "Not for…" clause names what the skill is not for: its words do not count
|
|
338
|
+
const own = s.description.split(/\bNot\b/)[0];
|
|
339
|
+
const hay = words(`${s.name.replace(/^sheetpane-?/, '')} ${own}`);
|
|
340
|
+
const nameWords = new Set(words(s.name.replace(/^sheetpane-?/, '').replace(/-/g, ' ')));
|
|
341
|
+
let score = 0;
|
|
342
|
+
for (const w of new Set(hay))
|
|
343
|
+
if (want.has(w))
|
|
344
|
+
score += nameWords.has(w) ? 3 : 1;
|
|
345
|
+
return { ...s, score };
|
|
346
|
+
}).filter((s) => s.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
347
|
+
const best = scored.slice(0, 3);
|
|
348
|
+
return best.length
|
|
349
|
+
? { matches: best.map(({ name, description, path, score }) => ({ name, path, description, score })) }
|
|
350
|
+
: { matches: [], hint: 'No skill matched. Start with the "sheetpane" skill — it routes to the rest.', start: skills().find((s) => s.name === 'sheetpane')?.path };
|
|
351
|
+
}
|
|
352
|
+
// ── the list ───────────────────────────────────────────────────────────────
|
|
353
|
+
const pathProp = { type: 'string', description: 'Path to an .xlsx or .csv file, absolute or relative to where the server runs.' };
|
|
354
|
+
/** The tools, in the order `tools/list` gives them. */
|
|
355
|
+
export const TOOLS = [
|
|
356
|
+
{
|
|
357
|
+
name: 'describe_api',
|
|
358
|
+
title: 'Describe sheetpane API',
|
|
359
|
+
description: 'Look up one name of sheetpane\'s public API — an element (sheetpane-excel), attribute, property, method, event (sheetpane-change), CSS custom property (--sp-*), slot, CSS part or plugin — and get its entry from the custom-elements manifest, its TSDoc and the record that specifies it. Use this before writing an attribute or event name instead of guessing.',
|
|
360
|
+
inputSchema: { $schema: SCHEMA, type: 'object', properties: { name: { type: 'string', description: 'The name to look up, e.g. "flagging", "sheetpane-export", "--sp-accent", "exportFile", "@sheetpane/charts".' } }, required: ['name'], additionalProperties: false },
|
|
361
|
+
run: async (a) => JSON.stringify(describeApi(str(a, 'name')), null, 2),
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: 'inspect_workbook',
|
|
365
|
+
title: 'Inspect a workbook',
|
|
366
|
+
description: 'Read an .xlsx or .csv file and report its sheets, sizes, header-row guess, column titles, inferred column types (text, number, integer, date, boolean, mixed), blanks, distinct values, merges, and which @sheetpane plugins the file would use. Read-only.',
|
|
367
|
+
inputSchema: { $schema: SCHEMA, type: 'object', properties: { path: pathProp }, required: ['path'], additionalProperties: false },
|
|
368
|
+
run: async (a) => JSON.stringify(await inspectWorkbook(str(a, 'path')), null, 2),
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: 'draft_rules',
|
|
372
|
+
title: 'Draft a rules file',
|
|
373
|
+
description: 'Draft a sheetpane rules file (the JSON <sheetpane-excel rules-src> loads) from a workbook: one rule per titled column — required where the column has no blanks, its inferred type, and oneOf for a text column with 2–12 distinct values. Always a draft for a person to review.',
|
|
374
|
+
inputSchema: { $schema: SCHEMA, type: 'object', properties: { path: pathProp, sheet: { type: 'string', description: 'Only this sheet; every sheet when omitted.' } }, required: ['path'], additionalProperties: false },
|
|
375
|
+
run: async (a) => JSON.stringify(await draftRules(str(a, 'path'), str(a, 'sheet', false)), null, 2),
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'check_rules',
|
|
379
|
+
title: 'Check a rules file',
|
|
380
|
+
description: 'Check a sheetpane rules file exactly as the grid does (unknown keys and rule words, bad types, patterns, ranges, version), and list each rule in words. With a workbook path, also list the sheets and column titles the rules name that the workbook lacks.',
|
|
381
|
+
inputSchema: { $schema: SCHEMA, type: 'object', properties: { rules: { description: 'The rules file: its JSON text, or the parsed object.', type: ['string', 'object'] }, path: pathProp }, required: ['rules'], additionalProperties: false },
|
|
382
|
+
run: async (a) => {
|
|
383
|
+
if (a.rules === undefined)
|
|
384
|
+
throw new ToolError('SP-7001', '“rules” is required: the rules file\'s JSON text or object');
|
|
385
|
+
return JSON.stringify(await checkRules(a.rules, str(a, 'path', false)), null, 2);
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: 'explain_code',
|
|
390
|
+
title: 'Explain an SP code',
|
|
391
|
+
description: 'Explain a sheetpane error code (SP-1001 … SP-6025) from its docs entry: title, severity, meaning, likely cause, fix and the link to the docs.',
|
|
392
|
+
inputSchema: { $schema: SCHEMA, type: 'object', properties: { code: { type: 'string', description: 'The code, e.g. "SP-1002".', pattern: '^[Ss][Pp]-?\\d{4}$' } }, required: ['code'], additionalProperties: false },
|
|
393
|
+
run: async (a) => JSON.stringify(explainCode(str(a, 'code')), null, 2),
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: 'find_skill',
|
|
397
|
+
title: 'Find the sheetpane skill',
|
|
398
|
+
description: 'Name the sheetpane agent skill for a task (embedding, React, column rules, a rules file, theming, testing, plugins…) by matching its words against the skills\' names and descriptions. Returns up to three, best first, with the SKILL.md path to read.',
|
|
399
|
+
inputSchema: { $schema: SCHEMA, type: 'object', properties: { task: { type: 'string', description: 'The task in a few words.' } }, required: ['task'], additionalProperties: false },
|
|
400
|
+
run: async (a) => JSON.stringify(findSkill(str(a, 'task')), null, 2),
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
/** Runs one tool. A ToolError or a failed read is the tool's error, never the server's. */
|
|
404
|
+
export async function callTool(name, args) {
|
|
405
|
+
const tool = TOOLS.find((t) => t.name === name);
|
|
406
|
+
try {
|
|
407
|
+
return { content: [{ type: 'text', text: await tool.run(args ?? {}) }] };
|
|
408
|
+
}
|
|
409
|
+
catch (e) {
|
|
410
|
+
return { content: [{ type: 'text', text: e instanceof ToolError ? e.message : `${name} failed: ${e.message}` }], isError: true };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=tools.js.map
|