@cryer/star-cli 0.3.9 → 0.3.11
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 +11 -2
- package/README.zh-CN.md +11 -2
- package/dist/{chunk-RE3NOULF.js → chunk-63CGUF3F.js} +333 -40
- package/dist/chunk-63CGUF3F.js.map +1 -0
- package/dist/{chunk-VJFWTXWN.js → chunk-GYXF6NPC.js} +32 -7
- package/dist/chunk-GYXF6NPC.js.map +1 -0
- package/dist/{chunk-A5PXPWHB.js → chunk-LSYGHLRU.js} +13 -1
- package/dist/chunk-LSYGHLRU.js.map +1 -0
- package/dist/{chunk-56ZIA57D.js → chunk-N4MIQSNZ.js} +17 -6
- package/dist/chunk-N4MIQSNZ.js.map +1 -0
- package/dist/{chunk-OIJYNQFZ.js → chunk-Q5FJN4LN.js} +305 -50
- package/dist/chunk-Q5FJN4LN.js.map +1 -0
- package/dist/{loop-PVOSF6LR.js → loop-AY4PXRVP.js} +5 -5
- package/dist/main.js +7 -8
- package/dist/main.js.map +1 -1
- package/dist/{repl-OO7C6V7M.js → repl-YBNTJOSB.js} +287 -51
- package/dist/repl-YBNTJOSB.js.map +1 -0
- package/dist/{tools-JDTACJYX.js → tools-BH55BVIS.js} +3 -3
- package/package.json +26 -16
- package/dist/chunk-56ZIA57D.js.map +0 -1
- package/dist/chunk-A5PXPWHB.js.map +0 -1
- package/dist/chunk-OIJYNQFZ.js.map +0 -1
- package/dist/chunk-RE3NOULF.js.map +0 -1
- package/dist/chunk-VJFWTXWN.js.map +0 -1
- package/dist/repl-OO7C6V7M.js.map +0 -1
- /package/dist/{loop-PVOSF6LR.js.map → loop-AY4PXRVP.js.map} +0 -0
- /package/dist/{tools-JDTACJYX.js.map → tools-BH55BVIS.js.map} +0 -0
|
@@ -18,10 +18,11 @@ import {
|
|
|
18
18
|
nextSnapshotId,
|
|
19
19
|
probeImageDimensions,
|
|
20
20
|
pushSnapshot,
|
|
21
|
+
sharedTodoStore,
|
|
21
22
|
starHome,
|
|
22
23
|
walkFiles,
|
|
23
24
|
webFetchTool
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-LSYGHLRU.js";
|
|
25
26
|
|
|
26
27
|
// src/tools/fs/edit.ts
|
|
27
28
|
import { readFile, stat, writeFile } from "fs/promises";
|
|
@@ -334,10 +335,250 @@ var grepTool = {
|
|
|
334
335
|
}
|
|
335
336
|
};
|
|
336
337
|
|
|
337
|
-
// src/tools/fs/
|
|
338
|
-
import {
|
|
338
|
+
// src/tools/fs/outline.ts
|
|
339
|
+
import { readFile as readFile3, realpath as realpath2, stat as stat3 } from "fs/promises";
|
|
339
340
|
import path4 from "path";
|
|
340
341
|
import { z as z4 } from "zod";
|
|
342
|
+
var MAX_DIR_FILES = 60;
|
|
343
|
+
var MAX_FILE_BYTES2 = 2 * 1024 * 1024;
|
|
344
|
+
var MAX_SYMBOLS_PER_FILE = 150;
|
|
345
|
+
var MAX_LINE_CHARS2 = 200;
|
|
346
|
+
var MAX_OUT_CHARS = 30 * 1024;
|
|
347
|
+
var MAX_INDENT_CHARS = 8;
|
|
348
|
+
var KEYWORD_EXCLUSION = "(?!if\\b|for\\b|while\\b|switch\\b|catch\\b|return\\b|else\\b|do\\b|new\\b|throw\\b)";
|
|
349
|
+
var TS_MEMBER_MODIFIERS = "(?:public|private|protected|static|async|readonly|get|set|override|abstract)";
|
|
350
|
+
var TS_LIKE = [
|
|
351
|
+
/^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\*?\s+[\w$]+/,
|
|
352
|
+
/^(?:export\s+)?(?:abstract\s+)?class\s+[\w$]+/,
|
|
353
|
+
/^(?:export\s+)?interface\s+[\w$]+/,
|
|
354
|
+
/^(?:export\s+)?type\s+[\w$]+\s*=/,
|
|
355
|
+
/^(?:export\s+)?(?:const\s+)?enum\s+[\w$]+/,
|
|
356
|
+
// const handler = (a, b) => / const handler = async (a) => — top level
|
|
357
|
+
// only; locals inside functions are navigation noise.
|
|
358
|
+
/^(?:export\s+)?(?:const|let|var)\s+[\w$]+\s*=\s*(?:async\s*)?(?:\([^)]*\)|[\w$]+)\s*=>/,
|
|
359
|
+
// Indented class/object members with a body: name<T>(args): ReturnType {
|
|
360
|
+
// — control flow excluded by the keyword lookahead, dotted calls
|
|
361
|
+
// (this.x()) fail the anchored name-then-paren shape.
|
|
362
|
+
new RegExp(
|
|
363
|
+
`^\\s+(?:${TS_MEMBER_MODIFIERS}\\s+)*${KEYWORD_EXCLUSION}[\\w$]+(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*(?::\\s*[^{}=;]+?)?\\s*(?:\\{\\s*)?\\}?\\s*$`
|
|
364
|
+
),
|
|
365
|
+
// Bodyless signatures (interface members, overloads): only when the
|
|
366
|
+
// parens carry type annotations or an explicit return type follows —
|
|
367
|
+
// otherwise indented calls like clearTimeout(timer); or finalize();
|
|
368
|
+
// would leak into the outline.
|
|
369
|
+
new RegExp(
|
|
370
|
+
`^\\s+(?:${TS_MEMBER_MODIFIERS}\\s+)*${KEYWORD_EXCLUSION}[\\w$]+(?:<[^>]*>)?\\s*(?:\\([^)]*:[^)]*\\)\\s*(?::\\s*[^{}=;]+?)?|\\([^)]*\\)\\s*:\\s*[^{}=;]+?)\\s*;\\s*$`
|
|
371
|
+
)
|
|
372
|
+
];
|
|
373
|
+
var LANGUAGE_PATTERNS = {
|
|
374
|
+
ts: TS_LIKE,
|
|
375
|
+
python: [/^\s*(?:async\s+)?def\s+\w+\s*\(/, /^\s*class\s+\w+/],
|
|
376
|
+
go: [/^func\s+(?:\([^)]*\)\s*)?\w+\s*\(/, /^type\s+\w+\s+(?:struct|interface)\b/],
|
|
377
|
+
rust: [
|
|
378
|
+
/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+\w+/,
|
|
379
|
+
/^\s*(?:pub\s+)?(?:struct|enum|trait|union)\s+\w+/
|
|
380
|
+
],
|
|
381
|
+
jvm: [
|
|
382
|
+
/^\s*(?:(?:public|private|protected|internal|static|final|abstract|sealed|open|suspend|inline|override|async|partial|virtual|extern)\s+)*(?:class|interface|enum|struct|record|object)\s+\w+/,
|
|
383
|
+
/^\s*(?:(?:public|private|protected|internal|open|suspend|inline|override)\s+)*fun\s+\w+\s*\(/,
|
|
384
|
+
new RegExp(
|
|
385
|
+
`^\\s+(?:(?:public|private|protected|internal|static|final|abstract|sealed|open|suspend|inline|override|async|partial|virtual)\\s+)*[\\w<>[\\],.?]+\\s+${KEYWORD_EXCLUSION}\\w+\\s*\\([^;{}]*\\)\\s*(?:\\{|;|=>|$)`
|
|
386
|
+
)
|
|
387
|
+
],
|
|
388
|
+
cpp: [
|
|
389
|
+
/^\s*(?:class|struct|enum|union|namespace)\s+\w+/,
|
|
390
|
+
new RegExp(
|
|
391
|
+
`^\\s*(?:(?:static|inline|virtual|constexpr|extern|friend|explicit)\\s+)*[\\w:<>,~*&\\s]+?\\s+${KEYWORD_EXCLUSION}[\\w:~]+\\s*\\([^;]*\\)\\s*(?:const\\s*)?(?:noexcept\\s*)?(?:\\{|;|$)`
|
|
392
|
+
)
|
|
393
|
+
],
|
|
394
|
+
ruby: [/^\s*def\s+[\w.=!?]+/, /^\s*(?:class|module)\s+[\w:]+/],
|
|
395
|
+
php: [
|
|
396
|
+
/^\s*(?:(?:public|private|protected|static|abstract|final)\s+)*function\s+\w+\s*\(/,
|
|
397
|
+
/^\s*(?:(?:abstract|final)\s+)?(?:class|interface|trait|enum)\s+\w+/
|
|
398
|
+
],
|
|
399
|
+
shell: [/^\s*(?:function\s+)?[\w.-]+\s*\(\s*\)\s*(?:\{|$)/],
|
|
400
|
+
swift: [
|
|
401
|
+
/^\s*(?:(?:public|private|internal|open|fileprivate|static|class|override|mutating|final)\s+)*func\s+\w+\s*\(/,
|
|
402
|
+
/^\s*(?:(?:public|private|internal|open|final)\s+)*(?:class|struct|enum|protocol|extension)\s+\w+/
|
|
403
|
+
],
|
|
404
|
+
scala: [
|
|
405
|
+
/^\s*(?:(?:private|protected|override|final|abstract|implicit)\s+)*def\s+\w+\s*[(\[]/,
|
|
406
|
+
/^\s*(?:(?:abstract|final|sealed)\s+)*(?:class|trait|object)\s+\w+/
|
|
407
|
+
]
|
|
408
|
+
};
|
|
409
|
+
var EXT_TO_LANGUAGE = {
|
|
410
|
+
ts: "ts",
|
|
411
|
+
tsx: "ts",
|
|
412
|
+
mts: "ts",
|
|
413
|
+
cts: "ts",
|
|
414
|
+
js: "ts",
|
|
415
|
+
jsx: "ts",
|
|
416
|
+
mjs: "ts",
|
|
417
|
+
cjs: "ts",
|
|
418
|
+
py: "python",
|
|
419
|
+
pyi: "python",
|
|
420
|
+
go: "go",
|
|
421
|
+
rs: "rust",
|
|
422
|
+
java: "jvm",
|
|
423
|
+
kt: "jvm",
|
|
424
|
+
kts: "jvm",
|
|
425
|
+
cs: "jvm",
|
|
426
|
+
c: "cpp",
|
|
427
|
+
h: "cpp",
|
|
428
|
+
cc: "cpp",
|
|
429
|
+
cpp: "cpp",
|
|
430
|
+
cxx: "cpp",
|
|
431
|
+
hpp: "cpp",
|
|
432
|
+
hh: "cpp",
|
|
433
|
+
rb: "ruby",
|
|
434
|
+
php: "php",
|
|
435
|
+
sh: "shell",
|
|
436
|
+
bash: "shell",
|
|
437
|
+
zsh: "shell",
|
|
438
|
+
swift: "swift",
|
|
439
|
+
scala: "scala",
|
|
440
|
+
sc: "scala"
|
|
441
|
+
};
|
|
442
|
+
function languageOf(filePath) {
|
|
443
|
+
const name = path4.basename(filePath);
|
|
444
|
+
if (/\.(min|bundle|generated)\.[^.]+$/.test(name)) return void 0;
|
|
445
|
+
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
446
|
+
const lang = EXT_TO_LANGUAGE[ext];
|
|
447
|
+
return lang === void 0 ? void 0 : LANGUAGE_PATTERNS[lang];
|
|
448
|
+
}
|
|
449
|
+
function extractSymbols(text, patterns) {
|
|
450
|
+
const out = [];
|
|
451
|
+
const lines = text.split("\n");
|
|
452
|
+
for (let i = 0; i < lines.length; i++) {
|
|
453
|
+
const raw = (lines[i] ?? "").replace(/\r$/, "");
|
|
454
|
+
const trimmed = raw.trim();
|
|
455
|
+
if (trimmed === "") continue;
|
|
456
|
+
if (!patterns.some((re) => re.test(raw))) continue;
|
|
457
|
+
const indent = raw.slice(0, raw.length - raw.trimStart().length).slice(0, MAX_INDENT_CHARS);
|
|
458
|
+
const shown = trimmed.length > MAX_LINE_CHARS2 ? `${trimmed.slice(0, MAX_LINE_CHARS2)}\u2026` : trimmed;
|
|
459
|
+
out.push({ line: i + 1, indent, text: shown });
|
|
460
|
+
}
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
async function outlineFile(abs) {
|
|
464
|
+
const st = await stat3(abs).catch(() => null);
|
|
465
|
+
if (!st || st.size > MAX_FILE_BYTES2) return { unreadable: true };
|
|
466
|
+
const buf = await readFile3(abs).catch(() => null);
|
|
467
|
+
if (!buf) return { unreadable: true };
|
|
468
|
+
if (buf.includes(0)) return { binary: true };
|
|
469
|
+
const patterns = languageOf(abs);
|
|
470
|
+
if (!patterns) return { unreadable: true };
|
|
471
|
+
const all = extractSymbols(buf.toString("utf8"), patterns);
|
|
472
|
+
return { symbols: all.slice(0, MAX_SYMBOLS_PER_FILE), total: all.length };
|
|
473
|
+
}
|
|
474
|
+
function formatSymbols(symbols, total) {
|
|
475
|
+
const out = symbols.map((s) => `${s.line} ${s.indent}${s.text}`);
|
|
476
|
+
if (total > symbols.length) {
|
|
477
|
+
out.push(`... (${total - symbols.length} more declarations)`);
|
|
478
|
+
}
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
var schema4 = z4.object({
|
|
482
|
+
path: z4.string().optional().describe("Source file or directory to outline (default: the working directory)"),
|
|
483
|
+
maxFiles: z4.number().int().positive().optional().describe(`Directory mode: maximum files to include (default ${MAX_DIR_FILES})`)
|
|
484
|
+
});
|
|
485
|
+
var codeOutlineTool = {
|
|
486
|
+
name: "code_outline",
|
|
487
|
+
description: "List the declaration skeleton of a source file or directory (functions, classes, methods, types) with line numbers, without reading the bodies. Cheaper than read_file for navigating unfamiliar code: outline first, then read_file the exact line ranges you need. Supports TS/JS, Python, Go, Rust, Java/Kotlin/C#, C/C++, Ruby, PHP, Shell, Swift, Scala.",
|
|
488
|
+
permission: "read",
|
|
489
|
+
parameters: schema4,
|
|
490
|
+
async execute(args, ctx) {
|
|
491
|
+
const root = path4.resolve(ctx.cwd, args.path ?? ".");
|
|
492
|
+
const st = await stat3(root).catch(() => null);
|
|
493
|
+
if (!st) {
|
|
494
|
+
return { content: `Path not found: ${args.path ?? "."}`, isError: true };
|
|
495
|
+
}
|
|
496
|
+
if (st.isFile()) {
|
|
497
|
+
const resolved = await realpath2(root).catch(() => null);
|
|
498
|
+
if (isSensitivePath(root) || resolved !== null && isSensitivePath(resolved)) {
|
|
499
|
+
return { content: `Refused to outline sensitive file: ${args.path ?? "."}`, isError: true };
|
|
500
|
+
}
|
|
501
|
+
if (languageOf(root) === void 0) {
|
|
502
|
+
return {
|
|
503
|
+
content: `No supported source language for: ${args.path ?? "."} (supported: TS/JS, Python, Go, Rust, Java/Kotlin/C#, C/C++, Ruby, PHP, Shell, Swift, Scala)`
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const result = await outlineFile(root);
|
|
507
|
+
if ("binary" in result) {
|
|
508
|
+
return { content: `Cannot outline binary file: ${args.path ?? "."}`, isError: true };
|
|
509
|
+
}
|
|
510
|
+
if ("unreadable" in result) {
|
|
511
|
+
return {
|
|
512
|
+
content: `Cannot outline ${args.path ?? "."}: unreadable or larger than 2MB`,
|
|
513
|
+
isError: true
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (result.symbols.length === 0) {
|
|
517
|
+
return { content: `(no recognizable declarations in ${args.path ?? "."})` };
|
|
518
|
+
}
|
|
519
|
+
return { content: formatSymbols(result.symbols, result.total).join("\n") };
|
|
520
|
+
}
|
|
521
|
+
const walked = await walkFiles(root, void 0, {
|
|
522
|
+
ignore: createIgnorePredicate(ctx.cwd),
|
|
523
|
+
withMtime: false,
|
|
524
|
+
signal: ctx.abortSignal,
|
|
525
|
+
symlinkBoundary: ctx.cwd,
|
|
526
|
+
nestedIgnore: true
|
|
527
|
+
});
|
|
528
|
+
const candidates = walked.files.filter(
|
|
529
|
+
(f) => languageOf(f.abs) !== void 0 && !isSensitivePath(f.abs) && (f.resolved === void 0 || !isSensitivePath(f.resolved))
|
|
530
|
+
);
|
|
531
|
+
const maxFiles = args.maxFiles ?? MAX_DIR_FILES;
|
|
532
|
+
const scan = async (file) => {
|
|
533
|
+
const result = await outlineFile(file.abs);
|
|
534
|
+
if ("symbols" in result && result.symbols.length > 0) {
|
|
535
|
+
const lines = formatSymbols(result.symbols, result.total);
|
|
536
|
+
const chars = lines.reduce((n, l) => n + l.length + 1, file.rel.length + 2);
|
|
537
|
+
return { rel: file.rel, lines, chars };
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
};
|
|
541
|
+
const out = [];
|
|
542
|
+
let size = 0;
|
|
543
|
+
let included = 0;
|
|
544
|
+
let hitLimit = false;
|
|
545
|
+
for (let i = 0; i < candidates.length && included < maxFiles; i += WALK_CONCURRENCY) {
|
|
546
|
+
if (ctx.abortSignal?.aborted) break;
|
|
547
|
+
const chunk = await Promise.all(
|
|
548
|
+
candidates.slice(i, i + WALK_CONCURRENCY).map((file) => scan(file))
|
|
549
|
+
);
|
|
550
|
+
for (const block of chunk) {
|
|
551
|
+
if (!block) continue;
|
|
552
|
+
if (size + block.chars > MAX_OUT_CHARS || included >= maxFiles) {
|
|
553
|
+
hitLimit = true;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
if (out.length > 0) out.push("");
|
|
557
|
+
out.push(`${block.rel}:`);
|
|
558
|
+
out.push(...block.lines);
|
|
559
|
+
size += block.chars;
|
|
560
|
+
included += 1;
|
|
561
|
+
}
|
|
562
|
+
if (hitLimit) break;
|
|
563
|
+
}
|
|
564
|
+
if (included === 0) {
|
|
565
|
+
return {
|
|
566
|
+
content: `No recognizable declarations under ${args.path ?? "."} (${candidates.length} source file(s) scanned)`
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (hitLimit) {
|
|
570
|
+
out.push("... (truncated: output limit reached, more files not outlined)");
|
|
571
|
+
} else if (walked.aborted || ctx.abortSignal?.aborted) {
|
|
572
|
+
out.push("... (interrupted: partial results)");
|
|
573
|
+
}
|
|
574
|
+
return { content: out.join("\n") };
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// src/tools/fs/read.ts
|
|
579
|
+
import { open, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
580
|
+
import path5 from "path";
|
|
581
|
+
import { z as z5 } from "zod";
|
|
341
582
|
var DEFAULT_LIMIT = 2e3;
|
|
342
583
|
var MAX_CHARS = 100 * 1024;
|
|
343
584
|
var MAX_READ_BYTES = 1024 * 1024;
|
|
@@ -363,10 +604,10 @@ function decodeTextBuffer(buf) {
|
|
|
363
604
|
}
|
|
364
605
|
return { text: buf.toString("utf8") };
|
|
365
606
|
}
|
|
366
|
-
var
|
|
367
|
-
path:
|
|
368
|
-
offset:
|
|
369
|
-
limit:
|
|
607
|
+
var schema5 = z5.object({
|
|
608
|
+
path: z5.string().describe("File path, absolute or relative to the working directory"),
|
|
609
|
+
offset: z5.number().int().positive().optional().describe("1-based line number to start reading from"),
|
|
610
|
+
limit: z5.number().int().positive().optional().describe("Maximum number of lines to read (default 2000)")
|
|
370
611
|
});
|
|
371
612
|
var READ_CACHE_MAX_ENTRIES = 200;
|
|
372
613
|
function createReadFileTool() {
|
|
@@ -375,15 +616,15 @@ function createReadFileTool() {
|
|
|
375
616
|
name: "read_file",
|
|
376
617
|
description: "Read a text file and return its contents with line numbers. Output is truncated to 2000 lines or 100KB by default. Re-reading an unchanged file range returns a short note instead of the content.",
|
|
377
618
|
permission: "read",
|
|
378
|
-
parameters:
|
|
619
|
+
parameters: schema5,
|
|
379
620
|
reset: () => cache.clear(),
|
|
380
621
|
async execute(args, ctx) {
|
|
381
|
-
const filePath =
|
|
382
|
-
const resolvedPath = await
|
|
622
|
+
const filePath = path5.resolve(ctx.cwd, args.path);
|
|
623
|
+
const resolvedPath = await realpath3(filePath).catch(() => null);
|
|
383
624
|
if (isSensitivePath(filePath) || resolvedPath !== null && isSensitivePath(resolvedPath)) {
|
|
384
625
|
return { content: `Refused to read sensitive file: ${args.path}`, isError: true };
|
|
385
626
|
}
|
|
386
|
-
const st = await
|
|
627
|
+
const st = await stat4(filePath).catch(() => null);
|
|
387
628
|
if (!st) {
|
|
388
629
|
return { content: `File not found: ${args.path}`, isError: true };
|
|
389
630
|
}
|
|
@@ -472,9 +713,9 @@ function createReadFileTool() {
|
|
|
472
713
|
var readFileTool = createReadFileTool();
|
|
473
714
|
|
|
474
715
|
// src/tools/fs/read-image.ts
|
|
475
|
-
import { readFile as
|
|
476
|
-
import
|
|
477
|
-
import { z as
|
|
716
|
+
import { readFile as readFile4, realpath as realpath4, stat as stat5 } from "fs/promises";
|
|
717
|
+
import path6 from "path";
|
|
718
|
+
import { z as z6 } from "zod";
|
|
478
719
|
var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
479
720
|
var IMAGE_MIME_TYPES = {
|
|
480
721
|
".png": "image/png",
|
|
@@ -483,8 +724,8 @@ var IMAGE_MIME_TYPES = {
|
|
|
483
724
|
".gif": "image/gif",
|
|
484
725
|
".webp": "image/webp"
|
|
485
726
|
};
|
|
486
|
-
var
|
|
487
|
-
path:
|
|
727
|
+
var schema6 = z6.object({
|
|
728
|
+
path: z6.string().describe(
|
|
488
729
|
"Image file path (png/jpg/jpeg/gif/webp), absolute or relative to the working directory"
|
|
489
730
|
)
|
|
490
731
|
});
|
|
@@ -492,7 +733,7 @@ var readImageTool = {
|
|
|
492
733
|
name: "read_image",
|
|
493
734
|
description: "Read an image file (png, jpg, gif, webp) and show it to the model. Use this whenever the user references a screenshot, diagram, or picture file you need to see. Images larger than 2000px are downsampled automatically.",
|
|
494
735
|
permission: "read",
|
|
495
|
-
parameters:
|
|
736
|
+
parameters: schema6,
|
|
496
737
|
async execute(args, ctx) {
|
|
497
738
|
if (ctx.visionEnabled === false) {
|
|
498
739
|
return {
|
|
@@ -500,19 +741,19 @@ var readImageTool = {
|
|
|
500
741
|
isError: true
|
|
501
742
|
};
|
|
502
743
|
}
|
|
503
|
-
const filePath =
|
|
504
|
-
const resolvedPath = await
|
|
744
|
+
const filePath = path6.resolve(ctx.cwd, args.path);
|
|
745
|
+
const resolvedPath = await realpath4(filePath).catch(() => null);
|
|
505
746
|
if (isSensitivePath(filePath) || resolvedPath !== null && isSensitivePath(resolvedPath)) {
|
|
506
747
|
return { content: `Refused to read sensitive file: ${args.path}`, isError: true };
|
|
507
748
|
}
|
|
508
|
-
const mimeType = IMAGE_MIME_TYPES[
|
|
749
|
+
const mimeType = IMAGE_MIME_TYPES[path6.extname(filePath).toLowerCase()];
|
|
509
750
|
if (!mimeType) {
|
|
510
751
|
return {
|
|
511
752
|
content: `Not a supported image (png/jpg/jpeg/gif/webp): ${args.path}`,
|
|
512
753
|
isError: true
|
|
513
754
|
};
|
|
514
755
|
}
|
|
515
|
-
const st = await
|
|
756
|
+
const st = await stat5(filePath).catch(() => null);
|
|
516
757
|
if (!st || !st.isFile()) {
|
|
517
758
|
return { content: `File not found: ${args.path}`, isError: true };
|
|
518
759
|
}
|
|
@@ -522,7 +763,7 @@ var readImageTool = {
|
|
|
522
763
|
isError: true
|
|
523
764
|
};
|
|
524
765
|
}
|
|
525
|
-
const buf = await
|
|
766
|
+
const buf = await readFile4(filePath).catch(() => null);
|
|
526
767
|
if (!buf || buf.length === 0) {
|
|
527
768
|
return { content: `Failed to read ${args.path}`, isError: true };
|
|
528
769
|
}
|
|
@@ -557,19 +798,19 @@ var readImageTool = {
|
|
|
557
798
|
|
|
558
799
|
// src/tools/fs/write.ts
|
|
559
800
|
import { mkdir, writeFile as writeFile2 } from "fs/promises";
|
|
560
|
-
import
|
|
561
|
-
import { z as
|
|
562
|
-
var
|
|
563
|
-
path:
|
|
564
|
-
content:
|
|
801
|
+
import path7 from "path";
|
|
802
|
+
import { z as z7 } from "zod";
|
|
803
|
+
var schema7 = z7.object({
|
|
804
|
+
path: z7.string().describe("File path, absolute or relative to the working directory"),
|
|
805
|
+
content: z7.string().describe("Full content to write to the file")
|
|
565
806
|
});
|
|
566
807
|
var writeFileTool = {
|
|
567
808
|
name: "write_file",
|
|
568
809
|
description: "Write content to a file, creating parent directories as needed. Overwrites existing files. Refuses content with NUL bytes (binary) or over 5MB.",
|
|
569
810
|
permission: "write",
|
|
570
|
-
parameters:
|
|
811
|
+
parameters: schema7,
|
|
571
812
|
async execute(args, ctx) {
|
|
572
|
-
const filePath =
|
|
813
|
+
const filePath = path7.resolve(ctx.cwd, args.path);
|
|
573
814
|
if (args.content.includes("\0")) {
|
|
574
815
|
return {
|
|
575
816
|
content: `Refused to write ${args.path}: content contains NUL bytes (write_file is text-only)`,
|
|
@@ -585,7 +826,7 @@ var writeFileTool = {
|
|
|
585
826
|
}
|
|
586
827
|
const snapshot = await captureSnapshot(filePath, "write_file", ctx.snapshotContext);
|
|
587
828
|
try {
|
|
588
|
-
await mkdir(
|
|
829
|
+
await mkdir(path7.dirname(filePath), { recursive: true });
|
|
589
830
|
await writeFile2(filePath, args.content, "utf8");
|
|
590
831
|
} catch (err) {
|
|
591
832
|
return { content: `Failed to write ${args.path}: ${err.message}`, isError: true };
|
|
@@ -596,14 +837,14 @@ var writeFileTool = {
|
|
|
596
837
|
};
|
|
597
838
|
|
|
598
839
|
// src/tools/screenshot.ts
|
|
599
|
-
import { z as
|
|
840
|
+
import { z as z8 } from "zod";
|
|
600
841
|
|
|
601
842
|
// src/core/screenshot.ts
|
|
602
843
|
import { execFile } from "child_process";
|
|
603
844
|
import { randomUUID } from "crypto";
|
|
604
|
-
import { readFile as
|
|
845
|
+
import { readFile as readFile5, unlink } from "fs/promises";
|
|
605
846
|
import os from "os";
|
|
606
|
-
import
|
|
847
|
+
import path8 from "path";
|
|
607
848
|
import { promisify } from "util";
|
|
608
849
|
var SCREENSHOT_TIMEOUT_MS = 2e4;
|
|
609
850
|
var execFileAsync = promisify(execFile);
|
|
@@ -676,7 +917,7 @@ async function commandExists(command) {
|
|
|
676
917
|
}
|
|
677
918
|
var LINUX_TOOL_NAMES = ["grim", "gnome-screenshot", "spectacle", "import", "scrot"];
|
|
678
919
|
async function captureScreenshot() {
|
|
679
|
-
const outputFile =
|
|
920
|
+
const outputFile = path8.join(os.tmpdir(), `star-screenshot-${randomUUID()}.png`);
|
|
680
921
|
try {
|
|
681
922
|
const tools = {};
|
|
682
923
|
if (process.platform === "linux") {
|
|
@@ -695,7 +936,7 @@ async function captureScreenshot() {
|
|
|
695
936
|
const detail = err instanceof Error && err.message ? ` (${err.message})` : "";
|
|
696
937
|
return { ok: false, error: `${plan.command} failed to capture the screen${detail}` };
|
|
697
938
|
}
|
|
698
|
-
const buf = await
|
|
939
|
+
const buf = await readFile5(outputFile).catch(() => null);
|
|
699
940
|
if (!buf || buf.length === 0) {
|
|
700
941
|
return { ok: false, error: `${plan.command} produced no image file` };
|
|
701
942
|
}
|
|
@@ -721,13 +962,13 @@ async function captureScreenshot() {
|
|
|
721
962
|
}
|
|
722
963
|
|
|
723
964
|
// src/tools/screenshot.ts
|
|
724
|
-
var
|
|
965
|
+
var schema8 = z8.object({});
|
|
725
966
|
function createScreenshotTool(capture = captureScreenshot) {
|
|
726
967
|
return {
|
|
727
968
|
name: "screenshot",
|
|
728
969
|
description: "Capture a screenshot of the primary display and see it. Use this to check the actual visual result of a running program (web page, GUI) \u2014 e.g. after starting a dev server \u2014 instead of inferring from code. The capture shows exactly what is visible on screen, including overlapping windows: content hidden behind other windows cannot be seen, so if a specific window matters ask the user to bring it to the front first. Note the capture may include anything else currently on screen.",
|
|
729
970
|
permission: "read",
|
|
730
|
-
parameters:
|
|
971
|
+
parameters: schema8,
|
|
731
972
|
async execute(_args, ctx) {
|
|
732
973
|
if (ctx.visionEnabled === false) {
|
|
733
974
|
return {
|
|
@@ -753,7 +994,7 @@ function createScreenshotTool(capture = captureScreenshot) {
|
|
|
753
994
|
var screenshotTool = createScreenshotTool();
|
|
754
995
|
|
|
755
996
|
// src/tools/tasks.ts
|
|
756
|
-
import { z as
|
|
997
|
+
import { z as z9 } from "zod";
|
|
757
998
|
|
|
758
999
|
// src/tasks/format.ts
|
|
759
1000
|
function formatDuration(startedAt, endedAt) {
|
|
@@ -799,8 +1040,8 @@ function formatTaskFinished(task) {
|
|
|
799
1040
|
}
|
|
800
1041
|
|
|
801
1042
|
// src/tools/tasks.ts
|
|
802
|
-
var idSchema =
|
|
803
|
-
id:
|
|
1043
|
+
var idSchema = z9.object({
|
|
1044
|
+
id: z9.string().describe("Task id, e.g. task-1 or agent-1")
|
|
804
1045
|
});
|
|
805
1046
|
function truncateOutput(s) {
|
|
806
1047
|
if (s.length <= MAX_OUTPUT) return s;
|
|
@@ -826,7 +1067,7 @@ var taskListTool = {
|
|
|
826
1067
|
name: "task_list",
|
|
827
1068
|
description: "List background tasks (shell commands and subagents) with status and runtime.",
|
|
828
1069
|
permission: "read",
|
|
829
|
-
parameters:
|
|
1070
|
+
parameters: z9.object({}),
|
|
830
1071
|
execute(_args, ctx) {
|
|
831
1072
|
const caller = callerId(ctx);
|
|
832
1073
|
const shell = formatTaskList(
|
|
@@ -888,17 +1129,17 @@ var taskKillTool = {
|
|
|
888
1129
|
};
|
|
889
1130
|
|
|
890
1131
|
// src/tools/web/search.ts
|
|
891
|
-
import { z as
|
|
1132
|
+
import { z as z10 } from "zod";
|
|
892
1133
|
|
|
893
1134
|
// src/llm/debug.ts
|
|
894
1135
|
import { appendFileSync } from "fs";
|
|
895
|
-
import
|
|
1136
|
+
import path9 from "path";
|
|
896
1137
|
function debugStreamLog(event, data) {
|
|
897
1138
|
if (!process.env.STAR_DEBUG) return;
|
|
898
1139
|
try {
|
|
899
1140
|
const line = `${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event, ...data })}
|
|
900
1141
|
`;
|
|
901
|
-
appendFileSync(
|
|
1142
|
+
appendFileSync(path9.join(starHome(), "debug.log"), line);
|
|
902
1143
|
} catch {
|
|
903
1144
|
}
|
|
904
1145
|
}
|
|
@@ -908,9 +1149,9 @@ var TIMEOUT_MS = 15e3;
|
|
|
908
1149
|
var DEFAULT_MAX_RESULTS = 5;
|
|
909
1150
|
var MAX_RESULTS_LIMIT = 10;
|
|
910
1151
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
911
|
-
var
|
|
912
|
-
query:
|
|
913
|
-
maxResults:
|
|
1152
|
+
var schema9 = z10.object({
|
|
1153
|
+
query: z10.string().min(1).describe("The search query"),
|
|
1154
|
+
maxResults: z10.number().int().min(1).max(MAX_RESULTS_LIMIT).optional().describe(
|
|
914
1155
|
`Maximum number of results to return (default ${DEFAULT_MAX_RESULTS}, max ${MAX_RESULTS_LIMIT})`
|
|
915
1156
|
)
|
|
916
1157
|
});
|
|
@@ -1031,7 +1272,7 @@ var webSearchTool = {
|
|
|
1031
1272
|
name: "web_search",
|
|
1032
1273
|
description: "Search the web and return a numbered list of results with title, URL and snippet. Uses DuckDuckGo with automatic fallback to DuckDuckGo Lite and Bing when a source fails or returns nothing. No API key required. Use web_fetch to read a result's full page.",
|
|
1033
1274
|
permission: "read",
|
|
1034
|
-
parameters:
|
|
1275
|
+
parameters: schema9,
|
|
1035
1276
|
async execute(args, ctx) {
|
|
1036
1277
|
const maxResults = Math.min(args.maxResults ?? DEFAULT_MAX_RESULTS, MAX_RESULTS_LIMIT);
|
|
1037
1278
|
const failures = [];
|
|
@@ -1105,7 +1346,10 @@ var ToolRegistry = class {
|
|
|
1105
1346
|
tools = /* @__PURE__ */ new Map();
|
|
1106
1347
|
// todoStore defaults to the module-level shared store; subagent loops pass
|
|
1107
1348
|
// their own instance so a child's todo_write cannot clobber the parent list.
|
|
1349
|
+
// Exposed so the agent loop can watch open items for staleness.
|
|
1350
|
+
todos;
|
|
1108
1351
|
constructor(todoStore) {
|
|
1352
|
+
this.todos = todoStore ?? sharedTodoStore();
|
|
1109
1353
|
for (const tool of [
|
|
1110
1354
|
// Fresh per registry (i.e. per agent loop): the unchanged-since-last-
|
|
1111
1355
|
// read cache must not leak across loops, whose contexts differ.
|
|
@@ -1116,13 +1360,14 @@ var ToolRegistry = class {
|
|
|
1116
1360
|
editFileTool,
|
|
1117
1361
|
globTool,
|
|
1118
1362
|
grepTool,
|
|
1363
|
+
codeOutlineTool,
|
|
1119
1364
|
bashTool,
|
|
1120
1365
|
webFetchTool,
|
|
1121
1366
|
webSearchTool,
|
|
1122
1367
|
taskListTool,
|
|
1123
1368
|
taskOutputTool,
|
|
1124
1369
|
taskKillTool,
|
|
1125
|
-
...createTodoTools(
|
|
1370
|
+
...createTodoTools(this.todos)
|
|
1126
1371
|
]) {
|
|
1127
1372
|
this.register(tool);
|
|
1128
1373
|
}
|
|
@@ -1130,6 +1375,16 @@ var ToolRegistry = class {
|
|
|
1130
1375
|
register(tool) {
|
|
1131
1376
|
this.tools.set(tool.name, tool);
|
|
1132
1377
|
}
|
|
1378
|
+
// Drops every tool the predicate rejects. Used to restrict a read-only
|
|
1379
|
+
// subagent loop's tool set; the agent loop reads list() lazily per
|
|
1380
|
+
// request, so a retain right after loop construction still shapes the
|
|
1381
|
+
// first request. The permission gate stays as backstop for anything
|
|
1382
|
+
// attempted outside the model's tool map.
|
|
1383
|
+
retain(predicate) {
|
|
1384
|
+
for (const [name, tool] of this.tools) {
|
|
1385
|
+
if (!predicate(tool)) this.tools.delete(name);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1133
1388
|
// Drops per-session volatile tool state after a wholesale history rewrite
|
|
1134
1389
|
// (compaction, /undo, resume): anything a tool remembers as "already sent
|
|
1135
1390
|
// to the model" may no longer be in the context.
|
|
@@ -1160,4 +1415,4 @@ export {
|
|
|
1160
1415
|
ToolRegistry,
|
|
1161
1416
|
createDefaultRegistry
|
|
1162
1417
|
};
|
|
1163
|
-
//# sourceMappingURL=chunk-
|
|
1418
|
+
//# sourceMappingURL=chunk-Q5FJN4LN.js.map
|