@cryer/star-cli 0.3.8 → 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 +16 -2
- package/README.zh-CN.md +15 -2
- package/dist/chunk-4IRY44TG.js +34 -0
- package/dist/chunk-4IRY44TG.js.map +1 -0
- package/dist/chunk-A5PXPWHB.js +2014 -0
- package/dist/chunk-A5PXPWHB.js.map +1 -0
- package/dist/chunk-IJGUW4PM.js +504 -0
- package/dist/chunk-IJGUW4PM.js.map +1 -0
- package/dist/chunk-MNVCCOI2.js +3510 -0
- package/dist/chunk-MNVCCOI2.js.map +1 -0
- package/dist/chunk-MVAMPVTA.js +232 -0
- package/dist/chunk-MVAMPVTA.js.map +1 -0
- package/dist/{chunk-IYWYSFSF.js → chunk-PDFC7QVW.js} +339 -218
- package/dist/chunk-PDFC7QVW.js.map +1 -0
- package/dist/chunk-URC7JXKX.js +1414 -0
- package/dist/chunk-URC7JXKX.js.map +1 -0
- package/dist/loop-TW2FHL7J.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-YA23CK5R.js → repl-GBVIW5QR.js} +435 -262
- package/dist/repl-GBVIW5QR.js.map +1 -0
- package/dist/tools-7EBGCYYE.js +18 -0
- package/dist/tools-7EBGCYYE.js.map +1 -0
- package/package.json +18 -26
- package/dist/chunk-IYWYSFSF.js.map +0 -1
- package/dist/chunk-OHLA2DP7.js +0 -5849
- package/dist/chunk-OHLA2DP7.js.map +0 -1
- package/dist/loop-2MOFLXTG.js +0 -12
- package/dist/repl-YA23CK5R.js.map +0 -1
- /package/dist/{loop-2MOFLXTG.js.map → loop-TW2FHL7J.js.map} +0 -0
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
buildDisplayMessages,
|
|
7
7
|
clearSessions,
|
|
8
8
|
computeCostUsd,
|
|
9
|
-
coreMessageText,
|
|
10
9
|
estimateCost,
|
|
10
|
+
folderIcon,
|
|
11
11
|
formatByteCount,
|
|
12
12
|
formatDollars,
|
|
13
13
|
formatElapsedSeconds,
|
|
@@ -25,65 +25,81 @@ import {
|
|
|
25
25
|
shortSessionId,
|
|
26
26
|
splitCommittableLines,
|
|
27
27
|
summarizeArgs,
|
|
28
|
+
thinkingIcon,
|
|
28
29
|
toTerminalSafe,
|
|
29
|
-
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
toolIcon
|
|
31
|
+
} from "./chunk-PDFC7QVW.js";
|
|
31
32
|
import {
|
|
32
33
|
AgentLoop,
|
|
33
|
-
MAX_IMAGE_DIMENSION,
|
|
34
|
-
SKIP_DIRS,
|
|
35
|
-
TodoStore,
|
|
36
34
|
WORKING_DIFF_MAX_LINES,
|
|
37
35
|
appendUserMemory,
|
|
38
|
-
bashTool,
|
|
39
36
|
buildAllowRule,
|
|
40
37
|
buildCommitPrompt,
|
|
41
|
-
clearSnapshots,
|
|
42
38
|
collectCommitContext,
|
|
43
39
|
collectWorkingDiff,
|
|
44
40
|
compactMessages,
|
|
41
|
+
discoverSkills,
|
|
42
|
+
estimateTokens,
|
|
43
|
+
getGitSummaryCached,
|
|
44
|
+
isAllowedByRules,
|
|
45
|
+
isDangerousCommand,
|
|
46
|
+
isGitRepo,
|
|
47
|
+
summarizeMessages
|
|
48
|
+
} from "./chunk-MNVCCOI2.js";
|
|
49
|
+
import {
|
|
45
50
|
contextWindowTokens,
|
|
51
|
+
coreMessageText,
|
|
52
|
+
diffTreeNames,
|
|
53
|
+
reconcileStarMessages,
|
|
54
|
+
toCoreMessages
|
|
55
|
+
} from "./chunk-IJGUW4PM.js";
|
|
56
|
+
import {
|
|
57
|
+
createModel,
|
|
58
|
+
reasoningEffortMetadata
|
|
59
|
+
} from "./chunk-MVAMPVTA.js";
|
|
60
|
+
import {
|
|
61
|
+
listModels,
|
|
62
|
+
resolveModelConfig
|
|
63
|
+
} from "./chunk-4IRY44TG.js";
|
|
64
|
+
import {
|
|
46
65
|
createDefaultRegistry,
|
|
66
|
+
formatTaskFinished,
|
|
67
|
+
formatTaskList,
|
|
68
|
+
formatTaskStarted
|
|
69
|
+
} from "./chunk-URC7JXKX.js";
|
|
70
|
+
import {
|
|
71
|
+
MAX_IMAGE_DIMENSION,
|
|
72
|
+
SKIP_DIRS,
|
|
73
|
+
TodoStore,
|
|
74
|
+
bashTool,
|
|
75
|
+
clearSnapshots,
|
|
47
76
|
createIgnorePredicate,
|
|
48
|
-
createModel,
|
|
49
77
|
defaultAgentTasks,
|
|
50
78
|
defaultTaskManager,
|
|
51
|
-
diffTreeNames,
|
|
52
|
-
discoverSkills,
|
|
53
79
|
downsampleImageIfNeeded,
|
|
54
80
|
envFilePath,
|
|
55
|
-
estimateTokens,
|
|
56
81
|
formatAgentTaskUpdate,
|
|
57
|
-
formatTaskFinished,
|
|
58
|
-
formatTaskList,
|
|
59
|
-
formatTaskStarted,
|
|
60
82
|
formatTodos,
|
|
61
|
-
getGitSummaryCached,
|
|
62
83
|
globalConfigPath,
|
|
63
84
|
hydrateSnapshots,
|
|
64
|
-
isAllowedByRules,
|
|
65
|
-
isDangerousCommand,
|
|
66
|
-
isGitRepo,
|
|
67
|
-
listModels,
|
|
68
85
|
listSnapshots,
|
|
69
86
|
listTurnSnapshots,
|
|
70
87
|
loadTodos,
|
|
71
88
|
parseTodoArgs,
|
|
72
|
-
reasoningEffortMetadata,
|
|
73
|
-
reconcileToolCalls,
|
|
74
89
|
resetTodos,
|
|
75
|
-
resolveModelConfig,
|
|
76
90
|
resolveShell,
|
|
77
91
|
resolveSnapshotContent,
|
|
78
92
|
rewindToSnapshot,
|
|
93
|
+
sessionsDir,
|
|
94
|
+
setTodoSession,
|
|
79
95
|
starHome,
|
|
80
|
-
|
|
96
|
+
upsertEnvContent,
|
|
81
97
|
userMemoryPath
|
|
82
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-A5PXPWHB.js";
|
|
83
99
|
|
|
84
100
|
// src/cli/repl.tsx
|
|
85
101
|
import { Box as Box13, Text as Text14, render, useApp } from "ink";
|
|
86
|
-
import { useCallback, useEffect as
|
|
102
|
+
import { useCallback, useEffect as useEffect4, useMemo, useRef as useRef6, useState as useState7 } from "react";
|
|
87
103
|
|
|
88
104
|
// src/config/save.ts
|
|
89
105
|
import fs from "fs";
|
|
@@ -99,6 +115,10 @@ async function writeFileAtomic(filePath, content) {
|
|
|
99
115
|
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
100
116
|
await fs.promises.writeFile(tmpPath, content, "utf8");
|
|
101
117
|
await fs.promises.rename(tmpPath, filePath);
|
|
118
|
+
try {
|
|
119
|
+
await fs.promises.chmod(filePath, 384);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
102
122
|
}
|
|
103
123
|
function upsertPermissionRuleText(content, kind, rules, hadPermissionsTable) {
|
|
104
124
|
const newLine = `${kind} = [${rules.map(tomlString).join(", ")}]`;
|
|
@@ -265,8 +285,8 @@ async function saveReasoningEffort(modelName, effort) {
|
|
|
265
285
|
async function resumeSession(id) {
|
|
266
286
|
const store = await SessionStore.open(id);
|
|
267
287
|
if (!store) return null;
|
|
268
|
-
const [meta, loaded] = await Promise.all([store.meta(), store.
|
|
269
|
-
const messages =
|
|
288
|
+
const [meta, loaded] = await Promise.all([store.meta(), store.starMessages()]);
|
|
289
|
+
const messages = reconcileStarMessages(loaded);
|
|
270
290
|
if (messages.length !== loaded.length) {
|
|
271
291
|
await store.replaceMessages(messages);
|
|
272
292
|
}
|
|
@@ -609,14 +629,14 @@ async function compactSession(opts) {
|
|
|
609
629
|
if (!(backend instanceof AgentLoop)) {
|
|
610
630
|
return { message: "Current backend does not support compaction.", compacted: false };
|
|
611
631
|
}
|
|
612
|
-
const messages = [...backend.
|
|
632
|
+
const messages = [...backend.getStarMessages()];
|
|
613
633
|
if (messages.length < MIN_COMPACT_MESSAGES) {
|
|
614
634
|
return {
|
|
615
635
|
message: `Nothing to compact: history has only ${messages.length} message(s).`,
|
|
616
636
|
compacted: false
|
|
617
637
|
};
|
|
618
638
|
}
|
|
619
|
-
const beforeTokens = estimateTokens(messages);
|
|
639
|
+
const beforeTokens = estimateTokens(toCoreMessages(messages));
|
|
620
640
|
const compacted = compactMessages(messages, config.contextMaxTokens, { force: true });
|
|
621
641
|
if (!compacted.compacted) {
|
|
622
642
|
return {
|
|
@@ -629,33 +649,44 @@ async function compactSession(opts) {
|
|
|
629
649
|
compacted,
|
|
630
650
|
config,
|
|
631
651
|
backend.getAuxModel(),
|
|
632
|
-
backend.getAuxTemperature()
|
|
652
|
+
backend.getAuxTemperature(),
|
|
653
|
+
opts.signal
|
|
633
654
|
);
|
|
655
|
+
if (next === null || opts.signal?.aborted) {
|
|
656
|
+
return { message: "Compaction cancelled \u2014 history left unchanged.", compacted: false };
|
|
657
|
+
}
|
|
634
658
|
await backend.loadMessages(next);
|
|
635
659
|
await sessionStore?.replaceMessages(next);
|
|
636
|
-
const
|
|
660
|
+
const coreNext = toCoreMessages(next);
|
|
661
|
+
const afterTokens = estimateTokens(coreNext);
|
|
637
662
|
return {
|
|
638
663
|
message: `Compacted context: ${messages.length} -> ${next.length} messages (~${formatTokens(beforeTokens)} -> ~${formatTokens(afterTokens)} estimated tokens).`,
|
|
639
664
|
compacted: true,
|
|
640
|
-
messages:
|
|
665
|
+
messages: coreNext
|
|
641
666
|
};
|
|
642
667
|
}
|
|
643
|
-
async function applyCompactionSummary(original, compacted, config, model, temperature) {
|
|
668
|
+
async function applyCompactionSummary(original, compacted, config, model, temperature, signal) {
|
|
644
669
|
if (config.contextCompaction !== "summary") {
|
|
645
670
|
return compacted.messages;
|
|
646
671
|
}
|
|
647
|
-
const headCount = compacted.messages[0]?.role === "system" ? 1 : 0;
|
|
648
|
-
const dropped = original.slice(headCount, headCount + compacted.droppedCount);
|
|
672
|
+
const headCount = compacted.messages[0]?.message.role === "system" ? 1 : 0;
|
|
673
|
+
const dropped = toCoreMessages(original.slice(headCount, headCount + compacted.droppedCount));
|
|
649
674
|
try {
|
|
650
|
-
const summary = await summarizeMessages(dropped, model,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
675
|
+
const summary = await summarizeMessages(dropped, model, signal, temperature);
|
|
676
|
+
if (summary.trim().length > 0) {
|
|
677
|
+
const messages = compacted.messages.slice();
|
|
678
|
+
messages[headCount] = {
|
|
679
|
+
message: {
|
|
680
|
+
role: "user",
|
|
681
|
+
content: `[earlier conversation summarized]
|
|
655
682
|
${summary}`
|
|
656
|
-
|
|
657
|
-
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
return messages;
|
|
686
|
+
}
|
|
687
|
+
return compacted.messages;
|
|
658
688
|
} catch {
|
|
689
|
+
if (signal?.aborted) return null;
|
|
659
690
|
return compacted.messages;
|
|
660
691
|
}
|
|
661
692
|
}
|
|
@@ -741,7 +772,11 @@ function stringifyPart(value) {
|
|
|
741
772
|
import { readFileSync } from "fs";
|
|
742
773
|
|
|
743
774
|
// src/session/search.ts
|
|
775
|
+
import fs3 from "fs/promises";
|
|
776
|
+
import path4 from "path";
|
|
777
|
+
import { StringDecoder } from "string_decoder";
|
|
744
778
|
var SNIPPET_LENGTH = 100;
|
|
779
|
+
var SEARCH_READ_CHUNK_BYTES = 64 * 1024;
|
|
745
780
|
function flattenMessage(message) {
|
|
746
781
|
const content = message.content;
|
|
747
782
|
if (typeof content === "string") return content;
|
|
@@ -768,17 +803,113 @@ function makeSnippet(text, query) {
|
|
|
768
803
|
start = Math.max(0, end - SNIPPET_LENGTH);
|
|
769
804
|
return `${start > 0 ? "\u2026" : ""}${flat.slice(start, end)}${end < flat.length ? "\u2026" : ""}`;
|
|
770
805
|
}
|
|
806
|
+
async function streamSessionPieces(id, onPiece) {
|
|
807
|
+
let handle = null;
|
|
808
|
+
try {
|
|
809
|
+
handle = await fs3.open(path4.join(sessionsDir(), id, "messages.jsonl"), "r");
|
|
810
|
+
const decoder = new StringDecoder("utf8");
|
|
811
|
+
const buffer = Buffer.alloc(SEARCH_READ_CHUNK_BYTES);
|
|
812
|
+
let tail = "";
|
|
813
|
+
let stop = false;
|
|
814
|
+
const pushLine = (line) => {
|
|
815
|
+
if (stop) return;
|
|
816
|
+
const trimmed = line.trim();
|
|
817
|
+
if (!trimmed) return;
|
|
818
|
+
let message;
|
|
819
|
+
try {
|
|
820
|
+
message = JSON.parse(trimmed);
|
|
821
|
+
} catch {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const piece = flattenMessage(message);
|
|
825
|
+
if (piece !== "") stop = onPiece(piece);
|
|
826
|
+
};
|
|
827
|
+
while (!stop) {
|
|
828
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
829
|
+
if (bytesRead === 0) break;
|
|
830
|
+
const chunk = tail + decoder.write(buffer.subarray(0, bytesRead));
|
|
831
|
+
const lines = chunk.split("\n");
|
|
832
|
+
tail = lines.pop() ?? "";
|
|
833
|
+
for (const line of lines) pushLine(line);
|
|
834
|
+
}
|
|
835
|
+
if (!stop) pushLine(tail + decoder.end());
|
|
836
|
+
} catch {
|
|
837
|
+
} finally {
|
|
838
|
+
await handle?.close().catch(() => {
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async function sessionTextContains(id, needle) {
|
|
843
|
+
let lowerTail = "";
|
|
844
|
+
let found = false;
|
|
845
|
+
let joined = false;
|
|
846
|
+
await streamSessionPieces(id, (piece) => {
|
|
847
|
+
const segment = joined ? `
|
|
848
|
+
${piece}` : piece;
|
|
849
|
+
joined = true;
|
|
850
|
+
const haystack = lowerTail + segment.toLowerCase();
|
|
851
|
+
lowerTail = needle.length > 1 ? haystack.slice(1 - needle.length) : "";
|
|
852
|
+
if (haystack.includes(needle)) {
|
|
853
|
+
found = true;
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
return false;
|
|
857
|
+
});
|
|
858
|
+
return found;
|
|
859
|
+
}
|
|
860
|
+
async function buildSessionSnippet(id, needle, query) {
|
|
861
|
+
const parts = [];
|
|
862
|
+
let lowerTail = "";
|
|
863
|
+
let mode = needle === "" ? "window" : "scan";
|
|
864
|
+
let flatLength = 0;
|
|
865
|
+
let flatLastChar = "";
|
|
866
|
+
let matchEndFlat = 0;
|
|
867
|
+
let stoppedEarly = false;
|
|
868
|
+
const foldCollapsed = (segment) => {
|
|
869
|
+
let folded = (flatLastChar + segment).replace(/\s+/g, " ");
|
|
870
|
+
if (flatLength === 0) {
|
|
871
|
+
if (folded.startsWith(" ")) folded = folded.slice(1);
|
|
872
|
+
} else {
|
|
873
|
+
folded = folded.slice(1);
|
|
874
|
+
}
|
|
875
|
+
flatLength += folded.length;
|
|
876
|
+
if (folded.length > 0) flatLastChar = folded.at(-1) ?? "";
|
|
877
|
+
};
|
|
878
|
+
await streamSessionPieces(id, (piece) => {
|
|
879
|
+
const segment = parts.length === 0 ? piece : `
|
|
880
|
+
${piece}`;
|
|
881
|
+
parts.push(piece);
|
|
882
|
+
if (mode === "scan") {
|
|
883
|
+
const haystack = lowerTail + segment.toLowerCase();
|
|
884
|
+
lowerTail = needle.length > 1 ? haystack.slice(1 - needle.length) : "";
|
|
885
|
+
if (!haystack.includes(needle)) return false;
|
|
886
|
+
const collapsed = parts.join("\n").replace(/\s+/g, " ").replace(/^ /, "");
|
|
887
|
+
const index = collapsed.toLowerCase().indexOf(needle);
|
|
888
|
+
if (index === -1) {
|
|
889
|
+
mode = "head";
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
mode = "window";
|
|
893
|
+
matchEndFlat = index + needle.length;
|
|
894
|
+
flatLength = collapsed.length;
|
|
895
|
+
flatLastChar = collapsed.at(-1) ?? "";
|
|
896
|
+
} else if (mode === "window") {
|
|
897
|
+
foldCollapsed(segment);
|
|
898
|
+
}
|
|
899
|
+
const stop = mode === "window" && flatLength > matchEndFlat + SNIPPET_LENGTH;
|
|
900
|
+
if (stop) stoppedEarly = true;
|
|
901
|
+
return stop;
|
|
902
|
+
});
|
|
903
|
+
const text = parts.join("\n");
|
|
904
|
+
return makeSnippet(stoppedEarly ? `${text}\0` : text, query);
|
|
905
|
+
}
|
|
771
906
|
async function searchSessions(query, limit = 10) {
|
|
772
907
|
const hits = [];
|
|
773
908
|
const needle = query.toLowerCase();
|
|
774
909
|
for (const meta of await SessionStore.list()) {
|
|
775
910
|
if (hits.length >= limit) break;
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const text = (await store.messages()).map(flattenMessage).filter((part) => part.length > 0).join("\n");
|
|
779
|
-
if (text.toLowerCase().includes(needle)) {
|
|
780
|
-
hits.push({ meta, snippet: makeSnippet(text, query) });
|
|
781
|
-
}
|
|
911
|
+
if (needle !== "" && !await sessionTextContains(meta.id, needle)) continue;
|
|
912
|
+
hits.push({ meta, snippet: await buildSessionSnippet(meta.id, needle, query) });
|
|
782
913
|
}
|
|
783
914
|
return hits;
|
|
784
915
|
}
|
|
@@ -794,7 +925,7 @@ function formatSearchResults(hits, query) {
|
|
|
794
925
|
|
|
795
926
|
// src/cli/diff-preview.ts
|
|
796
927
|
import { readFile as readFile2 } from "fs/promises";
|
|
797
|
-
import
|
|
928
|
+
import path5 from "path";
|
|
798
929
|
var CONTEXT_LINES = 2;
|
|
799
930
|
var MAX_HUNK_LINES = 15;
|
|
800
931
|
var MAX_TOTAL_LINES = 12;
|
|
@@ -955,7 +1086,7 @@ async function generateDiffPreview(toolName, args, cwd) {
|
|
|
955
1086
|
if (toolName !== "edit_file" && toolName !== "write_file") return null;
|
|
956
1087
|
const filePath = getStringArg(args, "path");
|
|
957
1088
|
if (filePath === void 0) return null;
|
|
958
|
-
const absolute =
|
|
1089
|
+
const absolute = path5.resolve(cwd, filePath);
|
|
959
1090
|
let existing;
|
|
960
1091
|
try {
|
|
961
1092
|
existing = await readFile2(absolute, "utf8");
|
|
@@ -1027,6 +1158,7 @@ ${sections.join("\n\n")}`);
|
|
|
1027
1158
|
description: "Delete stored sessions: this directory by default, every session with --all (the current session is kept)",
|
|
1028
1159
|
usage: "/clear-sessions [--all]",
|
|
1029
1160
|
category: "Sessions",
|
|
1161
|
+
argHints: ["--all"],
|
|
1030
1162
|
async run(args, ctx) {
|
|
1031
1163
|
const arg = args.trim();
|
|
1032
1164
|
if (arg !== "" && arg !== "--all") {
|
|
@@ -1180,6 +1312,7 @@ ${sections.join("\n\n")}`);
|
|
|
1180
1312
|
description: "Set the permission mode (opens a picker when no mode is given)",
|
|
1181
1313
|
usage: "/permission [ask|auto|readonly|yolo]",
|
|
1182
1314
|
category: "Settings",
|
|
1315
|
+
argHints: ["ask", "auto", "readonly", "yolo", "plan"],
|
|
1183
1316
|
async run(args, ctx) {
|
|
1184
1317
|
const arg = args.trim();
|
|
1185
1318
|
if (!arg) {
|
|
@@ -1203,6 +1336,7 @@ ${sections.join("\n\n")}`);
|
|
|
1203
1336
|
description: "Show user memory (MEMORY.md), or add an entry",
|
|
1204
1337
|
usage: "/memory [add <text>]",
|
|
1205
1338
|
category: "Settings",
|
|
1339
|
+
argHints: ["add"],
|
|
1206
1340
|
run(args, ctx) {
|
|
1207
1341
|
const arg = args.trim();
|
|
1208
1342
|
if (arg === "add" || arg.startsWith("add ")) {
|
|
@@ -1282,6 +1416,7 @@ ${content}`);
|
|
|
1282
1416
|
description: "Generate an AGENTS.md for the current project",
|
|
1283
1417
|
usage: "/init [force]",
|
|
1284
1418
|
category: "Settings",
|
|
1419
|
+
argHints: ["force"],
|
|
1285
1420
|
async run(args, ctx) {
|
|
1286
1421
|
ctx.addSystemMessage(await ctx.initProject(args));
|
|
1287
1422
|
}
|
|
@@ -1341,6 +1476,7 @@ ${lines.join("\n")}`);
|
|
|
1341
1476
|
description: "Copy the last assistant reply to the clipboard (`all` for the whole conversation)",
|
|
1342
1477
|
usage: "/copy [all]",
|
|
1343
1478
|
category: "Sessions",
|
|
1479
|
+
argHints: ["all"],
|
|
1344
1480
|
async run(args, ctx) {
|
|
1345
1481
|
const arg = args.trim();
|
|
1346
1482
|
if (arg !== "" && arg !== "all") {
|
|
@@ -1406,8 +1542,8 @@ ${result.diff}${note ? `
|
|
|
1406
1542
|
|
|
1407
1543
|
// src/cli/commands/connect.ts
|
|
1408
1544
|
import { spawn } from "child_process";
|
|
1409
|
-
import
|
|
1410
|
-
import
|
|
1545
|
+
import fs4 from "fs";
|
|
1546
|
+
import path6 from "path";
|
|
1411
1547
|
var PROVIDER_PRESETS = [
|
|
1412
1548
|
{
|
|
1413
1549
|
key: "openai",
|
|
@@ -1547,14 +1683,14 @@ function saveConnection(answers, configPath = globalConfigPath(), envPath = envF
|
|
|
1547
1683
|
promptPrice: 0,
|
|
1548
1684
|
completionPrice: 0
|
|
1549
1685
|
};
|
|
1550
|
-
|
|
1686
|
+
fs4.mkdirSync(path6.dirname(envPath), { recursive: true });
|
|
1551
1687
|
let envContent = "";
|
|
1552
1688
|
try {
|
|
1553
|
-
envContent =
|
|
1689
|
+
envContent = fs4.readFileSync(envPath, "utf8");
|
|
1554
1690
|
} catch {
|
|
1555
1691
|
envContent = "";
|
|
1556
1692
|
}
|
|
1557
|
-
|
|
1693
|
+
fs4.writeFileSync(
|
|
1558
1694
|
envPath,
|
|
1559
1695
|
upsertEnvContent(envContent, provider.apiKeyEnv ?? "", answers.apiKey),
|
|
1560
1696
|
{
|
|
@@ -1563,21 +1699,21 @@ function saveConnection(answers, configPath = globalConfigPath(), envPath = envF
|
|
|
1563
1699
|
}
|
|
1564
1700
|
);
|
|
1565
1701
|
try {
|
|
1566
|
-
|
|
1702
|
+
fs4.chmodSync(envPath, 384);
|
|
1567
1703
|
} catch {
|
|
1568
1704
|
}
|
|
1569
1705
|
if (provider.apiKeyEnv) {
|
|
1570
1706
|
process.env[provider.apiKeyEnv] = answers.apiKey;
|
|
1571
1707
|
}
|
|
1572
|
-
|
|
1708
|
+
fs4.mkdirSync(path6.dirname(configPath), { recursive: true });
|
|
1573
1709
|
let existing = "";
|
|
1574
1710
|
try {
|
|
1575
|
-
existing =
|
|
1711
|
+
existing = fs4.readFileSync(configPath, "utf8");
|
|
1576
1712
|
} catch {
|
|
1577
1713
|
existing = "";
|
|
1578
1714
|
}
|
|
1579
1715
|
const separator = existing === "" ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
1580
|
-
|
|
1716
|
+
fs4.writeFileSync(
|
|
1581
1717
|
configPath,
|
|
1582
1718
|
existing + separator + buildProviderTomlBlock(provider) + buildModelTomlBlock(model),
|
|
1583
1719
|
"utf8"
|
|
@@ -1602,12 +1738,12 @@ ${content}`;
|
|
|
1602
1738
|
function saveDefaultModel(modelName, configPath = globalConfigPath()) {
|
|
1603
1739
|
let content = "";
|
|
1604
1740
|
try {
|
|
1605
|
-
content =
|
|
1741
|
+
content = fs4.readFileSync(configPath, "utf8");
|
|
1606
1742
|
} catch {
|
|
1607
1743
|
content = "";
|
|
1608
1744
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1745
|
+
fs4.mkdirSync(path6.dirname(configPath), { recursive: true });
|
|
1746
|
+
fs4.writeFileSync(configPath, setDefaultModelInToml(content, modelName), "utf8");
|
|
1611
1747
|
}
|
|
1612
1748
|
function openConfigFile(filePath) {
|
|
1613
1749
|
const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", filePath]] : process.platform === "darwin" ? ["open", [filePath]] : ["xdg-open", [filePath]];
|
|
@@ -1622,7 +1758,7 @@ function openConfigFile(filePath) {
|
|
|
1622
1758
|
|
|
1623
1759
|
// src/cli/commands/custom.ts
|
|
1624
1760
|
import { readFileSync as readFileSync2, readdirSync } from "fs";
|
|
1625
|
-
import
|
|
1761
|
+
import path7 from "path";
|
|
1626
1762
|
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
1627
1763
|
var DESCRIPTION_PATTERN = /^<!--\s*description:\s*(.+?)\s*-->\s*$/;
|
|
1628
1764
|
var DEFAULT_DESCRIPTION = "Custom prompt command";
|
|
@@ -1657,14 +1793,14 @@ function scanDir(dir, into) {
|
|
|
1657
1793
|
if (!entry.endsWith(".md")) continue;
|
|
1658
1794
|
const name = entry.slice(0, -".md".length);
|
|
1659
1795
|
if (!NAME_PATTERN.test(name)) continue;
|
|
1660
|
-
const parsed = parseCommandFile(
|
|
1796
|
+
const parsed = parseCommandFile(path7.join(dir, entry), name);
|
|
1661
1797
|
if (parsed) into.set(name, parsed);
|
|
1662
1798
|
}
|
|
1663
1799
|
}
|
|
1664
1800
|
function loadCustomCommands(cwd, home = starHome()) {
|
|
1665
1801
|
const byName = /* @__PURE__ */ new Map();
|
|
1666
|
-
scanDir(
|
|
1667
|
-
scanDir(
|
|
1802
|
+
scanDir(path7.join(home, "commands"), byName);
|
|
1803
|
+
scanDir(path7.join(cwd, ".star", "commands"), byName);
|
|
1668
1804
|
return [...byName.values()];
|
|
1669
1805
|
}
|
|
1670
1806
|
function registerCustomCommands(registry, cwd, home) {
|
|
@@ -1690,8 +1826,8 @@ function registerCustomCommands(registry, cwd, home) {
|
|
|
1690
1826
|
}
|
|
1691
1827
|
|
|
1692
1828
|
// src/cli/commands/doctor.ts
|
|
1693
|
-
import
|
|
1694
|
-
import
|
|
1829
|
+
import fs5 from "fs/promises";
|
|
1830
|
+
import path8 from "path";
|
|
1695
1831
|
var MIN_NODE_MAJOR = 20;
|
|
1696
1832
|
var REGISTRY_URL = "https://registry.npmjs.org/";
|
|
1697
1833
|
var REGISTRY_TIMEOUT_MS = 5e3;
|
|
@@ -1705,7 +1841,7 @@ function nodeMajor(version) {
|
|
|
1705
1841
|
}
|
|
1706
1842
|
async function pathExists(target) {
|
|
1707
1843
|
try {
|
|
1708
|
-
await
|
|
1844
|
+
await fs5.access(target);
|
|
1709
1845
|
return true;
|
|
1710
1846
|
} catch {
|
|
1711
1847
|
return false;
|
|
@@ -1743,7 +1879,7 @@ async function runDoctor(opts) {
|
|
|
1743
1879
|
detail: `${spec.label} (${spec.shell})`
|
|
1744
1880
|
});
|
|
1745
1881
|
}
|
|
1746
|
-
const configPath =
|
|
1882
|
+
const configPath = path8.join(home, "config.toml");
|
|
1747
1883
|
const configExists = await pathExists(configPath);
|
|
1748
1884
|
checks.push({
|
|
1749
1885
|
name: "STAR_HOME / config",
|
|
@@ -1780,14 +1916,14 @@ async function runDoctor(opts) {
|
|
|
1780
1916
|
});
|
|
1781
1917
|
}
|
|
1782
1918
|
}
|
|
1783
|
-
const sessions =
|
|
1919
|
+
const sessions = path8.join(home, "sessions");
|
|
1784
1920
|
let writable = false;
|
|
1785
1921
|
let writeError = "";
|
|
1786
1922
|
try {
|
|
1787
|
-
await
|
|
1788
|
-
const probe =
|
|
1789
|
-
await
|
|
1790
|
-
await
|
|
1923
|
+
await fs5.mkdir(sessions, { recursive: true });
|
|
1924
|
+
const probe = path8.join(sessions, `.doctor-probe-${process.pid}`);
|
|
1925
|
+
await fs5.writeFile(probe, "ok");
|
|
1926
|
+
await fs5.unlink(probe);
|
|
1791
1927
|
writable = true;
|
|
1792
1928
|
} catch (error) {
|
|
1793
1929
|
writeError = error instanceof Error ? error.message : String(error);
|
|
@@ -1822,15 +1958,15 @@ function formatDoctorReport(report) {
|
|
|
1822
1958
|
}
|
|
1823
1959
|
|
|
1824
1960
|
// src/cli/commands/init-project.ts
|
|
1825
|
-
import
|
|
1826
|
-
import
|
|
1961
|
+
import fs6 from "fs/promises";
|
|
1962
|
+
import path9 from "path";
|
|
1827
1963
|
import { generateText } from "ai";
|
|
1828
1964
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", "coverage"]);
|
|
1829
1965
|
var MAX_ENTRIES = 20;
|
|
1830
1966
|
var README_HEAD_LINES = 5;
|
|
1831
1967
|
async function fileExists2(target) {
|
|
1832
1968
|
try {
|
|
1833
|
-
await
|
|
1969
|
+
await fs6.access(target);
|
|
1834
1970
|
return true;
|
|
1835
1971
|
} catch {
|
|
1836
1972
|
return false;
|
|
@@ -1838,7 +1974,7 @@ async function fileExists2(target) {
|
|
|
1838
1974
|
}
|
|
1839
1975
|
async function readPackageJson(cwd) {
|
|
1840
1976
|
try {
|
|
1841
|
-
const raw = await
|
|
1977
|
+
const raw = await fs6.readFile(path9.join(cwd, "package.json"), "utf8");
|
|
1842
1978
|
const parsed = JSON.parse(raw);
|
|
1843
1979
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1844
1980
|
return parsed;
|
|
@@ -1855,10 +1991,10 @@ function stringRecord(value) {
|
|
|
1855
1991
|
return out;
|
|
1856
1992
|
}
|
|
1857
1993
|
async function detectPackageManager(cwd) {
|
|
1858
|
-
if (await fileExists2(
|
|
1859
|
-
if (await fileExists2(
|
|
1860
|
-
if (await fileExists2(
|
|
1861
|
-
if (await fileExists2(
|
|
1994
|
+
if (await fileExists2(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1995
|
+
if (await fileExists2(path9.join(cwd, "yarn.lock"))) return "yarn";
|
|
1996
|
+
if (await fileExists2(path9.join(cwd, "package-lock.json"))) return "npm";
|
|
1997
|
+
if (await fileExists2(path9.join(cwd, "bun.lockb"))) return "bun";
|
|
1862
1998
|
return null;
|
|
1863
1999
|
}
|
|
1864
2000
|
function inferCommands(pm, scripts) {
|
|
@@ -1873,7 +2009,7 @@ function inferCommands(pm, scripts) {
|
|
|
1873
2009
|
}
|
|
1874
2010
|
async function listEntries(cwd) {
|
|
1875
2011
|
try {
|
|
1876
|
-
const dirents = await
|
|
2012
|
+
const dirents = await fs6.readdir(cwd, { withFileTypes: true });
|
|
1877
2013
|
const entries = [];
|
|
1878
2014
|
for (const dirent of dirents) {
|
|
1879
2015
|
if (dirent.isDirectory()) {
|
|
@@ -1892,7 +2028,7 @@ async function listEntries(cwd) {
|
|
|
1892
2028
|
async function readReadmeHead(cwd) {
|
|
1893
2029
|
for (const name of ["README.md", "readme.md", "README"]) {
|
|
1894
2030
|
try {
|
|
1895
|
-
const raw = await
|
|
2031
|
+
const raw = await fs6.readFile(path9.join(cwd, name), "utf8");
|
|
1896
2032
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).slice(0, README_HEAD_LINES);
|
|
1897
2033
|
} catch {
|
|
1898
2034
|
}
|
|
@@ -1905,14 +2041,14 @@ async function scanProject(cwd) {
|
|
|
1905
2041
|
const scripts = stringRecord(pkg?.scripts);
|
|
1906
2042
|
return {
|
|
1907
2043
|
name: typeof pkg?.name === "string" ? pkg.name : null,
|
|
1908
|
-
dirName:
|
|
2044
|
+
dirName: path9.basename(cwd),
|
|
1909
2045
|
packageManager,
|
|
1910
2046
|
scripts,
|
|
1911
2047
|
dependencies: Object.keys(stringRecord(pkg?.dependencies)),
|
|
1912
2048
|
devDependencies: Object.keys(stringRecord(pkg?.devDependencies)),
|
|
1913
2049
|
commands: inferCommands(packageManager, scripts),
|
|
1914
|
-
hasTsconfig: await fileExists2(
|
|
1915
|
-
isGitRepo: await fileExists2(
|
|
2050
|
+
hasTsconfig: await fileExists2(path9.join(cwd, "tsconfig.json")),
|
|
2051
|
+
isGitRepo: await fileExists2(path9.join(cwd, ".git")),
|
|
1916
2052
|
readmeHead: await readReadmeHead(cwd),
|
|
1917
2053
|
entries: await listEntries(cwd)
|
|
1918
2054
|
};
|
|
@@ -1979,7 +2115,7 @@ Write the AGENTS.md.`
|
|
|
1979
2115
|
}
|
|
1980
2116
|
async function initProject(opts) {
|
|
1981
2117
|
const { cwd, args, model } = opts;
|
|
1982
|
-
const target =
|
|
2118
|
+
const target = path9.join(cwd, "AGENTS.md");
|
|
1983
2119
|
const force = args.split(/\s+/).includes("force");
|
|
1984
2120
|
if (!force && await fileExists2(target)) {
|
|
1985
2121
|
return {
|
|
@@ -2013,7 +2149,7 @@ async function initProject(opts) {
|
|
|
2013
2149
|
content = renderAgentsMd(facts);
|
|
2014
2150
|
note = " No model available; used the template \u2014 review and refine manually.";
|
|
2015
2151
|
}
|
|
2016
|
-
await
|
|
2152
|
+
await fs6.writeFile(target, content);
|
|
2017
2153
|
const lines = content.split("\n").length;
|
|
2018
2154
|
const how = usedLlm ? "generated with model refinement" : "generated from template";
|
|
2019
2155
|
return {
|
|
@@ -2069,7 +2205,7 @@ function parseSlashCommand(input) {
|
|
|
2069
2205
|
}
|
|
2070
2206
|
|
|
2071
2207
|
// src/cli/commands/rewind.ts
|
|
2072
|
-
import
|
|
2208
|
+
import path10 from "path";
|
|
2073
2209
|
function planRewind(snapshots, id) {
|
|
2074
2210
|
const target = snapshots.find((snapshot) => snapshot.id === id);
|
|
2075
2211
|
if (!target) return null;
|
|
@@ -2083,7 +2219,7 @@ function planRewind(snapshots, id) {
|
|
|
2083
2219
|
return { target, affected, messageIndex };
|
|
2084
2220
|
}
|
|
2085
2221
|
function displayPath(filePath, cwd) {
|
|
2086
|
-
const relative =
|
|
2222
|
+
const relative = path10.relative(cwd, filePath);
|
|
2087
2223
|
return relative !== "" && !relative.startsWith("..") ? relative : filePath;
|
|
2088
2224
|
}
|
|
2089
2225
|
function formatTime(timestamp) {
|
|
@@ -2132,12 +2268,27 @@ function didYouMeanSuffix(names) {
|
|
|
2132
2268
|
const listed = names.slice(0, 3).map((name) => `/${name}`);
|
|
2133
2269
|
return ` Did you mean: ${listed.join(", ")}?`;
|
|
2134
2270
|
}
|
|
2271
|
+
function filterArgHints(input, commands) {
|
|
2272
|
+
if (!input.startsWith("/")) return [];
|
|
2273
|
+
const spaceIndex = input.search(/\s/);
|
|
2274
|
+
if (spaceIndex === -1) return [];
|
|
2275
|
+
const name = input.slice(1, spaceIndex).toLowerCase();
|
|
2276
|
+
const command = commands.find((cmd) => cmd.name.toLowerCase() === name);
|
|
2277
|
+
if (!command?.argHints) return [];
|
|
2278
|
+
const tokenMatch = /\S+$/.exec(input.slice(spaceIndex));
|
|
2279
|
+
const argPrefix = tokenMatch ? tokenMatch[0] : "";
|
|
2280
|
+
const source = command.argHints;
|
|
2281
|
+
const candidates = typeof source === "function" ? source(argPrefix) : source;
|
|
2282
|
+
const prefix = argPrefix.toLowerCase();
|
|
2283
|
+
const stem = input.slice(0, input.length - argPrefix.length);
|
|
2284
|
+
return candidates.filter((candidate) => candidate.toLowerCase().startsWith(prefix)).slice(0, MAX_SUGGESTIONS).map((value) => ({ value, replacement: `${stem}${value} ` }));
|
|
2285
|
+
}
|
|
2135
2286
|
|
|
2136
2287
|
// src/cli/commands/undo.ts
|
|
2137
2288
|
import { readFile as readFile3 } from "fs/promises";
|
|
2138
|
-
import
|
|
2289
|
+
import path11 from "path";
|
|
2139
2290
|
function displayPath2(filePath, cwd) {
|
|
2140
|
-
const relative =
|
|
2291
|
+
const relative = path11.relative(cwd, filePath);
|
|
2141
2292
|
return relative !== "" && !relative.startsWith("..") ? relative : filePath;
|
|
2142
2293
|
}
|
|
2143
2294
|
async function buildUndoDiffs(snapshots, cwd) {
|
|
@@ -2524,11 +2675,11 @@ import { useRef as useRef4, useState as useState3 } from "react";
|
|
|
2524
2675
|
|
|
2525
2676
|
// src/cli/components/InputBox.tsx
|
|
2526
2677
|
import { Box as Box2, Text as Text2, useStdout } from "ink";
|
|
2527
|
-
import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
2678
|
+
import { memo, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
2528
2679
|
|
|
2529
2680
|
// src/cli/path-suggest.ts
|
|
2530
2681
|
import { readdir } from "fs/promises";
|
|
2531
|
-
import
|
|
2682
|
+
import path12 from "path";
|
|
2532
2683
|
var MAX_SCAN_ENTRIES = 2e3;
|
|
2533
2684
|
function extractAtToken(value, cursor) {
|
|
2534
2685
|
const before = value.slice(0, cursor);
|
|
@@ -2543,7 +2694,7 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
|
|
|
2543
2694
|
const dirPart = slash >= 0 ? normalized.slice(0, slash + 1) : "";
|
|
2544
2695
|
const basePrefix = slash >= 0 ? normalized.slice(slash + 1) : normalized;
|
|
2545
2696
|
if (dirPart.split("/").some((segment) => SKIP_DIRS.has(segment))) return [];
|
|
2546
|
-
const dirAbs =
|
|
2697
|
+
const dirAbs = path12.resolve(cwd, dirPart === "" ? "." : dirPart);
|
|
2547
2698
|
const entries = await readdir(dirAbs, { withFileTypes: true }).catch(() => null);
|
|
2548
2699
|
if (!entries) return [];
|
|
2549
2700
|
const ignore = createIgnorePredicate(cwd);
|
|
@@ -2558,7 +2709,7 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
|
|
|
2558
2709
|
if (isDir && SKIP_DIRS.has(entry.name)) continue;
|
|
2559
2710
|
if (entry.name.startsWith(".") && !basePrefix.startsWith(".")) continue;
|
|
2560
2711
|
if (!entry.name.toLowerCase().startsWith(lower)) continue;
|
|
2561
|
-
if (ignore?.(
|
|
2712
|
+
if (ignore?.(path12.join(dirAbs, entry.name), isDir)) continue;
|
|
2562
2713
|
matches.push({ path: `${dirPart}${entry.name}${isDir ? "/" : ""}`, isDir });
|
|
2563
2714
|
}
|
|
2564
2715
|
matches.sort((a, b) => a.path.localeCompare(b.path));
|
|
@@ -2656,7 +2807,7 @@ function pasteToken(id, lineCount) {
|
|
|
2656
2807
|
function stylePasteTokens(segment) {
|
|
2657
2808
|
return segment.replace(PASTE_TOKEN_RE, (match) => `\x1B[2m${match}\x1B[22m`);
|
|
2658
2809
|
}
|
|
2659
|
-
|
|
2810
|
+
var InputBox = memo(function InputBox2({
|
|
2660
2811
|
isStreaming,
|
|
2661
2812
|
disabled,
|
|
2662
2813
|
commands,
|
|
@@ -2789,6 +2940,7 @@ function InputBox({
|
|
|
2789
2940
|
});
|
|
2790
2941
|
const suggestionsEnabled = !isStreaming && !disabled && !suggestionsDismissed && !search;
|
|
2791
2942
|
const suggestions = suggestionsEnabled && commands ? filterCommands(value, commands) : [];
|
|
2943
|
+
const argHints = suggestionsEnabled && commands && cursor === value.length ? filterArgHints(value, commands) : [];
|
|
2792
2944
|
useEffect2(() => {
|
|
2793
2945
|
if (!suggestionsEnabled || !cwd) {
|
|
2794
2946
|
setPathSuggestions([]);
|
|
@@ -2807,8 +2959,8 @@ function InputBox({
|
|
|
2807
2959
|
cancelled = true;
|
|
2808
2960
|
};
|
|
2809
2961
|
}, [value, cursor, cwd, suggestionsEnabled]);
|
|
2810
|
-
const showPathSuggestions = suggestions.length === 0 && pathSuggestions.length > 0;
|
|
2811
|
-
const suggestionCount = suggestions.length > 0 ? suggestions.length : pathSuggestions.length;
|
|
2962
|
+
const showPathSuggestions = suggestions.length === 0 && argHints.length === 0 && pathSuggestions.length > 0;
|
|
2963
|
+
const suggestionCount = suggestions.length > 0 ? suggestions.length : argHints.length > 0 ? argHints.length : pathSuggestions.length;
|
|
2812
2964
|
const activeIndex = suggestionCount === 0 ? 0 : Math.min(highlight, suggestionCount - 1);
|
|
2813
2965
|
const completeHighlighted = () => {
|
|
2814
2966
|
if (suggestions.length > 0) {
|
|
@@ -2818,6 +2970,12 @@ function InputBox({
|
|
|
2818
2970
|
edit(text, text.length);
|
|
2819
2971
|
return;
|
|
2820
2972
|
}
|
|
2973
|
+
if (argHints.length > 0) {
|
|
2974
|
+
const hint = argHints[activeIndex];
|
|
2975
|
+
if (!hint) return;
|
|
2976
|
+
edit(hint.replacement, hint.replacement.length);
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2821
2979
|
const sugg = pathSuggestions[activeIndex];
|
|
2822
2980
|
const token = extractAtToken(value, cursor);
|
|
2823
2981
|
if (!sugg || !token) return;
|
|
@@ -3070,6 +3228,9 @@ ${value.slice(cursor)}`, cursor + 1);
|
|
|
3070
3228
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` - ${cmd.description}` })
|
|
3071
3229
|
] }, cmd.name);
|
|
3072
3230
|
}) }),
|
|
3231
|
+
argHints.length > 0 && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: argHints.map(
|
|
3232
|
+
(hint, index) => index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: hint.value }, hint.value) : /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: hint.value }, hint.value)
|
|
3233
|
+
) }),
|
|
3073
3234
|
showPathSuggestions && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: pathSuggestions.map(
|
|
3074
3235
|
(sugg, index) => index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: `@${sugg.path}` }, sugg.path) : /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
3075
3236
|
/* @__PURE__ */ jsx2(Text2, { color: "cyan", children: `@${sugg.path}` }),
|
|
@@ -3077,7 +3238,7 @@ ${value.slice(cursor)}`, cursor + 1);
|
|
|
3077
3238
|
] }, sugg.path)
|
|
3078
3239
|
) })
|
|
3079
3240
|
] });
|
|
3080
|
-
}
|
|
3241
|
+
});
|
|
3081
3242
|
|
|
3082
3243
|
// src/cli/components/TextField.tsx
|
|
3083
3244
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -3374,7 +3535,7 @@ function ExitPlanPrompt({ onDecision }) {
|
|
|
3374
3535
|
|
|
3375
3536
|
// src/cli/components/MessageList.tsx
|
|
3376
3537
|
import { Box as Box6, Static, Text as Text7, useStdout as useStdout2 } from "ink";
|
|
3377
|
-
import { memo } from "react";
|
|
3538
|
+
import { memo as memo2, useState as useState5 } from "react";
|
|
3378
3539
|
|
|
3379
3540
|
// src/cli/components/DiffLines.tsx
|
|
3380
3541
|
import { Text as Text6 } from "ink";
|
|
@@ -3409,10 +3570,28 @@ var roleStyles = {
|
|
|
3409
3570
|
system: { label: "\u25CF system", color: "yellow" }
|
|
3410
3571
|
};
|
|
3411
3572
|
var interruptedMarker = (text) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: text.length > 0 ? " [interrupted]" : "[interrupted]" });
|
|
3412
|
-
var
|
|
3573
|
+
var INITIAL_RENDER_LIMIT = 100;
|
|
3574
|
+
function collapseInitialMessages(messages, limit = INITIAL_RENDER_LIMIT) {
|
|
3575
|
+
if (messages.length <= limit) return { hidden: 0, visible: [...messages] };
|
|
3576
|
+
return { hidden: messages.length - limit, visible: messages.slice(-limit) };
|
|
3577
|
+
}
|
|
3578
|
+
var MessageList = memo2(function MessageList2({ messages }) {
|
|
3413
3579
|
const { stdout } = useStdout2();
|
|
3414
3580
|
const termWidth = stdout.columns ?? 80;
|
|
3415
|
-
|
|
3581
|
+
const [initial] = useState5(() => ({
|
|
3582
|
+
length: messages.length,
|
|
3583
|
+
view: collapseInitialMessages(messages)
|
|
3584
|
+
}));
|
|
3585
|
+
const placeholder = initial.view.hidden > 0 ? [{ id: -1, role: "system", text: "", collapsed: initial.view.hidden }] : [];
|
|
3586
|
+
const items = [...placeholder, ...initial.view.visible, ...messages.slice(initial.length)];
|
|
3587
|
+
return /* @__PURE__ */ jsx7(Static, { items, children: (message) => {
|
|
3588
|
+
if (message.collapsed !== void 0) {
|
|
3589
|
+
return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
3590
|
+
"\u2026 ",
|
|
3591
|
+
message.collapsed,
|
|
3592
|
+
" earlier messages hidden \u2014 full history in the session file"
|
|
3593
|
+
] }) }, message.id);
|
|
3594
|
+
}
|
|
3416
3595
|
const marginBottom = message.tight ? 0 : 1;
|
|
3417
3596
|
if (message.role === "assistant-cont") {
|
|
3418
3597
|
return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom, children: /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
|
|
@@ -3437,7 +3616,7 @@ var MessageList = memo(function MessageList2({ messages }) {
|
|
|
3437
3616
|
}
|
|
3438
3617
|
if (message.role === "tool") {
|
|
3439
3618
|
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginBottom, children: [
|
|
3440
|
-
/* @__PURE__ */ jsx7(Text7, { color: "magenta", children: message.text }),
|
|
3619
|
+
/* @__PURE__ */ jsx7(Text7, { color: message.dim ? void 0 : "magenta", dimColor: message.dim, children: message.text }),
|
|
3441
3620
|
message.diff && /* @__PURE__ */ jsx7(DiffLines, { lines: message.diff })
|
|
3442
3621
|
] }, message.id);
|
|
3443
3622
|
}
|
|
@@ -3527,73 +3706,7 @@ function RewindConfirmPrompt({
|
|
|
3527
3706
|
|
|
3528
3707
|
// src/cli/components/StatusBar.tsx
|
|
3529
3708
|
import { Box as Box9, Text as Text10, useStdout as useStdout3 } from "ink";
|
|
3530
|
-
import { memo as
|
|
3531
|
-
|
|
3532
|
-
// src/cli/icons.ts
|
|
3533
|
-
var EMOJI_TERM_PROGRAMS = /* @__PURE__ */ new Set([
|
|
3534
|
-
"vscode",
|
|
3535
|
-
"WezTerm",
|
|
3536
|
-
"iTerm.app",
|
|
3537
|
-
"Apple_Terminal",
|
|
3538
|
-
"ghostty",
|
|
3539
|
-
"kitty",
|
|
3540
|
-
"Hyper",
|
|
3541
|
-
"WarpTerminal"
|
|
3542
|
-
]);
|
|
3543
|
-
function resolveIconMode(env = process.env) {
|
|
3544
|
-
const override = env.STAR_ICONS;
|
|
3545
|
-
if (override === "emoji" || override === "plain") return override;
|
|
3546
|
-
if (env.WT_SESSION) return "emoji";
|
|
3547
|
-
if (EMOJI_TERM_PROGRAMS.has(env.TERM_PROGRAM ?? "")) return "emoji";
|
|
3548
|
-
const term = env.TERM ?? "";
|
|
3549
|
-
if (term.includes("kitty") || term.includes("alacritty")) return "emoji";
|
|
3550
|
-
return "plain";
|
|
3551
|
-
}
|
|
3552
|
-
var EMOJI_TOOL_ICONS = {
|
|
3553
|
-
bash: "\u{1F4BB}",
|
|
3554
|
-
read_file: "\u{1F4C4}",
|
|
3555
|
-
write_file: "\u{1F4DD}",
|
|
3556
|
-
edit_file: "\u270F\uFE0F",
|
|
3557
|
-
glob: "\u{1F4C2}",
|
|
3558
|
-
grep: "\u{1F50D}",
|
|
3559
|
-
web_search: "\u{1F50E}",
|
|
3560
|
-
web_fetch: "\u{1F310}",
|
|
3561
|
-
todo_write: "\u{1F4CB}",
|
|
3562
|
-
todo_read: "\u{1F4CB}",
|
|
3563
|
-
task_list: "\u{1F9F5}",
|
|
3564
|
-
task_output: "\u{1F9F5}",
|
|
3565
|
-
task_kill: "\u{1F6D1}",
|
|
3566
|
-
subagent: "\u{1F916}",
|
|
3567
|
-
skill: "\u2728",
|
|
3568
|
-
remember: "\u{1F9E0}"
|
|
3569
|
-
};
|
|
3570
|
-
var PLAIN_TOOL_ICONS = {
|
|
3571
|
-
bash: "$",
|
|
3572
|
-
read_file: "\u2261",
|
|
3573
|
-
write_file: "+",
|
|
3574
|
-
edit_file: "\u270E",
|
|
3575
|
-
glob: "*",
|
|
3576
|
-
grep: "/",
|
|
3577
|
-
web_search: "?",
|
|
3578
|
-
web_fetch: "\u21D3",
|
|
3579
|
-
todo_write: "\u2023",
|
|
3580
|
-
todo_read: "\u2023",
|
|
3581
|
-
task_list: "\xB6",
|
|
3582
|
-
task_output: "\xB6",
|
|
3583
|
-
task_kill: "\xD7",
|
|
3584
|
-
subagent: "\u25C9",
|
|
3585
|
-
skill: "\u2727",
|
|
3586
|
-
remember: "\u203B"
|
|
3587
|
-
};
|
|
3588
|
-
var iconMode = resolveIconMode();
|
|
3589
|
-
function toolIcon(name) {
|
|
3590
|
-
const map = iconMode === "emoji" ? EMOJI_TOOL_ICONS : PLAIN_TOOL_ICONS;
|
|
3591
|
-
return map[name] ?? (iconMode === "emoji" ? "\u{1F527}" : "\u25B8");
|
|
3592
|
-
}
|
|
3593
|
-
var thinkingIcon = iconMode === "emoji" ? "\u{1F4AD}" : "\u2727";
|
|
3594
|
-
var folderIcon = iconMode === "emoji" ? "\u{1F4C1} " : "";
|
|
3595
|
-
|
|
3596
|
-
// src/cli/components/StatusBar.tsx
|
|
3709
|
+
import { memo as memo3 } from "react";
|
|
3597
3710
|
import { jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3598
3711
|
var BG_LABEL_MAX = 40;
|
|
3599
3712
|
var WIDE_COLUMNS = 120;
|
|
@@ -3602,7 +3715,7 @@ function cwdLabel(cwd, wide) {
|
|
|
3602
3715
|
const base = cwd.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
|
|
3603
3716
|
return base && base.length > 0 ? base : cwd;
|
|
3604
3717
|
}
|
|
3605
|
-
var StatusBar =
|
|
3718
|
+
var StatusBar = memo3(function StatusBar2({
|
|
3606
3719
|
cwd,
|
|
3607
3720
|
model,
|
|
3608
3721
|
permissionMode,
|
|
@@ -3660,9 +3773,9 @@ var StatusBar = memo2(function StatusBar2({
|
|
|
3660
3773
|
|
|
3661
3774
|
// src/cli/components/StreamingMessage.tsx
|
|
3662
3775
|
import { Box as Box10, Text as Text11, useStdout as useStdout4 } from "ink";
|
|
3663
|
-
import { memo as
|
|
3776
|
+
import { memo as memo4 } from "react";
|
|
3664
3777
|
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3665
|
-
var StreamingMessage =
|
|
3778
|
+
var StreamingMessage = memo4(function StreamingMessage2({
|
|
3666
3779
|
text,
|
|
3667
3780
|
continuation
|
|
3668
3781
|
}) {
|
|
@@ -3675,6 +3788,24 @@ var StreamingMessage = memo3(function StreamingMessage2({
|
|
|
3675
3788
|
|
|
3676
3789
|
// src/cli/components/ThinkingIndicator.tsx
|
|
3677
3790
|
import { Box as Box11, Text as Text12 } from "ink";
|
|
3791
|
+
import { memo as memo5, useEffect as useEffect3, useState as useState6 } from "react";
|
|
3792
|
+
|
|
3793
|
+
// src/cli/ticker.ts
|
|
3794
|
+
var TICK_MS = 100;
|
|
3795
|
+
function nextFlush(prev, next) {
|
|
3796
|
+
if (prev.streamed === next.streamed && prev.reasoning === next.reasoning) return null;
|
|
3797
|
+
return next;
|
|
3798
|
+
}
|
|
3799
|
+
function startTicker(onTick, intervalMs = TICK_MS) {
|
|
3800
|
+
let tick = 0;
|
|
3801
|
+
const timer = setInterval(() => {
|
|
3802
|
+
tick += 1;
|
|
3803
|
+
onTick(tick);
|
|
3804
|
+
}, intervalMs);
|
|
3805
|
+
return () => clearInterval(timer);
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
// src/cli/components/ThinkingIndicator.tsx
|
|
3678
3809
|
import { jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3679
3810
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3680
3811
|
var REASONING_TAIL_LENGTH = 200;
|
|
@@ -3684,15 +3815,18 @@ function truncateTail(text, max) {
|
|
|
3684
3815
|
if (flat.length <= max) return flat;
|
|
3685
3816
|
return `\u2026${flat.slice(flat.length - max)}`;
|
|
3686
3817
|
}
|
|
3687
|
-
|
|
3818
|
+
var ThinkingIndicator = memo5(function ThinkingIndicator2({
|
|
3688
3819
|
reasoning,
|
|
3689
|
-
frame
|
|
3820
|
+
frame,
|
|
3690
3821
|
activity,
|
|
3691
|
-
|
|
3822
|
+
startedAt
|
|
3692
3823
|
}) {
|
|
3824
|
+
const [tick, setTick] = useState6(0);
|
|
3825
|
+
useEffect3(() => startTicker((t) => setTick(t)), []);
|
|
3693
3826
|
const tail = reasoning ? truncateTail(reasoning, REASONING_TAIL_LENGTH) : "";
|
|
3694
|
-
const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
3827
|
+
const spinner = SPINNER_FRAMES[(frame ?? tick) % SPINNER_FRAMES.length];
|
|
3695
3828
|
const label = activity ?? `${thinkingIcon} star is thinking\u2026`;
|
|
3829
|
+
const elapsedSec = startedAt === void 0 ? void 0 : Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
|
|
3696
3830
|
const elapsed = elapsedSec !== void 0 && elapsedSec >= 3 ? ` (${formatElapsedSeconds(elapsedSec)})` : "";
|
|
3697
3831
|
return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
|
|
3698
3832
|
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
@@ -3706,10 +3840,11 @@ function ThinkingIndicator({
|
|
|
3706
3840
|
tail
|
|
3707
3841
|
] })
|
|
3708
3842
|
] });
|
|
3709
|
-
}
|
|
3843
|
+
});
|
|
3710
3844
|
|
|
3711
3845
|
// src/cli/components/TodoPanel.tsx
|
|
3712
3846
|
import { Box as Box12, Text as Text13 } from "ink";
|
|
3847
|
+
import { memo as memo6 } from "react";
|
|
3713
3848
|
import { jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3714
3849
|
var SYMBOLS = {
|
|
3715
3850
|
pending: "\u25CB",
|
|
@@ -3728,7 +3863,7 @@ function visibleTodoWindow(todos, max = MAX_VISIBLE_TODOS) {
|
|
|
3728
3863
|
hiddenAfter: todos.length - start - items.length
|
|
3729
3864
|
};
|
|
3730
3865
|
}
|
|
3731
|
-
|
|
3866
|
+
var TodoPanel = memo6(function TodoPanel2({ todos }) {
|
|
3732
3867
|
if (todos.length === 0 || todos.every((t) => t.status === "done")) return null;
|
|
3733
3868
|
const { items, hiddenBefore, hiddenAfter } = visibleTodoWindow(todos);
|
|
3734
3869
|
return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
|
|
@@ -3757,7 +3892,7 @@ function TodoPanel({ todos }) {
|
|
|
3757
3892
|
" more"
|
|
3758
3893
|
] })
|
|
3759
3894
|
] });
|
|
3760
|
-
}
|
|
3895
|
+
});
|
|
3761
3896
|
|
|
3762
3897
|
// src/cli/components/ToolCallCard.tsx
|
|
3763
3898
|
function formatToolCard(card) {
|
|
@@ -3775,20 +3910,20 @@ function isDoubleEscape(previous, now, windowMs = DOUBLE_ESC_WINDOW_MS) {
|
|
|
3775
3910
|
}
|
|
3776
3911
|
|
|
3777
3912
|
// src/cli/history.ts
|
|
3778
|
-
import
|
|
3779
|
-
import
|
|
3913
|
+
import fs7 from "fs";
|
|
3914
|
+
import path13 from "path";
|
|
3780
3915
|
var HISTORY_LIMIT = 50;
|
|
3781
3916
|
var JUNK_ENTRY_PATTERN = /^(.)\1{19,}$/;
|
|
3782
3917
|
function historyPath() {
|
|
3783
|
-
return
|
|
3918
|
+
return path13.join(starHome(), "history");
|
|
3784
3919
|
}
|
|
3785
3920
|
function readLines(file) {
|
|
3786
|
-
const raw =
|
|
3921
|
+
const raw = fs7.readFileSync(file, "utf8");
|
|
3787
3922
|
return raw.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.trim().length > 0);
|
|
3788
3923
|
}
|
|
3789
3924
|
function writeHistory(file, lines) {
|
|
3790
|
-
|
|
3791
|
-
|
|
3925
|
+
fs7.mkdirSync(path13.dirname(file), { recursive: true });
|
|
3926
|
+
fs7.writeFileSync(file, `${lines.join("\n")}
|
|
3792
3927
|
`);
|
|
3793
3928
|
}
|
|
3794
3929
|
function isJunkHistoryEntry(line) {
|
|
@@ -3825,8 +3960,8 @@ function appendHistory(entry) {
|
|
|
3825
3960
|
writeHistory(file, [...existing.slice(-(HISTORY_LIMIT - 1)), line]);
|
|
3826
3961
|
return;
|
|
3827
3962
|
}
|
|
3828
|
-
|
|
3829
|
-
|
|
3963
|
+
fs7.mkdirSync(path13.dirname(file), { recursive: true });
|
|
3964
|
+
fs7.appendFileSync(file, `${line}
|
|
3830
3965
|
`);
|
|
3831
3966
|
} catch {
|
|
3832
3967
|
}
|
|
@@ -3900,27 +4035,38 @@ async function executeShellBang(raw, cwd, signal) {
|
|
|
3900
4035
|
};
|
|
3901
4036
|
}
|
|
3902
4037
|
|
|
3903
|
-
// src/cli/ticker.ts
|
|
3904
|
-
var TICK_MS = 100;
|
|
3905
|
-
function nextFlush(prev, next) {
|
|
3906
|
-
if (prev.streamed === next.streamed && prev.reasoning === next.reasoning) return null;
|
|
3907
|
-
return next;
|
|
3908
|
-
}
|
|
3909
|
-
function startTicker(onTick, intervalMs = TICK_MS) {
|
|
3910
|
-
let tick = 0;
|
|
3911
|
-
const timer = setInterval(() => {
|
|
3912
|
-
tick += 1;
|
|
3913
|
-
onTick(tick);
|
|
3914
|
-
}, intervalMs);
|
|
3915
|
-
return () => clearInterval(timer);
|
|
3916
|
-
}
|
|
3917
|
-
|
|
3918
4038
|
// src/cli/update-check.ts
|
|
4039
|
+
import { mkdir, readFile as readFile4, rename, writeFile } from "fs/promises";
|
|
4040
|
+
import path14 from "path";
|
|
3919
4041
|
var REGISTRY_URL2 = "https://registry.npmjs.org/@cryer%2fstar-cli/latest";
|
|
3920
4042
|
var TIMEOUT_MS = 5e3;
|
|
4043
|
+
var THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
3921
4044
|
async function defaultFetcher2(url) {
|
|
3922
4045
|
return fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
3923
4046
|
}
|
|
4047
|
+
function defaultStatePath() {
|
|
4048
|
+
return path14.join(starHome(), "update-check.json");
|
|
4049
|
+
}
|
|
4050
|
+
async function readState(statePath) {
|
|
4051
|
+
try {
|
|
4052
|
+
const raw = JSON.parse(await readFile4(statePath, "utf8"));
|
|
4053
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
4054
|
+
const { lastCheckAt, latest } = raw;
|
|
4055
|
+
if (typeof lastCheckAt !== "number" || !Number.isFinite(lastCheckAt)) return null;
|
|
4056
|
+
return { lastCheckAt, latest: typeof latest === "string" ? latest : null };
|
|
4057
|
+
} catch {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
async function writeState(statePath, state) {
|
|
4062
|
+
try {
|
|
4063
|
+
await mkdir(path14.dirname(statePath), { recursive: true });
|
|
4064
|
+
const tmp = `${statePath}.tmp-${process.pid}`;
|
|
4065
|
+
await writeFile(tmp, JSON.stringify(state), "utf8");
|
|
4066
|
+
await rename(tmp, statePath);
|
|
4067
|
+
} catch {
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
3924
4070
|
function isNewerVersion(latest, current) {
|
|
3925
4071
|
const parse2 = (v) => v.split(".").slice(0, 3).map((part) => Number.parseInt(part, 10));
|
|
3926
4072
|
const a = parse2(latest);
|
|
@@ -3933,14 +4079,25 @@ function isNewerVersion(latest, current) {
|
|
|
3933
4079
|
}
|
|
3934
4080
|
return false;
|
|
3935
4081
|
}
|
|
3936
|
-
|
|
4082
|
+
function noticeFor(latest, currentVersion) {
|
|
4083
|
+
if (typeof latest !== "string" || !isNewerVersion(latest, currentVersion)) return null;
|
|
4084
|
+
return `New version available: ${currentVersion} -> ${latest} \u2014 run: npm i -g @cryer/star-cli`;
|
|
4085
|
+
}
|
|
4086
|
+
async function checkForUpdate(currentVersion, fetcher = defaultFetcher2, statePath = defaultStatePath()) {
|
|
3937
4087
|
try {
|
|
4088
|
+
const state = await readState(statePath);
|
|
4089
|
+
if (state && Date.now() - state.lastCheckAt < THROTTLE_MS) {
|
|
4090
|
+
return noticeFor(state.latest, currentVersion);
|
|
4091
|
+
}
|
|
3938
4092
|
const res = await fetcher(REGISTRY_URL2);
|
|
3939
4093
|
if (!res.ok) return null;
|
|
3940
4094
|
const data = await res.json();
|
|
3941
4095
|
const latest = typeof data === "object" && data !== null ? data.version : null;
|
|
3942
|
-
|
|
3943
|
-
|
|
4096
|
+
await writeState(statePath, {
|
|
4097
|
+
lastCheckAt: Date.now(),
|
|
4098
|
+
latest: typeof latest === "string" ? latest : null
|
|
4099
|
+
});
|
|
4100
|
+
return noticeFor(typeof latest === "string" ? latest : null, currentVersion);
|
|
3944
4101
|
} catch {
|
|
3945
4102
|
return null;
|
|
3946
4103
|
}
|
|
@@ -3989,30 +4146,30 @@ function Repl({
|
|
|
3989
4146
|
initialUsage
|
|
3990
4147
|
}) {
|
|
3991
4148
|
const { exit } = useApp();
|
|
3992
|
-
const [initialDisplay] =
|
|
3993
|
-
const [messages, setMessages] =
|
|
3994
|
-
const [epoch, setEpoch] =
|
|
3995
|
-
const [streamingText, setStreamingText] =
|
|
3996
|
-
const [isStreaming, setIsStreaming] =
|
|
3997
|
-
const [thinking, setThinking] =
|
|
3998
|
-
const [thinkingText, setThinkingText] =
|
|
3999
|
-
const [thoughtSummary, setThoughtSummary] =
|
|
4000
|
-
const [, setUsageVersion] =
|
|
4001
|
-
const [modelName, setModelName] =
|
|
4002
|
-
const [pending, setPending] =
|
|
4003
|
-
const [
|
|
4004
|
-
const [
|
|
4005
|
-
const [
|
|
4006
|
-
const [
|
|
4007
|
-
const [
|
|
4008
|
-
const [
|
|
4009
|
-
const [
|
|
4010
|
-
const [
|
|
4011
|
-
const [pendingConnect, setPendingConnectState] = useState5(null);
|
|
4149
|
+
const [initialDisplay] = useState7(() => buildDisplayMessages(initialMessages ?? []));
|
|
4150
|
+
const [messages, setMessages] = useState7(initialDisplay);
|
|
4151
|
+
const [epoch, setEpoch] = useState7(0);
|
|
4152
|
+
const [streamingText, setStreamingText] = useState7(null);
|
|
4153
|
+
const [isStreaming, setIsStreaming] = useState7(false);
|
|
4154
|
+
const [thinking, setThinking] = useState7(false);
|
|
4155
|
+
const [thinkingText, setThinkingText] = useState7("");
|
|
4156
|
+
const [thoughtSummary, setThoughtSummary] = useState7(null);
|
|
4157
|
+
const [, setUsageVersion] = useState7(0);
|
|
4158
|
+
const [modelName, setModelName] = useState7(model);
|
|
4159
|
+
const [pending, setPending] = useState7(null);
|
|
4160
|
+
const [activity, setActivity] = useState7(null);
|
|
4161
|
+
const [bgLabels, setBgLabels] = useState7(() => runningTaskLabels());
|
|
4162
|
+
const [todos, setTodos] = useState7([]);
|
|
4163
|
+
const [permissionModeState, setPermissionModeState] = useState7(permissionMode);
|
|
4164
|
+
const [planApproval, setPlanApprovalState] = useState7(false);
|
|
4165
|
+
const [pendingRewind, setPendingRewindState] = useState7(null);
|
|
4166
|
+
const [picker, setPickerState] = useState7(null);
|
|
4167
|
+
const [pendingConnect, setPendingConnectState] = useState7(null);
|
|
4012
4168
|
const backendRef = useRef6(backend);
|
|
4013
4169
|
const nextIdRef = useRef6(initialDisplay.length);
|
|
4014
4170
|
const abortRef = useRef6(null);
|
|
4015
4171
|
const busyCommandRef = useRef6(null);
|
|
4172
|
+
const busyAbortRef = useRef6(null);
|
|
4016
4173
|
const streamedRef = useRef6("");
|
|
4017
4174
|
const turnStartedAtRef = useRef6(0);
|
|
4018
4175
|
const lastCommittedLenRef = useRef6(0);
|
|
@@ -4024,6 +4181,7 @@ function Repl({
|
|
|
4024
4181
|
const pendingRef = useRef6(null);
|
|
4025
4182
|
const permissionChainRef = useRef6(Promise.resolve());
|
|
4026
4183
|
const alwaysAllowedRef = useRef6(/* @__PURE__ */ new Set());
|
|
4184
|
+
const alwaysAllowedListRef = useRef6([]);
|
|
4027
4185
|
const prevModeRef = useRef6(null);
|
|
4028
4186
|
const planApprovalRef = useRef6(false);
|
|
4029
4187
|
const pendingRewindRef = useRef6(null);
|
|
@@ -4032,23 +4190,23 @@ function Repl({
|
|
|
4032
4190
|
const modelNameRef = useRef6(model);
|
|
4033
4191
|
const sessionStoreRef = useRef6(sessionStore);
|
|
4034
4192
|
const configRef = useRef6(config);
|
|
4035
|
-
|
|
4193
|
+
useEffect4(() => {
|
|
4036
4194
|
configRef.current = config;
|
|
4037
4195
|
}, [config]);
|
|
4038
4196
|
const usageRef = useRef6(initialUsage ? { ...initialUsage } : emptyUsage());
|
|
4039
4197
|
const turnChunksRef = useRef6(0);
|
|
4040
4198
|
const messagesRef = useRef6(initialDisplay);
|
|
4041
|
-
const [queueItems, setQueueItems] =
|
|
4199
|
+
const [queueItems, setQueueItems] = useState7([]);
|
|
4042
4200
|
const queueRef = useRef6(new PromptQueue());
|
|
4043
|
-
const [pendingImages, setPendingImages] =
|
|
4201
|
+
const [pendingImages, setPendingImages] = useState7([]);
|
|
4044
4202
|
const pendingImagesRef = useRef6([]);
|
|
4045
4203
|
const imageSeqRef = useRef6(1);
|
|
4046
|
-
const [inputRefill, setInputRefill] =
|
|
4204
|
+
const [inputRefill, setInputRefill] = useState7();
|
|
4047
4205
|
const refillSeqRef = useRef6(0);
|
|
4048
4206
|
const lastEscRef = useRef6(null);
|
|
4049
|
-
const [gitBranch, setGitBranch] =
|
|
4050
|
-
const [contextPercent, setContextPercent] =
|
|
4051
|
-
const [initialHistory] =
|
|
4207
|
+
const [gitBranch, setGitBranch] = useState7(null);
|
|
4208
|
+
const [contextPercent, setContextPercent] = useState7(null);
|
|
4209
|
+
const [initialHistory] = useState7(() => loadHistory());
|
|
4052
4210
|
const budgetWarnedRef = useRef6(false);
|
|
4053
4211
|
const budgetExceededRef = useRef6(false);
|
|
4054
4212
|
const runStreamRef = useRef6(
|
|
@@ -4214,7 +4372,7 @@ function Repl({
|
|
|
4214
4372
|
const attachConfirmHandler = useCallback(
|
|
4215
4373
|
(target) => {
|
|
4216
4374
|
target.confirmHandler = (req) => {
|
|
4217
|
-
if (isAllowedByRules(
|
|
4375
|
+
if (isAllowedByRules(alwaysAllowedListRef.current, req)) {
|
|
4218
4376
|
return Promise.resolve(true);
|
|
4219
4377
|
}
|
|
4220
4378
|
const ask = async () => {
|
|
@@ -4234,7 +4392,7 @@ function Repl({
|
|
|
4234
4392
|
},
|
|
4235
4393
|
[setPendingPermission, cwd, pushMessage]
|
|
4236
4394
|
);
|
|
4237
|
-
|
|
4395
|
+
useEffect4(() => {
|
|
4238
4396
|
attachConfirmHandler(backendRef.current);
|
|
4239
4397
|
return () => {
|
|
4240
4398
|
tickerStopRef.current?.();
|
|
@@ -4242,19 +4400,19 @@ function Repl({
|
|
|
4242
4400
|
pendingRef.current?.resolve(false);
|
|
4243
4401
|
};
|
|
4244
4402
|
}, [attachConfirmHandler]);
|
|
4245
|
-
|
|
4403
|
+
useEffect4(() => {
|
|
4246
4404
|
if (process.env.STAR_NO_UPDATE_CHECK === "1") return;
|
|
4247
4405
|
void checkForUpdate(VERSION).then((message) => {
|
|
4248
4406
|
if (message) pushMessage("system", message);
|
|
4249
4407
|
});
|
|
4250
4408
|
}, [pushMessage]);
|
|
4251
4409
|
const resumedAtStart = initialMessages !== void 0;
|
|
4252
|
-
|
|
4410
|
+
useEffect4(() => {
|
|
4253
4411
|
if (!resumedAtStart) return;
|
|
4254
|
-
loadTodos(cwd).then(setTodos).catch(() => {
|
|
4412
|
+
loadTodos(cwd, sessionStoreRef.current?.id).then(setTodos).catch(() => {
|
|
4255
4413
|
});
|
|
4256
4414
|
}, [cwd, resumedAtStart]);
|
|
4257
|
-
|
|
4415
|
+
useEffect4(() => {
|
|
4258
4416
|
const onUpdate = (task) => {
|
|
4259
4417
|
setBgLabels(runningTaskLabels());
|
|
4260
4418
|
pushMessage(
|
|
@@ -4348,6 +4506,11 @@ function Repl({
|
|
|
4348
4506
|
lastEscRef.current = null;
|
|
4349
4507
|
return;
|
|
4350
4508
|
}
|
|
4509
|
+
if (busyCommandRef.current) {
|
|
4510
|
+
lastEscRef.current = null;
|
|
4511
|
+
busyAbortRef.current?.abort();
|
|
4512
|
+
return;
|
|
4513
|
+
}
|
|
4351
4514
|
const busy = abortRef.current !== null || pendingRef.current !== null;
|
|
4352
4515
|
if (busy) {
|
|
4353
4516
|
lastEscRef.current = null;
|
|
@@ -4365,7 +4528,7 @@ function Repl({
|
|
|
4365
4528
|
}
|
|
4366
4529
|
lastEscRef.current = null;
|
|
4367
4530
|
if (key.shift && key.tab) {
|
|
4368
|
-
if (abortRef.current || pendingRef.current || planApprovalRef.current || pendingRewindRef.current || pendingConnectRef.current || pickerRef.current)
|
|
4531
|
+
if (abortRef.current || pendingRef.current || busyCommandRef.current || planApprovalRef.current || pendingRewindRef.current || pendingConnectRef.current || pickerRef.current)
|
|
4369
4532
|
return;
|
|
4370
4533
|
const current = config.permissionMode;
|
|
4371
4534
|
const index = SESSION_MODE_CYCLE.indexOf(current);
|
|
@@ -4380,6 +4543,7 @@ function Repl({
|
|
|
4380
4543
|
if (decision === "always") {
|
|
4381
4544
|
const rule = buildAllowRule(p.request);
|
|
4382
4545
|
alwaysAllowedRef.current.add(rule);
|
|
4546
|
+
alwaysAllowedListRef.current = [...alwaysAllowedRef.current];
|
|
4383
4547
|
void addAllowRule(rule).then((added) => {
|
|
4384
4548
|
pushMessage(
|
|
4385
4549
|
"system",
|
|
@@ -4410,11 +4574,13 @@ function Repl({
|
|
|
4410
4574
|
providerMetadata: reasoningEffortMetadata(config, name),
|
|
4411
4575
|
temperature: modelConfig.temperature,
|
|
4412
4576
|
streamIdleTimeoutSec: modelConfig.streamIdleTimeoutSec,
|
|
4413
|
-
streamFirstChunkTimeoutSec: modelConfig.streamFirstChunkTimeoutSec
|
|
4577
|
+
streamFirstChunkTimeoutSec: modelConfig.streamFirstChunkTimeoutSec,
|
|
4578
|
+
vision: modelConfig.vision,
|
|
4579
|
+
modelName: name
|
|
4414
4580
|
});
|
|
4415
4581
|
const prev = backendRef.current;
|
|
4416
4582
|
if (prev instanceof AgentLoop) {
|
|
4417
|
-
await loop.loadMessages([...prev.
|
|
4583
|
+
await loop.loadMessages([...prev.getStarMessages()]);
|
|
4418
4584
|
}
|
|
4419
4585
|
attachConfirmHandler(loop);
|
|
4420
4586
|
backendRef.current = loop;
|
|
@@ -4495,9 +4661,10 @@ function Repl({
|
|
|
4495
4661
|
await current.loadMessages(resumed.messages);
|
|
4496
4662
|
current.setSessionStore(store);
|
|
4497
4663
|
sessionStoreRef.current = store;
|
|
4498
|
-
|
|
4664
|
+
setTodoSession(store.id);
|
|
4665
|
+
setTodos(await loadTodos(cwd, store.id));
|
|
4499
4666
|
hydrateSnapshots(await loadSessionSnapshots(store.dir));
|
|
4500
|
-
const display = buildDisplayMessages(resumed.messages);
|
|
4667
|
+
const display = buildDisplayMessages(toCoreMessages(resumed.messages));
|
|
4501
4668
|
nextIdRef.current = display.length;
|
|
4502
4669
|
applyMessages(display);
|
|
4503
4670
|
usageRef.current = resumed.meta.usage ? { ...resumed.meta.usage } : emptyUsage();
|
|
@@ -4544,8 +4711,7 @@ function Repl({
|
|
|
4544
4711
|
flushedRef.current = { ...flushedRef.current, streamed: "" };
|
|
4545
4712
|
setStreamingText("");
|
|
4546
4713
|
};
|
|
4547
|
-
tickerStopRef.current = startTicker((
|
|
4548
|
-
setSpinnerTick(tick);
|
|
4714
|
+
tickerStopRef.current = startTicker(() => {
|
|
4549
4715
|
const uncommitted = streamedRef.current.slice(lastCommittedLenRef.current);
|
|
4550
4716
|
const split = splitCommittableLines(uncommitted, 8);
|
|
4551
4717
|
if (split) {
|
|
@@ -4761,7 +4927,7 @@ function Repl({
|
|
|
4761
4927
|
sessionCostUsd
|
|
4762
4928
|
]
|
|
4763
4929
|
);
|
|
4764
|
-
|
|
4930
|
+
useEffect4(() => {
|
|
4765
4931
|
runStreamRef.current = runStream;
|
|
4766
4932
|
}, [runStream]);
|
|
4767
4933
|
const handlePlanDecision = useCallback(
|
|
@@ -4919,7 +5085,7 @@ ${lines.join("\n")}`;
|
|
|
4919
5085
|
if (!(current instanceof AgentLoop) || !store) {
|
|
4920
5086
|
return "Nothing to fork \u2014 no active session.";
|
|
4921
5087
|
}
|
|
4922
|
-
const messages2 = await store.
|
|
5088
|
+
const messages2 = await store.starMessages();
|
|
4923
5089
|
if (messages2.length === 0) {
|
|
4924
5090
|
return "Nothing to fork \u2014 the current session has no messages yet.";
|
|
4925
5091
|
}
|
|
@@ -4930,6 +5096,7 @@ ${lines.join("\n")}`;
|
|
|
4930
5096
|
await fork.setTitle(title);
|
|
4931
5097
|
current.setSessionStore(fork);
|
|
4932
5098
|
sessionStoreRef.current = fork;
|
|
5099
|
+
setTodoSession(fork.id);
|
|
4933
5100
|
return `Forked into new session ${fork.id} ("${title}") and switched to it. Checkpoints and rewind history do not carry over.`;
|
|
4934
5101
|
},
|
|
4935
5102
|
newSession: async () => {
|
|
@@ -4941,8 +5108,9 @@ ${lines.join("\n")}`;
|
|
|
4941
5108
|
const store = await SessionStore.create(cwd, modelNameRef.current);
|
|
4942
5109
|
current.setSessionStore(store);
|
|
4943
5110
|
sessionStoreRef.current = store;
|
|
4944
|
-
|
|
4945
|
-
|
|
5111
|
+
setTodoSession(store.id);
|
|
5112
|
+
const [first] = current.getStarMessages();
|
|
5113
|
+
await current.loadMessages(first?.message.role === "system" ? [first] : []);
|
|
4946
5114
|
clearSnapshots();
|
|
4947
5115
|
redrawMessages([]);
|
|
4948
5116
|
resetTodos();
|
|
@@ -4987,14 +5155,16 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
|
|
|
4987
5155
|
return `Busy \u2014 /${busyCommandRef.current} is still running.`;
|
|
4988
5156
|
}
|
|
4989
5157
|
busyCommandRef.current = "compact";
|
|
5158
|
+
const controller = new AbortController();
|
|
5159
|
+
busyAbortRef.current = controller;
|
|
4990
5160
|
turnStartedAtRef.current = Date.now();
|
|
4991
|
-
tickerStopRef.current = startTicker((tick) => setSpinnerTick(tick));
|
|
4992
5161
|
setActivity("compacting context\u2026");
|
|
4993
5162
|
try {
|
|
4994
5163
|
const result = await compactSession({
|
|
4995
5164
|
backend: backendRef.current,
|
|
4996
5165
|
sessionStore: sessionStoreRef.current,
|
|
4997
|
-
config
|
|
5166
|
+
config,
|
|
5167
|
+
signal: controller.signal
|
|
4998
5168
|
});
|
|
4999
5169
|
if (result.compacted && result.messages) {
|
|
5000
5170
|
const display = buildDisplayMessages(result.messages);
|
|
@@ -5007,6 +5177,7 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
|
|
|
5007
5177
|
tickerStopRef.current = null;
|
|
5008
5178
|
setActivity(null);
|
|
5009
5179
|
busyCommandRef.current = null;
|
|
5180
|
+
busyAbortRef.current = null;
|
|
5010
5181
|
const nextPrompt = queueRef.current.dequeue();
|
|
5011
5182
|
if (nextPrompt) {
|
|
5012
5183
|
setQueueItems(queueRef.current.list());
|
|
@@ -5200,6 +5371,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5200
5371
|
};
|
|
5201
5372
|
const reg = new CommandRegistry();
|
|
5202
5373
|
registerBuiltinCommands(reg);
|
|
5374
|
+
const modelCommand = reg.get("model");
|
|
5375
|
+
if (modelCommand) modelCommand.argHints = () => config.models.map((m) => m.name);
|
|
5203
5376
|
registerCustomCommands(reg, cwd);
|
|
5204
5377
|
return Object.assign(reg, { ctx });
|
|
5205
5378
|
}, [
|
|
@@ -5318,16 +5491,17 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5318
5491
|
() => registry.list().map((cmd) => ({
|
|
5319
5492
|
name: cmd.name,
|
|
5320
5493
|
description: cmd.description,
|
|
5321
|
-
usage: cmd.usage ?? `/${cmd.name}
|
|
5494
|
+
usage: cmd.usage ?? `/${cmd.name}`,
|
|
5495
|
+
argHints: cmd.argHints
|
|
5322
5496
|
})),
|
|
5323
5497
|
[registry]
|
|
5324
5498
|
);
|
|
5325
|
-
|
|
5499
|
+
useEffect4(() => {
|
|
5326
5500
|
setGitBranch(getGitSummaryCached(cwd)?.branch ?? null);
|
|
5327
5501
|
const current = backendRef.current;
|
|
5328
5502
|
if (current instanceof AgentLoop) {
|
|
5329
5503
|
const window_ = contextWindowTokens(config, modelNameRef.current);
|
|
5330
|
-
const pct = estimateTokens(
|
|
5504
|
+
const pct = (estimateTokens(current.getMessages()) + current.getToolSchemaTokens()) / window_ * 100;
|
|
5331
5505
|
setContextPercent(pct < 10 ? Math.round(pct * 10) / 10 : Math.round(pct));
|
|
5332
5506
|
} else {
|
|
5333
5507
|
setContextPercent(null);
|
|
@@ -5340,9 +5514,8 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5340
5514
|
ThinkingIndicator,
|
|
5341
5515
|
{
|
|
5342
5516
|
reasoning: thinkingText,
|
|
5343
|
-
frame: spinnerTick,
|
|
5344
5517
|
activity: activity ?? void 0,
|
|
5345
|
-
|
|
5518
|
+
startedAt: turnStartedAtRef.current
|
|
5346
5519
|
}
|
|
5347
5520
|
),
|
|
5348
5521
|
!thinking && activity === null && thoughtSummary !== null && /* @__PURE__ */ jsx11(Text14, { dimColor: true, children: thoughtSummary }),
|
|
@@ -5461,4 +5634,4 @@ export {
|
|
|
5461
5634
|
Repl,
|
|
5462
5635
|
renderRepl
|
|
5463
5636
|
};
|
|
5464
|
-
//# sourceMappingURL=repl-
|
|
5637
|
+
//# sourceMappingURL=repl-GBVIW5QR.js.map
|