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