@cryer/star-cli 0.3.7 → 0.3.9
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 +10 -2
- package/README.zh-CN.md +9 -2
- package/dist/chunk-4IRY44TG.js +34 -0
- package/dist/chunk-4IRY44TG.js.map +1 -0
- package/dist/chunk-56ZIA57D.js +493 -0
- package/dist/chunk-56ZIA57D.js.map +1 -0
- package/dist/chunk-A5PXPWHB.js +2014 -0
- package/dist/chunk-A5PXPWHB.js.map +1 -0
- package/dist/chunk-MVAMPVTA.js +232 -0
- package/dist/chunk-MVAMPVTA.js.map +1 -0
- package/dist/chunk-OIJYNQFZ.js +1163 -0
- package/dist/chunk-OIJYNQFZ.js.map +1 -0
- package/dist/chunk-RE3NOULF.js +3415 -0
- package/dist/chunk-RE3NOULF.js.map +1 -0
- package/dist/{chunk-4X25DNTF.js → chunk-VJFWTXWN.js} +337 -420
- package/dist/chunk-VJFWTXWN.js.map +1 -0
- package/dist/loop-PVOSF6LR.js +17 -0
- package/dist/main.js +257 -79
- package/dist/main.js.map +1 -1
- package/dist/provider-EWMUPBLH.js +11 -0
- package/dist/provider-EWMUPBLH.js.map +1 -0
- package/dist/{repl-RT6HBM5B.js → repl-OO7C6V7M.js} +444 -271
- package/dist/repl-OO7C6V7M.js.map +1 -0
- package/dist/tools-JDTACJYX.js +18 -0
- package/dist/tools-JDTACJYX.js.map +1 -0
- package/package.json +17 -26
- package/dist/chunk-4X25DNTF.js.map +0 -1
- package/dist/chunk-OOOW7FC4.js +0 -5354
- package/dist/chunk-OOOW7FC4.js.map +0 -1
- package/dist/loop-DVVPIOS7.js +0 -10
- package/dist/repl-RT6HBM5B.js.map +0 -1
- /package/dist/{loop-DVVPIOS7.js.map → loop-PVOSF6LR.js.map} +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
coreMessageText,
|
|
4
|
+
pruneTreeReposExcept,
|
|
5
|
+
removeTreeRepo
|
|
6
|
+
} from "./chunk-56ZIA57D.js";
|
|
2
7
|
import {
|
|
3
8
|
SKIP_DIRS,
|
|
4
9
|
createIgnorePredicate,
|
|
5
10
|
downsampleImageIfNeeded,
|
|
6
|
-
envFilePath,
|
|
7
11
|
isSensitivePath,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
sessionsDir,
|
|
11
|
-
wrapFetchWithActivity
|
|
12
|
-
} from "./chunk-OOOW7FC4.js";
|
|
12
|
+
sessionsDir
|
|
13
|
+
} from "./chunk-A5PXPWHB.js";
|
|
13
14
|
|
|
14
15
|
// src/cli/cost.ts
|
|
15
16
|
function formatDollars(cost) {
|
|
@@ -53,6 +54,70 @@ function estimateCost(usage, modelName, model) {
|
|
|
53
54
|
return `Estimated cost: $${formatDollars(cost)} (${model?.name} @ $${model?.promptPrice}/M prompt, $${model?.completionPrice}/M completion${cachePrice})`;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// src/cli/icons.ts
|
|
58
|
+
var EMOJI_TERM_PROGRAMS = /* @__PURE__ */ new Set([
|
|
59
|
+
"vscode",
|
|
60
|
+
"WezTerm",
|
|
61
|
+
"iTerm.app",
|
|
62
|
+
"Apple_Terminal",
|
|
63
|
+
"ghostty",
|
|
64
|
+
"kitty",
|
|
65
|
+
"Hyper",
|
|
66
|
+
"WarpTerminal"
|
|
67
|
+
]);
|
|
68
|
+
function resolveIconMode(env = process.env) {
|
|
69
|
+
const override = env.STAR_ICONS;
|
|
70
|
+
if (override === "emoji" || override === "plain") return override;
|
|
71
|
+
if (env.WT_SESSION) return "emoji";
|
|
72
|
+
if (EMOJI_TERM_PROGRAMS.has(env.TERM_PROGRAM ?? "")) return "emoji";
|
|
73
|
+
const term = env.TERM ?? "";
|
|
74
|
+
if (term.includes("kitty") || term.includes("alacritty")) return "emoji";
|
|
75
|
+
return "plain";
|
|
76
|
+
}
|
|
77
|
+
var EMOJI_TOOL_ICONS = {
|
|
78
|
+
bash: "\u{1F4BB}",
|
|
79
|
+
read_file: "\u{1F4C4}",
|
|
80
|
+
write_file: "\u{1F4DD}",
|
|
81
|
+
edit_file: "\u270F\uFE0F",
|
|
82
|
+
glob: "\u{1F4C2}",
|
|
83
|
+
grep: "\u{1F50D}",
|
|
84
|
+
web_search: "\u{1F50E}",
|
|
85
|
+
web_fetch: "\u{1F310}",
|
|
86
|
+
todo_write: "\u{1F4CB}",
|
|
87
|
+
todo_read: "\u{1F4CB}",
|
|
88
|
+
task_list: "\u{1F9F5}",
|
|
89
|
+
task_output: "\u{1F9F5}",
|
|
90
|
+
task_kill: "\u{1F6D1}",
|
|
91
|
+
subagent: "\u{1F916}",
|
|
92
|
+
skill: "\u2728",
|
|
93
|
+
remember: "\u{1F9E0}"
|
|
94
|
+
};
|
|
95
|
+
var PLAIN_TOOL_ICONS = {
|
|
96
|
+
bash: "$",
|
|
97
|
+
read_file: "\u2261",
|
|
98
|
+
write_file: "+",
|
|
99
|
+
edit_file: "\u270E",
|
|
100
|
+
glob: "*",
|
|
101
|
+
grep: "/",
|
|
102
|
+
web_search: "?",
|
|
103
|
+
web_fetch: "\u21D3",
|
|
104
|
+
todo_write: "\u2023",
|
|
105
|
+
todo_read: "\u2023",
|
|
106
|
+
task_list: "\xB6",
|
|
107
|
+
task_output: "\xB6",
|
|
108
|
+
task_kill: "\xD7",
|
|
109
|
+
subagent: "\u25C9",
|
|
110
|
+
skill: "\u2727",
|
|
111
|
+
remember: "\u203B"
|
|
112
|
+
};
|
|
113
|
+
var iconMode = resolveIconMode();
|
|
114
|
+
function toolIcon(name) {
|
|
115
|
+
const map = iconMode === "emoji" ? EMOJI_TOOL_ICONS : PLAIN_TOOL_ICONS;
|
|
116
|
+
return map[name] ?? (iconMode === "emoji" ? "\u{1F527}" : "\u25B8");
|
|
117
|
+
}
|
|
118
|
+
var thinkingIcon = iconMode === "emoji" ? "\u{1F4AD}" : "\u2727";
|
|
119
|
+
var folderIcon = iconMode === "emoji" ? "\u{1F4C1} " : "";
|
|
120
|
+
|
|
56
121
|
// src/cli/terminal-text.ts
|
|
57
122
|
var UNSAFE_CHARS = /[\x00-\x09\x0b-\x1f\x7f]/;
|
|
58
123
|
var CONTROL_CHARS = /[\x00-\x08\x0b-\x1f\x7f]/g;
|
|
@@ -298,25 +363,48 @@ function formatStreamError(error) {
|
|
|
298
363
|
}
|
|
299
364
|
return error.message;
|
|
300
365
|
}
|
|
301
|
-
function coreMessageText(message) {
|
|
302
|
-
const content = message.content;
|
|
303
|
-
if (typeof content === "string") return content;
|
|
304
|
-
if (Array.isArray(content)) {
|
|
305
|
-
return content.filter((part) => part.type === "text").map((part) => "text" in part ? part.text : "").join(" ");
|
|
306
|
-
}
|
|
307
|
-
return "";
|
|
308
|
-
}
|
|
309
366
|
function buildDisplayMessages(messages) {
|
|
310
367
|
const display = [];
|
|
311
368
|
let collapsed = 0;
|
|
312
369
|
for (const message of messages) {
|
|
313
370
|
if (message.role === "user" || message.role === "assistant") {
|
|
314
|
-
const
|
|
315
|
-
if (
|
|
371
|
+
const content = message.content;
|
|
372
|
+
if (!Array.isArray(content)) {
|
|
373
|
+
const text = coreMessageText(message);
|
|
374
|
+
if (text) {
|
|
375
|
+
display.push({ id: display.length, role: message.role, text: toTerminalSafe(text) });
|
|
376
|
+
} else {
|
|
377
|
+
collapsed++;
|
|
378
|
+
}
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
let textParts = [];
|
|
382
|
+
let represented = false;
|
|
383
|
+
const flushText = () => {
|
|
384
|
+
const text = textParts.join(" ");
|
|
385
|
+
textParts = [];
|
|
386
|
+
if (!text) return;
|
|
387
|
+
represented = true;
|
|
316
388
|
display.push({ id: display.length, role: message.role, text: toTerminalSafe(text) });
|
|
317
|
-
}
|
|
318
|
-
|
|
389
|
+
};
|
|
390
|
+
for (const part of content) {
|
|
391
|
+
if (part.type === "text" && "text" in part) {
|
|
392
|
+
textParts.push(part.text);
|
|
393
|
+
} else if (part.type === "tool-call") {
|
|
394
|
+
flushText();
|
|
395
|
+
represented = true;
|
|
396
|
+
display.push({
|
|
397
|
+
id: display.length,
|
|
398
|
+
role: "tool",
|
|
399
|
+
text: toTerminalSafe(
|
|
400
|
+
`${toolIcon(part.toolName)} ${part.toolName} ${summarizeArgs(part.args)}`
|
|
401
|
+
),
|
|
402
|
+
dim: true
|
|
403
|
+
});
|
|
404
|
+
}
|
|
319
405
|
}
|
|
406
|
+
flushText();
|
|
407
|
+
if (!represented) collapsed++;
|
|
320
408
|
} else if (message.role === "tool") {
|
|
321
409
|
collapsed++;
|
|
322
410
|
}
|
|
@@ -529,292 +617,15 @@ ${buf.toString("utf8")}
|
|
|
529
617
|
return { input, attached, skipped, images };
|
|
530
618
|
}
|
|
531
619
|
|
|
532
|
-
// src/cli/system-prompt.ts
|
|
533
|
-
var SYSTEM_PROMPT = `You are Star CLI, an AI coding agent running in the user's terminal.
|
|
534
|
-
You help with software engineering tasks: reading, writing and editing code, running shell commands, and managing todos.
|
|
535
|
-
Be concise and direct. Use tools when they help accomplish the task.
|
|
536
|
-
For any task with two or more steps, start by creating a todo list with todo_write and keep it updated as you progress; only mark an item completed after you have verified its result.
|
|
537
|
-
The working directory is the user's project root; never touch files outside it without explicit instruction.
|
|
538
|
-
For conversion, batch-processing, or other scriptable tasks, write a script file into the working directory and run it instead of pasting long inline code into the shell; once the script's output is verified, delete the script unless the user asked to keep it.
|
|
539
|
-
When a tool call or command fails, read the error output, work out the cause, and try again with a corrected or alternative approach \u2014 never repeat an identical failing call without changing something.
|
|
540
|
-
Never consider the task finished until you have verified the result yourself: run the tests, the build, or a check command that proves the output is correct, and if verification fails or reveals gaps, keep fixing until it passes.
|
|
541
|
-
Do not end your turn while the task is still incomplete; keep going until it is done or you are genuinely blocked, and if you are blocked, state exactly what is missing.
|
|
542
|
-
Never end a reply by announcing what you will do next \u2014 either do it now with tool calls, or do not mention it. Narrating future actions is not progress.`;
|
|
543
|
-
|
|
544
|
-
// src/llm/registry.ts
|
|
545
|
-
function listModels(config) {
|
|
546
|
-
return config.models;
|
|
547
|
-
}
|
|
548
|
-
function resolveModelConfig(config, modelName) {
|
|
549
|
-
const name = modelName ?? config.defaultModel;
|
|
550
|
-
const model = config.models.find((m) => m.name === name);
|
|
551
|
-
if (!model) {
|
|
552
|
-
const available = config.models.map((m) => m.name).join(", ") || "(none)";
|
|
553
|
-
throw new Error(`Model "${name}" not found. Available models: ${available}`);
|
|
554
|
-
}
|
|
555
|
-
return model;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// src/llm/provider.ts
|
|
559
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
560
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
561
|
-
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
562
|
-
|
|
563
|
-
// src/llm/sse-normalize.ts
|
|
564
|
-
function splitJsonPrefix(text) {
|
|
565
|
-
if (!text.startsWith("{")) return null;
|
|
566
|
-
let depth = 0;
|
|
567
|
-
let inString = false;
|
|
568
|
-
let escaped = false;
|
|
569
|
-
for (let i = 0; i < text.length; i++) {
|
|
570
|
-
const ch = text[i];
|
|
571
|
-
if (inString) {
|
|
572
|
-
if (escaped) escaped = false;
|
|
573
|
-
else if (ch === "\\") escaped = true;
|
|
574
|
-
else if (ch === '"') inString = false;
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
if (ch === '"') inString = true;
|
|
578
|
-
else if (ch === "{") depth += 1;
|
|
579
|
-
else if (ch === "}") {
|
|
580
|
-
depth -= 1;
|
|
581
|
-
if (depth === 0) return [text.slice(0, i + 1), text.slice(i + 1).trimStart()];
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return null;
|
|
585
|
-
}
|
|
586
|
-
var SseNormalizeTransform = class extends TransformStream {
|
|
587
|
-
constructor() {
|
|
588
|
-
let buffer = "";
|
|
589
|
-
let pending = null;
|
|
590
|
-
const flushPending = (controller) => {
|
|
591
|
-
if (pending === null) return;
|
|
592
|
-
controller.enqueue(`data: ${pending}
|
|
593
|
-
|
|
594
|
-
`);
|
|
595
|
-
pending = null;
|
|
596
|
-
};
|
|
597
|
-
const emitPayload = (payload, controller) => {
|
|
598
|
-
let rest = payload;
|
|
599
|
-
for (; ; ) {
|
|
600
|
-
const split = splitJsonPrefix(rest);
|
|
601
|
-
if (!split) {
|
|
602
|
-
pending = rest;
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
const [json, tail] = split;
|
|
606
|
-
if (tail === "") {
|
|
607
|
-
pending = json;
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
controller.enqueue(`data: ${json}
|
|
611
|
-
|
|
612
|
-
`);
|
|
613
|
-
if (tail.startsWith("data:")) {
|
|
614
|
-
rest = tail.slice(5).replace(/^ /, "");
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
if (tail.startsWith("{")) {
|
|
618
|
-
rest = tail;
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
controller.enqueue(`${tail}
|
|
622
|
-
`);
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
const handleLine = (raw, controller) => {
|
|
627
|
-
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
628
|
-
if (line === "") {
|
|
629
|
-
flushPending(controller);
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
if (line.startsWith("data:")) {
|
|
633
|
-
flushPending(controller);
|
|
634
|
-
emitPayload(line.slice(5).replace(/^ /, ""), controller);
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (line.startsWith("{") && pending !== null) {
|
|
638
|
-
flushPending(controller);
|
|
639
|
-
emitPayload(line, controller);
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
flushPending(controller);
|
|
643
|
-
controller.enqueue(`${line}
|
|
644
|
-
`);
|
|
645
|
-
};
|
|
646
|
-
super({
|
|
647
|
-
transform(chunk, controller) {
|
|
648
|
-
buffer += chunk;
|
|
649
|
-
let index = buffer.indexOf("\n");
|
|
650
|
-
while (index !== -1) {
|
|
651
|
-
handleLine(buffer.slice(0, index), controller);
|
|
652
|
-
buffer = buffer.slice(index + 1);
|
|
653
|
-
index = buffer.indexOf("\n");
|
|
654
|
-
}
|
|
655
|
-
},
|
|
656
|
-
flush(controller) {
|
|
657
|
-
if (buffer.length > 0) handleLine(buffer, controller);
|
|
658
|
-
flushPending(controller);
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
function createSseNormalizingFetch(baseFetch = fetch) {
|
|
664
|
-
return async (input, init) => {
|
|
665
|
-
const response = await baseFetch(input, init);
|
|
666
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
667
|
-
if (!response.body || !contentType.includes("text/event-stream")) {
|
|
668
|
-
return response;
|
|
669
|
-
}
|
|
670
|
-
const body = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new SseNormalizeTransform()).pipeThrough(new TextEncoderStream());
|
|
671
|
-
const headers = new Headers(response.headers);
|
|
672
|
-
headers.delete("content-length");
|
|
673
|
-
headers.delete("content-encoding");
|
|
674
|
-
return new Response(body, {
|
|
675
|
-
status: response.status,
|
|
676
|
-
statusText: response.statusText,
|
|
677
|
-
headers
|
|
678
|
-
});
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// src/llm/provider.ts
|
|
683
|
-
function resolveApiKey(provider) {
|
|
684
|
-
const fromEnv = provider.apiKeyEnv ? process.env[provider.apiKeyEnv] : void 0;
|
|
685
|
-
const apiKey = fromEnv ?? provider.apiKey;
|
|
686
|
-
if (!apiKey) {
|
|
687
|
-
const hint = provider.apiKeyEnv ? `set ${provider.apiKeyEnv} or configure apiKey` : "configure apiKey";
|
|
688
|
-
throw new Error(`Missing API key for provider "${provider.name}": ${hint}`);
|
|
689
|
-
}
|
|
690
|
-
return apiKey;
|
|
691
|
-
}
|
|
692
|
-
function createModel(config, modelName) {
|
|
693
|
-
const modelConfig = resolveModelConfig(config, modelName);
|
|
694
|
-
const provider = config.providers.find((p) => p.name === modelConfig.provider);
|
|
695
|
-
if (!provider) {
|
|
696
|
-
const available = config.providers.map((p) => p.name).join(", ") || "(none)";
|
|
697
|
-
throw new Error(
|
|
698
|
-
`Provider "${modelConfig.provider}" for model "${modelConfig.name}" not found. Available providers: ${available}`
|
|
699
|
-
);
|
|
700
|
-
}
|
|
701
|
-
const apiKey = resolveApiKey(provider);
|
|
702
|
-
const trackedFetch = wrapFetchWithActivity();
|
|
703
|
-
switch (provider.protocol) {
|
|
704
|
-
case "anthropic":
|
|
705
|
-
return createAnthropic({
|
|
706
|
-
baseURL: provider.baseURL,
|
|
707
|
-
apiKey,
|
|
708
|
-
headers: provider.headers,
|
|
709
|
-
fetch: trackedFetch
|
|
710
|
-
})(modelConfig.model);
|
|
711
|
-
case "openai-compatible":
|
|
712
|
-
return createOpenAICompatible({
|
|
713
|
-
name: provider.name,
|
|
714
|
-
baseURL: provider.baseURL,
|
|
715
|
-
apiKey,
|
|
716
|
-
headers: provider.headers,
|
|
717
|
-
fetch: trackedFetch
|
|
718
|
-
})(modelConfig.model, {}, { includeUsage: true });
|
|
719
|
-
case "openai-responses":
|
|
720
|
-
return createOpenAI({
|
|
721
|
-
baseURL: provider.baseURL,
|
|
722
|
-
apiKey,
|
|
723
|
-
headers: provider.headers,
|
|
724
|
-
// Relays fronting the Responses API sometimes batch several JSON
|
|
725
|
-
// events into one SSE data payload, which the SDK's parser cannot
|
|
726
|
-
// read; normalize the wire format back to one event per data line.
|
|
727
|
-
fetch: createSseNormalizingFetch(trackedFetch)
|
|
728
|
-
}).responses(modelConfig.model);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
function reasoningEffortMetadata(config, modelName) {
|
|
732
|
-
const modelConfig = resolveModelConfig(config, modelName);
|
|
733
|
-
if (!modelConfig.reasoningEffort) return void 0;
|
|
734
|
-
const provider = config.providers.find((p) => p.name === modelConfig.provider);
|
|
735
|
-
if (!provider || provider.protocol === "anthropic") return void 0;
|
|
736
|
-
const key = provider.protocol === "openai-responses" ? "openai" : provider.name;
|
|
737
|
-
return { [key]: { reasoningEffort: modelConfig.reasoningEffort } };
|
|
738
|
-
}
|
|
739
|
-
|
|
740
620
|
// src/session/checkpoints.ts
|
|
741
|
-
import fs from "fs/promises";
|
|
742
|
-
import path2 from "path";
|
|
743
|
-
function checkpointsDir(sessionDir) {
|
|
744
|
-
return path2.join(sessionDir, "checkpoints");
|
|
745
|
-
}
|
|
746
|
-
function indexPath(sessionDir) {
|
|
747
|
-
return path2.join(checkpointsDir(sessionDir), "index.json");
|
|
748
|
-
}
|
|
749
|
-
var indexWriteQueues = /* @__PURE__ */ new Map();
|
|
750
|
-
function enqueueIndexWrite(sessionDir, op) {
|
|
751
|
-
const queued = indexWriteQueues.get(sessionDir) ?? Promise.resolve();
|
|
752
|
-
const run = queued.then(op);
|
|
753
|
-
indexWriteQueues.set(
|
|
754
|
-
sessionDir,
|
|
755
|
-
run.then(
|
|
756
|
-
() => {
|
|
757
|
-
},
|
|
758
|
-
() => {
|
|
759
|
-
}
|
|
760
|
-
)
|
|
761
|
-
);
|
|
762
|
-
return run;
|
|
763
|
-
}
|
|
764
|
-
function checkpointContentPath(sessionDir, id) {
|
|
765
|
-
return path2.join(checkpointsDir(sessionDir), `${id}.snapshot`);
|
|
766
|
-
}
|
|
767
|
-
async function listCheckpointRecords(sessionDir) {
|
|
768
|
-
let raw;
|
|
769
|
-
try {
|
|
770
|
-
raw = await fs.readFile(indexPath(sessionDir), "utf8");
|
|
771
|
-
} catch {
|
|
772
|
-
return [];
|
|
773
|
-
}
|
|
774
|
-
try {
|
|
775
|
-
const parsed = JSON.parse(raw);
|
|
776
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
777
|
-
} catch {
|
|
778
|
-
return [];
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
async function appendCheckpointRecord(sessionDir, record, content) {
|
|
782
|
-
await enqueueIndexWrite(sessionDir, async () => {
|
|
783
|
-
await fs.mkdir(checkpointsDir(sessionDir), { recursive: true, mode: 448 });
|
|
784
|
-
if (record.existed && content !== null) {
|
|
785
|
-
await fs.writeFile(checkpointContentPath(sessionDir, record.id), content, "utf8");
|
|
786
|
-
}
|
|
787
|
-
const records = await listCheckpointRecords(sessionDir);
|
|
788
|
-
records.push(record);
|
|
789
|
-
await fs.writeFile(indexPath(sessionDir), JSON.stringify(records, null, 2));
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
async function removeCheckpointRecords(sessionDir, ids) {
|
|
793
|
-
if (ids.length === 0) return;
|
|
794
|
-
await enqueueIndexWrite(sessionDir, async () => {
|
|
795
|
-
const drop = new Set(ids);
|
|
796
|
-
const records = await listCheckpointRecords(sessionDir);
|
|
797
|
-
const kept = records.filter((record) => !drop.has(record.id));
|
|
798
|
-
if (kept.length === records.length) return;
|
|
799
|
-
await fs.mkdir(checkpointsDir(sessionDir), { recursive: true, mode: 448 });
|
|
800
|
-
await fs.writeFile(indexPath(sessionDir), JSON.stringify(kept, null, 2));
|
|
801
|
-
for (const id of ids) {
|
|
802
|
-
await fs.rm(checkpointContentPath(sessionDir, id), { force: true });
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
async function loadSessionSnapshots(sessionDir) {
|
|
807
|
-
const records = await listCheckpointRecords(sessionDir);
|
|
808
|
-
return records.map((record) => ({
|
|
809
|
-
...record,
|
|
810
|
-
content: null,
|
|
811
|
-
contentFile: record.existed && !record.contentTooLarge ? checkpointContentPath(sessionDir, record.id) : void 0
|
|
812
|
-
}));
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// src/session/store.ts
|
|
816
621
|
import fs2 from "fs/promises";
|
|
817
622
|
import path3 from "path";
|
|
623
|
+
|
|
624
|
+
// src/session/store.ts
|
|
625
|
+
import { randomUUID } from "crypto";
|
|
626
|
+
import { existsSync } from "fs";
|
|
627
|
+
import fs from "fs/promises";
|
|
628
|
+
import path2 from "path";
|
|
818
629
|
import { StringDecoder } from "string_decoder";
|
|
819
630
|
function generateId(now) {
|
|
820
631
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -822,6 +633,24 @@ function generateId(now) {
|
|
|
822
633
|
const suffix = Math.random().toString(16).slice(2, 8).padEnd(6, "0");
|
|
823
634
|
return `${stamp}-${suffix}`;
|
|
824
635
|
}
|
|
636
|
+
var ID_COLLISION_MAX_ATTEMPTS = 5;
|
|
637
|
+
function generateUniqueId() {
|
|
638
|
+
let id = generateId(/* @__PURE__ */ new Date());
|
|
639
|
+
for (let attempt = 1; existsSync(path2.join(sessionsDir(), id)); attempt++) {
|
|
640
|
+
if (attempt >= ID_COLLISION_MAX_ATTEMPTS) {
|
|
641
|
+
const stamp = id.slice(0, id.lastIndexOf("-"));
|
|
642
|
+
return `${stamp}-${randomUUID().replaceAll("-", "").slice(0, 12)}`;
|
|
643
|
+
}
|
|
644
|
+
id = generateId(/* @__PURE__ */ new Date());
|
|
645
|
+
}
|
|
646
|
+
return id;
|
|
647
|
+
}
|
|
648
|
+
function normalizeCwdForCompare(cwd, platform = process.platform) {
|
|
649
|
+
if (platform !== "win32") return cwd;
|
|
650
|
+
const resolved = path2.win32.resolve(cwd);
|
|
651
|
+
const trimmed = resolved.replace(/[\\/]+$/, "");
|
|
652
|
+
return (trimmed === "" ? resolved : trimmed).toLowerCase();
|
|
653
|
+
}
|
|
825
654
|
function debugWarn(message) {
|
|
826
655
|
if (process.env.STAR_DEBUG === "1") process.stderr.write(`[star-cli] ${message}
|
|
827
656
|
`);
|
|
@@ -861,6 +690,23 @@ function isFallbackMeta(meta) {
|
|
|
861
690
|
}
|
|
862
691
|
var META_FLUSH_INTERVAL_MS = 2e3;
|
|
863
692
|
var MESSAGES_READ_CHUNK_BYTES = 64 * 1024;
|
|
693
|
+
var STAR_META_KEY = "__starMeta";
|
|
694
|
+
function serializeMessage(star) {
|
|
695
|
+
if (!star.meta) return JSON.stringify(star.message);
|
|
696
|
+
return JSON.stringify({ ...star.message, [STAR_META_KEY]: star.meta });
|
|
697
|
+
}
|
|
698
|
+
function parseMessageLine(line) {
|
|
699
|
+
try {
|
|
700
|
+
const parsed = JSON.parse(line);
|
|
701
|
+
if (parsed !== null && typeof parsed === "object" && STAR_META_KEY in parsed) {
|
|
702
|
+
const { [STAR_META_KEY]: meta, ...message } = parsed;
|
|
703
|
+
return { message, meta };
|
|
704
|
+
}
|
|
705
|
+
return { message: parsed };
|
|
706
|
+
} catch {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
864
710
|
function dayKey(date) {
|
|
865
711
|
const pad = (n) => String(n).padStart(2, "0");
|
|
866
712
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
@@ -879,16 +725,16 @@ var SessionStore = class _SessionStore {
|
|
|
879
725
|
metaDirty = false;
|
|
880
726
|
constructor(id) {
|
|
881
727
|
this.id = id;
|
|
882
|
-
this.dir =
|
|
728
|
+
this.dir = path2.join(sessionsDir(), id);
|
|
883
729
|
}
|
|
884
730
|
metaPath() {
|
|
885
|
-
return
|
|
731
|
+
return path2.join(this.dir, "meta.json");
|
|
886
732
|
}
|
|
887
733
|
messagesPath() {
|
|
888
|
-
return
|
|
734
|
+
return path2.join(this.dir, "messages.jsonl");
|
|
889
735
|
}
|
|
890
736
|
static async create(cwd, model) {
|
|
891
|
-
const store = new _SessionStore(
|
|
737
|
+
const store = new _SessionStore(generateUniqueId());
|
|
892
738
|
store.pendingMeta = { cwd, model, createdAt: Date.now() };
|
|
893
739
|
return store;
|
|
894
740
|
}
|
|
@@ -896,7 +742,7 @@ var SessionStore = class _SessionStore {
|
|
|
896
742
|
const store = new _SessionStore(id);
|
|
897
743
|
let raw;
|
|
898
744
|
try {
|
|
899
|
-
raw = await
|
|
745
|
+
raw = await fs.readFile(store.metaPath(), "utf8");
|
|
900
746
|
} catch {
|
|
901
747
|
return null;
|
|
902
748
|
}
|
|
@@ -913,16 +759,19 @@ var SessionStore = class _SessionStore {
|
|
|
913
759
|
static async list(cwd) {
|
|
914
760
|
let entries;
|
|
915
761
|
try {
|
|
916
|
-
entries = await
|
|
762
|
+
entries = await fs.readdir(sessionsDir());
|
|
917
763
|
} catch {
|
|
918
764
|
return [];
|
|
919
765
|
}
|
|
766
|
+
const wanted = cwd === void 0 ? void 0 : normalizeCwdForCompare(cwd);
|
|
920
767
|
const metas = await Promise.all(
|
|
921
768
|
entries.map(async (entry) => {
|
|
922
769
|
try {
|
|
923
|
-
const raw = await
|
|
770
|
+
const raw = await fs.readFile(path2.join(sessionsDir(), entry, "meta.json"), "utf8");
|
|
924
771
|
const meta = JSON.parse(raw);
|
|
925
|
-
if (meta.id === entry && (
|
|
772
|
+
if (meta.id === entry && (wanted === void 0 || normalizeCwdForCompare(meta.cwd) === wanted)) {
|
|
773
|
+
return meta;
|
|
774
|
+
}
|
|
926
775
|
} catch {
|
|
927
776
|
}
|
|
928
777
|
return null;
|
|
@@ -945,11 +794,11 @@ var SessionStore = class _SessionStore {
|
|
|
945
794
|
const run = this.metaWriteQueue.then(async () => {
|
|
946
795
|
try {
|
|
947
796
|
await withTransientFsRetry(async () => {
|
|
948
|
-
await
|
|
949
|
-
await
|
|
797
|
+
await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
|
|
798
|
+
await fs.rename(tmp, this.metaPath());
|
|
950
799
|
});
|
|
951
800
|
} catch (error) {
|
|
952
|
-
await
|
|
801
|
+
await fs.unlink(tmp).catch(() => {
|
|
953
802
|
});
|
|
954
803
|
debugWarn(
|
|
955
804
|
`session ${this.id}: meta write failed (${error.code ?? String(error)}), skipping`
|
|
@@ -1008,7 +857,7 @@ var SessionStore = class _SessionStore {
|
|
|
1008
857
|
this.initialized = true;
|
|
1009
858
|
return;
|
|
1010
859
|
}
|
|
1011
|
-
await
|
|
860
|
+
await fs.mkdir(this.dir, { recursive: true, mode: 448 });
|
|
1012
861
|
await this.sweepTmpFiles();
|
|
1013
862
|
const meta = {
|
|
1014
863
|
id: this.id,
|
|
@@ -1029,22 +878,22 @@ var SessionStore = class _SessionStore {
|
|
|
1029
878
|
async sweepTmpFiles() {
|
|
1030
879
|
let entries;
|
|
1031
880
|
try {
|
|
1032
|
-
entries = await
|
|
881
|
+
entries = await fs.readdir(this.dir);
|
|
1033
882
|
} catch {
|
|
1034
883
|
return;
|
|
1035
884
|
}
|
|
1036
885
|
await Promise.all(
|
|
1037
886
|
entries.filter(
|
|
1038
887
|
(entry) => entry.startsWith("meta.json.tmp-") || entry.startsWith("messages.jsonl.tmp-")
|
|
1039
|
-
).map((entry) =>
|
|
888
|
+
).map((entry) => fs.unlink(path2.join(this.dir, entry)).catch(() => {
|
|
1040
889
|
}))
|
|
1041
890
|
);
|
|
1042
891
|
}
|
|
1043
|
-
async append(message) {
|
|
892
|
+
async append(message, meta) {
|
|
1044
893
|
await this.ensureInitialized();
|
|
1045
894
|
try {
|
|
1046
895
|
await withTransientFsRetry(
|
|
1047
|
-
() =>
|
|
896
|
+
() => fs.appendFile(this.messagesPath(), `${serializeMessage({ message, meta })}
|
|
1048
897
|
`)
|
|
1049
898
|
);
|
|
1050
899
|
} catch (error) {
|
|
@@ -1052,22 +901,22 @@ var SessionStore = class _SessionStore {
|
|
|
1052
901
|
`session ${this.id}: message append failed (${error.code ?? String(error)}), message kept in memory only`
|
|
1053
902
|
);
|
|
1054
903
|
}
|
|
1055
|
-
const
|
|
1056
|
-
|
|
904
|
+
const sessionMeta = await this.meta();
|
|
905
|
+
sessionMeta.updatedAt = Date.now();
|
|
1057
906
|
this.markMetaDirty();
|
|
1058
907
|
}
|
|
1059
908
|
async replaceMessages(messages) {
|
|
1060
909
|
await this.ensureInitialized();
|
|
1061
|
-
const content = messages.map(
|
|
910
|
+
const content = messages.map(serializeMessage).join("\n");
|
|
1062
911
|
const tmp = `${this.messagesPath()}.tmp-${process.pid}-${this.messagesTmpSeq++}`;
|
|
1063
912
|
try {
|
|
1064
913
|
await withTransientFsRetry(async () => {
|
|
1065
|
-
await
|
|
914
|
+
await fs.writeFile(tmp, content ? `${content}
|
|
1066
915
|
` : "");
|
|
1067
|
-
await
|
|
916
|
+
await fs.rename(tmp, this.messagesPath());
|
|
1068
917
|
});
|
|
1069
918
|
} catch (error) {
|
|
1070
|
-
await
|
|
919
|
+
await fs.unlink(tmp).catch(() => {
|
|
1071
920
|
});
|
|
1072
921
|
warn(
|
|
1073
922
|
`session ${this.id}: history rewrite failed (${error.code ?? String(error)}), kept the previous messages.jsonl`
|
|
@@ -1078,9 +927,12 @@ var SessionStore = class _SessionStore {
|
|
|
1078
927
|
await this.flushMetaNow();
|
|
1079
928
|
}
|
|
1080
929
|
async messages() {
|
|
930
|
+
return (await this.starMessages()).map((star) => star.message);
|
|
931
|
+
}
|
|
932
|
+
async starMessages() {
|
|
1081
933
|
let handle = null;
|
|
1082
934
|
try {
|
|
1083
|
-
handle = await
|
|
935
|
+
handle = await fs.open(this.messagesPath(), "r");
|
|
1084
936
|
const messages = [];
|
|
1085
937
|
const decoder = new StringDecoder("utf8");
|
|
1086
938
|
const buffer = Buffer.alloc(MESSAGES_READ_CHUNK_BYTES);
|
|
@@ -1088,9 +940,10 @@ var SessionStore = class _SessionStore {
|
|
|
1088
940
|
const pushLine = (line) => {
|
|
1089
941
|
const trimmed = line.trim();
|
|
1090
942
|
if (!trimmed) return;
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
943
|
+
const parsed = parseMessageLine(trimmed);
|
|
944
|
+
if (parsed) {
|
|
945
|
+
messages.push(parsed);
|
|
946
|
+
} else {
|
|
1094
947
|
debugWarn(`session ${this.id}: skipping corrupt messages.jsonl line`);
|
|
1095
948
|
}
|
|
1096
949
|
};
|
|
@@ -1128,7 +981,7 @@ var SessionStore = class _SessionStore {
|
|
|
1128
981
|
await this.ensureInitialized();
|
|
1129
982
|
if (this.cachedMeta) return this.cachedMeta;
|
|
1130
983
|
try {
|
|
1131
|
-
const raw = await
|
|
984
|
+
const raw = await fs.readFile(this.metaPath(), "utf8");
|
|
1132
985
|
this.cachedMeta = JSON.parse(raw);
|
|
1133
986
|
return this.cachedMeta;
|
|
1134
987
|
} catch {
|
|
@@ -1195,48 +1048,107 @@ var SessionStore = class _SessionStore {
|
|
|
1195
1048
|
}
|
|
1196
1049
|
};
|
|
1197
1050
|
|
|
1198
|
-
// src/session/
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1051
|
+
// src/session/checkpoints.ts
|
|
1052
|
+
function checkpointsDir(sessionDir) {
|
|
1053
|
+
return path3.join(sessionDir, "checkpoints");
|
|
1054
|
+
}
|
|
1055
|
+
function indexPath(sessionDir) {
|
|
1056
|
+
return path3.join(checkpointsDir(sessionDir), "index.json");
|
|
1057
|
+
}
|
|
1058
|
+
var indexWriteQueues = /* @__PURE__ */ new Map();
|
|
1059
|
+
function enqueueIndexWrite(sessionDir, op) {
|
|
1060
|
+
const queued = indexWriteQueues.get(sessionDir) ?? Promise.resolve();
|
|
1061
|
+
const run = queued.then(op);
|
|
1062
|
+
const tail = run.then(
|
|
1063
|
+
() => {
|
|
1064
|
+
},
|
|
1065
|
+
() => {
|
|
1212
1066
|
}
|
|
1213
|
-
|
|
1067
|
+
);
|
|
1068
|
+
indexWriteQueues.set(sessionDir, tail);
|
|
1069
|
+
void tail.then(() => {
|
|
1070
|
+
if (indexWriteQueues.get(sessionDir) === tail) indexWriteQueues.delete(sessionDir);
|
|
1071
|
+
});
|
|
1072
|
+
return run;
|
|
1073
|
+
}
|
|
1074
|
+
var indexTmpSeq = 0;
|
|
1075
|
+
async function writeIndex(sessionDir, records) {
|
|
1076
|
+
const target = indexPath(sessionDir);
|
|
1077
|
+
const tmp = `${target}.tmp-${process.pid}-${indexTmpSeq++}`;
|
|
1078
|
+
try {
|
|
1079
|
+
await withTransientFsRetry(async () => {
|
|
1080
|
+
await fs2.writeFile(tmp, JSON.stringify(records, null, 2));
|
|
1081
|
+
await fs2.rename(tmp, target);
|
|
1082
|
+
});
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
await fs2.unlink(tmp).catch(() => {
|
|
1085
|
+
});
|
|
1086
|
+
throw error;
|
|
1214
1087
|
}
|
|
1215
|
-
|
|
1088
|
+
}
|
|
1089
|
+
function checkpointContentPath(sessionDir, id) {
|
|
1090
|
+
return path3.join(checkpointsDir(sessionDir), `${id}.snapshot`);
|
|
1091
|
+
}
|
|
1092
|
+
async function listCheckpointRecords(sessionDir) {
|
|
1093
|
+
let raw;
|
|
1216
1094
|
try {
|
|
1217
|
-
|
|
1095
|
+
raw = await fs2.readFile(indexPath(sessionDir), "utf8");
|
|
1218
1096
|
} catch {
|
|
1219
|
-
return
|
|
1097
|
+
return [];
|
|
1220
1098
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1099
|
+
try {
|
|
1100
|
+
const parsed = JSON.parse(raw);
|
|
1101
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1102
|
+
} catch {
|
|
1103
|
+
process.stderr.write(
|
|
1104
|
+
`[star-cli] checkpoints index unreadable for session ${path3.basename(sessionDir)}, ignoring it
|
|
1105
|
+
`
|
|
1106
|
+
);
|
|
1107
|
+
return [];
|
|
1226
1108
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1109
|
+
}
|
|
1110
|
+
async function appendCheckpointRecord(sessionDir, record, content) {
|
|
1111
|
+
await enqueueIndexWrite(sessionDir, async () => {
|
|
1112
|
+
await fs2.mkdir(checkpointsDir(sessionDir), { recursive: true, mode: 448 });
|
|
1113
|
+
if (record.existed && content !== null) {
|
|
1114
|
+
await fs2.writeFile(checkpointContentPath(sessionDir, record.id), content, "utf8");
|
|
1115
|
+
}
|
|
1116
|
+
const records = await listCheckpointRecords(sessionDir);
|
|
1117
|
+
records.push(record);
|
|
1118
|
+
await writeIndex(sessionDir, records);
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
async function removeCheckpointRecords(sessionDir, ids) {
|
|
1122
|
+
if (ids.length === 0) return;
|
|
1123
|
+
await enqueueIndexWrite(sessionDir, async () => {
|
|
1124
|
+
const drop = new Set(ids);
|
|
1125
|
+
const records = await listCheckpointRecords(sessionDir);
|
|
1126
|
+
const kept = records.filter((record) => !drop.has(record.id));
|
|
1127
|
+
if (kept.length === records.length) return;
|
|
1128
|
+
await fs2.mkdir(checkpointsDir(sessionDir), { recursive: true, mode: 448 });
|
|
1129
|
+
await writeIndex(sessionDir, kept);
|
|
1130
|
+
for (const id of ids) {
|
|
1131
|
+
await fs2.rm(checkpointContentPath(sessionDir, id), { force: true });
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
async function loadSessionSnapshots(sessionDir) {
|
|
1136
|
+
const records = await listCheckpointRecords(sessionDir);
|
|
1137
|
+
return records.map((record) => ({
|
|
1138
|
+
...record,
|
|
1139
|
+
content: null,
|
|
1140
|
+
contentFile: record.existed && !record.contentTooLarge ? checkpointContentPath(sessionDir, record.id) : void 0
|
|
1141
|
+
}));
|
|
1230
1142
|
}
|
|
1231
1143
|
|
|
1232
1144
|
// src/session/list.ts
|
|
1233
|
-
import
|
|
1234
|
-
import
|
|
1145
|
+
import fs3 from "fs/promises";
|
|
1146
|
+
import path4 from "path";
|
|
1235
1147
|
var COUNT_CHUNK_BYTES = 64 * 1024;
|
|
1236
1148
|
async function countMessages(id) {
|
|
1237
1149
|
let handle = null;
|
|
1238
1150
|
try {
|
|
1239
|
-
handle = await
|
|
1151
|
+
handle = await fs3.open(path4.join(sessionsDir(), id, "messages.jsonl"), "r");
|
|
1240
1152
|
const buffer = Buffer.alloc(COUNT_CHUNK_BYTES);
|
|
1241
1153
|
let count = 0;
|
|
1242
1154
|
let lineHasContent = false;
|
|
@@ -1261,11 +1173,24 @@ async function countMessages(id) {
|
|
|
1261
1173
|
});
|
|
1262
1174
|
}
|
|
1263
1175
|
}
|
|
1176
|
+
var LIST_COUNT_CONCURRENCY = 8;
|
|
1264
1177
|
async function listSessionEntries(cwd) {
|
|
1265
1178
|
const metas = await SessionStore.list(cwd);
|
|
1266
|
-
|
|
1267
|
-
|
|
1179
|
+
const entries = [];
|
|
1180
|
+
let next = 0;
|
|
1181
|
+
const worker = async () => {
|
|
1182
|
+
while (next < metas.length) {
|
|
1183
|
+
const index = next;
|
|
1184
|
+
next += 1;
|
|
1185
|
+
const meta = metas[index];
|
|
1186
|
+
if (!meta) continue;
|
|
1187
|
+
entries[index] = { meta, messageCount: await countMessages(meta.id) };
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
await Promise.all(
|
|
1191
|
+
Array.from({ length: Math.min(LIST_COUNT_CONCURRENCY, metas.length) }, () => worker())
|
|
1268
1192
|
);
|
|
1193
|
+
return entries;
|
|
1269
1194
|
}
|
|
1270
1195
|
async function findLatestSession(cwd) {
|
|
1271
1196
|
const [latest] = await SessionStore.list(cwd);
|
|
@@ -1274,7 +1199,7 @@ async function findLatestSession(cwd) {
|
|
|
1274
1199
|
async function sessionPreview(id, maxBytes = 4096) {
|
|
1275
1200
|
let handle = null;
|
|
1276
1201
|
try {
|
|
1277
|
-
handle = await
|
|
1202
|
+
handle = await fs3.open(path4.join(sessionsDir(), id, "messages.jsonl"), "r");
|
|
1278
1203
|
const buffer = Buffer.alloc(maxBytes);
|
|
1279
1204
|
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
1280
1205
|
const head = buffer.toString("utf8", 0, bytesRead);
|
|
@@ -1332,6 +1257,52 @@ function formatSessionEntries(entries, options = {}) {
|
|
|
1332
1257
|
}).join("\n");
|
|
1333
1258
|
}
|
|
1334
1259
|
|
|
1260
|
+
// src/cli/system-prompt.ts
|
|
1261
|
+
var SYSTEM_PROMPT = `You are Star CLI, an AI coding agent running in the user's terminal.
|
|
1262
|
+
You help with software engineering tasks: reading, writing and editing code, running shell commands, and managing todos.
|
|
1263
|
+
Be concise and direct. Use tools when they help accomplish the task.
|
|
1264
|
+
For any task with two or more steps, start by creating a todo list with todo_write and keep it updated as you progress; only mark an item completed after you have verified its result.
|
|
1265
|
+
The working directory is the user's project root; never touch files outside it without explicit instruction.
|
|
1266
|
+
For conversion, batch-processing, or other scriptable tasks, write a script file into the working directory and run it instead of pasting long inline code into the shell; once the script's output is verified, delete the script unless the user asked to keep it.
|
|
1267
|
+
When a tool call or command fails, read the error output, work out the cause, and try again with a corrected or alternative approach \u2014 never repeat an identical failing call without changing something.
|
|
1268
|
+
Never consider the task finished until you have verified the result yourself: run the tests, the build, or a check command that proves the output is correct, and if verification fails or reveals gaps, keep fixing until it passes.
|
|
1269
|
+
Do not end your turn while the task is still incomplete; keep going until it is done or you are genuinely blocked, and if you are blocked, state exactly what is missing.
|
|
1270
|
+
Never end a reply by announcing what you will do next \u2014 either do it now with tool calls, or do not mention it. Narrating future actions is not progress.`;
|
|
1271
|
+
|
|
1272
|
+
// src/session/clear.ts
|
|
1273
|
+
import fs4 from "fs/promises";
|
|
1274
|
+
import path5 from "path";
|
|
1275
|
+
async function clearSessions(cwd, excludeId) {
|
|
1276
|
+
if (cwd !== void 0) {
|
|
1277
|
+
const metas = await SessionStore.list(cwd);
|
|
1278
|
+
let removed2 = 0;
|
|
1279
|
+
for (const meta of metas) {
|
|
1280
|
+
if (meta.id === excludeId) continue;
|
|
1281
|
+
await fs4.rm(path5.join(sessionsDir(), meta.id), { recursive: true, force: true });
|
|
1282
|
+
removed2 += 1;
|
|
1283
|
+
}
|
|
1284
|
+
if ((await SessionStore.list(cwd)).length === 0) {
|
|
1285
|
+
await removeTreeRepo(cwd);
|
|
1286
|
+
}
|
|
1287
|
+
return removed2;
|
|
1288
|
+
}
|
|
1289
|
+
let entries;
|
|
1290
|
+
try {
|
|
1291
|
+
entries = await fs4.readdir(sessionsDir());
|
|
1292
|
+
} catch {
|
|
1293
|
+
return 0;
|
|
1294
|
+
}
|
|
1295
|
+
let removed = 0;
|
|
1296
|
+
for (const entry of entries) {
|
|
1297
|
+
if (entry === excludeId) continue;
|
|
1298
|
+
await fs4.rm(path5.join(sessionsDir(), entry), { recursive: true, force: true });
|
|
1299
|
+
removed += 1;
|
|
1300
|
+
}
|
|
1301
|
+
const survivors = await SessionStore.list();
|
|
1302
|
+
await pruneTreeReposExcept(survivors.map((meta) => meta.cwd));
|
|
1303
|
+
return removed;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1335
1306
|
// src/version.ts
|
|
1336
1307
|
import fs5 from "fs";
|
|
1337
1308
|
import path6 from "path";
|
|
@@ -1350,61 +1321,14 @@ function readPackageVersion() {
|
|
|
1350
1321
|
}
|
|
1351
1322
|
var VERSION = readPackageVersion();
|
|
1352
1323
|
|
|
1353
|
-
// src/config/env.ts
|
|
1354
|
-
import fs6 from "fs";
|
|
1355
|
-
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1356
|
-
function parseEnvContent(content) {
|
|
1357
|
-
const vars = {};
|
|
1358
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
1359
|
-
const line = rawLine.trim();
|
|
1360
|
-
if (!line || line.startsWith("#")) continue;
|
|
1361
|
-
const eq = line.indexOf("=");
|
|
1362
|
-
if (eq <= 0) continue;
|
|
1363
|
-
const key = line.slice(0, eq).trim();
|
|
1364
|
-
if (!ENV_KEY_PATTERN.test(key)) continue;
|
|
1365
|
-
let value = line.slice(eq + 1).trim();
|
|
1366
|
-
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
1367
|
-
value = value.slice(1, -1);
|
|
1368
|
-
}
|
|
1369
|
-
vars[key] = value;
|
|
1370
|
-
}
|
|
1371
|
-
return vars;
|
|
1372
|
-
}
|
|
1373
|
-
function loadEnvFile(filePath = envFilePath()) {
|
|
1374
|
-
let content;
|
|
1375
|
-
try {
|
|
1376
|
-
content = fs6.readFileSync(filePath, "utf8");
|
|
1377
|
-
} catch {
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
for (const [key, value] of Object.entries(parseEnvContent(content))) {
|
|
1381
|
-
if (process.env[key] === void 0) {
|
|
1382
|
-
process.env[key] = value;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
function upsertEnvContent(content, key, value) {
|
|
1387
|
-
const entry = `${key}=${value.replace(/[\r\n]/g, "")}`;
|
|
1388
|
-
const pattern = new RegExp(`^\\s*${key}\\s*=`);
|
|
1389
|
-
const lines = content.split("\n");
|
|
1390
|
-
while (lines.length > 0 && (lines[lines.length - 1] ?? "").trim() === "") lines.pop();
|
|
1391
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1392
|
-
if (pattern.test(lines[i] ?? "")) {
|
|
1393
|
-
lines[i] = entry;
|
|
1394
|
-
return `${lines.join("\n")}
|
|
1395
|
-
`;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
lines.push(entry);
|
|
1399
|
-
return `${lines.join("\n")}
|
|
1400
|
-
`;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
1324
|
export {
|
|
1404
1325
|
formatDollars,
|
|
1405
1326
|
formatTokens,
|
|
1406
1327
|
computeCostUsd,
|
|
1407
1328
|
estimateCost,
|
|
1329
|
+
toolIcon,
|
|
1330
|
+
thinkingIcon,
|
|
1331
|
+
folderIcon,
|
|
1408
1332
|
renderMarkdown,
|
|
1409
1333
|
toTerminalSafe,
|
|
1410
1334
|
summarizeArgs,
|
|
@@ -1413,22 +1337,13 @@ export {
|
|
|
1413
1337
|
formatElapsedSeconds,
|
|
1414
1338
|
splitCommittableLines,
|
|
1415
1339
|
formatStreamError,
|
|
1416
|
-
coreMessageText,
|
|
1417
1340
|
buildDisplayMessages,
|
|
1418
1341
|
MAX_IMAGE_BYTES,
|
|
1419
1342
|
imageMimeType,
|
|
1420
1343
|
readImageInput,
|
|
1421
1344
|
resolveMentions,
|
|
1422
|
-
SYSTEM_PROMPT,
|
|
1423
|
-
loadEnvFile,
|
|
1424
|
-
upsertEnvContent,
|
|
1425
|
-
listModels,
|
|
1426
|
-
resolveModelConfig,
|
|
1427
|
-
createModel,
|
|
1428
|
-
reasoningEffortMetadata,
|
|
1429
1345
|
loadSessionSnapshots,
|
|
1430
1346
|
SessionStore,
|
|
1431
|
-
clearSessions,
|
|
1432
1347
|
listSessionEntries,
|
|
1433
1348
|
findLatestSession,
|
|
1434
1349
|
sessionPreview,
|
|
@@ -1436,6 +1351,8 @@ export {
|
|
|
1436
1351
|
resolveSessionId,
|
|
1437
1352
|
relativeTime,
|
|
1438
1353
|
formatSessionEntries,
|
|
1354
|
+
SYSTEM_PROMPT,
|
|
1355
|
+
clearSessions,
|
|
1439
1356
|
VERSION
|
|
1440
1357
|
};
|
|
1441
|
-
//# sourceMappingURL=chunk-
|
|
1358
|
+
//# sourceMappingURL=chunk-VJFWTXWN.js.map
|