scanruler-mcp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -17
- package/package.json +1 -1
- package/src/commands.json +347 -312
- package/src/main.js +14 -8
- package/src/mcp.js +272 -47
- package/src/tools.js +28 -9
- package/src/trace.js +12 -1
package/src/tools.js
CHANGED
|
@@ -90,14 +90,14 @@ export const agentJson = (value, exact = false) =>
|
|
|
90
90
|
* false answers with the job at once. */
|
|
91
91
|
export const waitIn = {
|
|
92
92
|
type: 'boolean',
|
|
93
|
-
description: 'false: answer
|
|
93
|
+
description: 'false: answer { queued: true, job } at once and run on; scanruler_wait { for: "job", job } gives the answer. true, the default: answer when done — or queued all the same past the server’s answer limit.',
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/** The `timings` a tool that changes the session takes: the server keeps
|
|
97
97
|
* what the page's answer carries of the build's steps (mcp.js). */
|
|
98
98
|
export const timingsIn = {
|
|
99
99
|
type: 'boolean',
|
|
100
|
-
description: 'true: the answer adds ms
|
|
100
|
+
description: 'true: the answer adds ms and, where the change built, steps.played — each step with its ms.',
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export function toolFor(command, { outDir, cwd }) {
|
|
@@ -297,11 +297,28 @@ const KINDS = {
|
|
|
297
297
|
drawing: ['.dxf', '.svg', '.pdf'],
|
|
298
298
|
text: ['.md', '.txt', '.csv', '.json', '.jsonl'],
|
|
299
299
|
}
|
|
300
|
+
/** The extensions of the files scanruler_read reads. */
|
|
301
|
+
export const TEXT_FILES = KINDS.text
|
|
300
302
|
export const fileKind = (name) => {
|
|
301
303
|
const ext = extname(name).toLowerCase()
|
|
302
304
|
return Object.keys(KINDS).find((kind) => KINDS[kind].includes(ext)) ?? 'other'
|
|
303
305
|
}
|
|
304
306
|
|
|
307
|
+
/** How many bytes the UTF-8 character that starts with `byte` takes. */
|
|
308
|
+
const utf8Length = (byte) => (byte < 0x80 ? 1 : byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : 2)
|
|
309
|
+
|
|
310
|
+
/** The start of UTF-8 `bytes` up to `most` of them, ending on a character's
|
|
311
|
+
* end — the first character at least, when they hold it whole. */
|
|
312
|
+
export function wholeUtf8(bytes, most = bytes.length) {
|
|
313
|
+
const cut = bytes.subarray(0, Math.max(most, utf8Length(bytes[0])))
|
|
314
|
+
for (let back = 1; back <= Math.min(4, cut.length); back++) {
|
|
315
|
+
const byte = cut[cut.length - back]
|
|
316
|
+
if ((byte & 0xc0) === 0x80) continue
|
|
317
|
+
return utf8Length(byte) > back ? cut.subarray(0, cut.length - back) : cut
|
|
318
|
+
}
|
|
319
|
+
return cut
|
|
320
|
+
}
|
|
321
|
+
|
|
305
322
|
/** A picture a command handed back, as MCP content: the image, and the rest
|
|
306
323
|
* of the result as text — where the picture was written, if the call gave
|
|
307
324
|
* a path, in place of its bytes; written and not shown with `inline:
|
|
@@ -342,16 +359,18 @@ export async function writeNew(folder, name, data) {
|
|
|
342
359
|
}
|
|
343
360
|
|
|
344
361
|
/** A picture a batched call handed back, which a batch cannot show:
|
|
345
|
-
* written where the call asked, or into `folder` — the
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* where, in place of the
|
|
362
|
+
* written where the call asked, or into `folder` — the server's work
|
|
363
|
+
* folder in the one the scan came from, which a sandboxed agent can open,
|
|
364
|
+
* made when it is not there — else, or when that folder takes no file,
|
|
365
|
+
* into `outDir`; as saveFileResult writes any file, numbered past one
|
|
366
|
+
* already there, so a batch of renders, or a render from an earlier
|
|
367
|
+
* batch, is not written over. The result says where, in place of the
|
|
368
|
+
* bytes — written into `folder`, that folder too (file.folder). */
|
|
351
369
|
export async function batchedPicture(result, args, { cwd, outDir, folder }) {
|
|
352
370
|
if (args?.path || !folder) return saveFileResult(result, args, { cwd, outDir })
|
|
353
371
|
try {
|
|
354
|
-
|
|
372
|
+
const saved = await saveFileResult(result, args, { cwd, outDir: folder })
|
|
373
|
+
return saved?.file?.path ? { ...saved, file: { ...saved.file, folder } } : saved
|
|
355
374
|
} catch {
|
|
356
375
|
return saveFileResult(result, args, { cwd, outDir })
|
|
357
376
|
}
|
package/src/trace.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// own, for measuring a run afterwards — how long each call took, how much
|
|
4
4
|
// went each way, what was refused, and whether the tab was hidden. Off
|
|
5
5
|
// unless asked for (--trace <dir>, SCANRULER_MCP_TRACE); local only; an
|
|
6
|
-
// argument's contents
|
|
6
|
+
// argument's contents, and the head of each answer, are written only with
|
|
7
|
+
// --trace-args, their sizes always.
|
|
7
8
|
// scripts/trace-summary.mjs reads the files.
|
|
8
9
|
|
|
9
10
|
import { appendFileSync, mkdirSync } from 'node:fs'
|
|
@@ -29,6 +30,16 @@ export function replyMeasure(reply) {
|
|
|
29
30
|
return { out, error, ...(images ? { images } : {}) }
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/** How much of an answer the trace keeps, characters. */
|
|
34
|
+
const HEAD_CHARS = 500
|
|
35
|
+
|
|
36
|
+
/** The start of a reply's text — a refusal's message, most often whole — for
|
|
37
|
+
* reading a run afterwards. */
|
|
38
|
+
export function replyHead(reply) {
|
|
39
|
+
const text = reply?.content?.find((c) => c.type === 'text')?.text
|
|
40
|
+
return typeof text === 'string' && text.length > 0 ? text.slice(0, HEAD_CHARS) : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
/** A trace into a new file in `dir`, or null when `dir` is empty. Each
|
|
33
44
|
* line is a JSON object with `ev`: "start" (the server), "list" (the tool
|
|
34
45
|
* list handed out), "call" (a tool call answered), "entry" (one call of a
|