@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
|
@@ -6,84 +6,100 @@ import {
|
|
|
6
6
|
buildDisplayMessages,
|
|
7
7
|
clearSessions,
|
|
8
8
|
computeCostUsd,
|
|
9
|
-
coreMessageText,
|
|
10
|
-
createModel,
|
|
11
9
|
estimateCost,
|
|
10
|
+
folderIcon,
|
|
12
11
|
formatByteCount,
|
|
13
12
|
formatDollars,
|
|
14
13
|
formatElapsedSeconds,
|
|
15
14
|
formatSessionEntries,
|
|
16
15
|
formatStreamError,
|
|
17
16
|
formatTokens,
|
|
18
|
-
listModels,
|
|
19
17
|
listSessionEntries,
|
|
20
18
|
loadSessionSnapshots,
|
|
21
19
|
previewLines,
|
|
22
|
-
reasoningEffortMetadata,
|
|
23
20
|
relativeTime,
|
|
24
21
|
renderMarkdown,
|
|
25
22
|
resolveMentions,
|
|
26
|
-
resolveModelConfig,
|
|
27
23
|
resolveSessionId,
|
|
28
24
|
sessionPreview,
|
|
29
25
|
shortSessionId,
|
|
30
26
|
splitCommittableLines,
|
|
31
27
|
summarizeArgs,
|
|
28
|
+
thinkingIcon,
|
|
32
29
|
toTerminalSafe,
|
|
33
|
-
|
|
34
|
-
} from "./chunk-
|
|
30
|
+
toolIcon
|
|
31
|
+
} from "./chunk-VJFWTXWN.js";
|
|
35
32
|
import {
|
|
36
33
|
AgentLoop,
|
|
37
|
-
MAX_IMAGE_DIMENSION,
|
|
38
|
-
SKIP_DIRS,
|
|
39
|
-
TodoStore,
|
|
40
34
|
WORKING_DIFF_MAX_LINES,
|
|
41
35
|
appendUserMemory,
|
|
42
|
-
bashTool,
|
|
43
36
|
buildAllowRule,
|
|
44
37
|
buildCommitPrompt,
|
|
45
|
-
clearSnapshots,
|
|
46
38
|
collectCommitContext,
|
|
47
39
|
collectWorkingDiff,
|
|
48
40
|
compactMessages,
|
|
41
|
+
discoverSkills,
|
|
42
|
+
estimateTokens,
|
|
43
|
+
getGitSummaryCached,
|
|
44
|
+
isAllowedByRules,
|
|
45
|
+
isDangerousCommand,
|
|
46
|
+
isGitRepo,
|
|
47
|
+
summarizeMessages
|
|
48
|
+
} from "./chunk-RE3NOULF.js";
|
|
49
|
+
import {
|
|
49
50
|
contextWindowTokens,
|
|
51
|
+
coreMessageText,
|
|
52
|
+
diffTreeNames,
|
|
53
|
+
reconcileStarMessages,
|
|
54
|
+
toCoreMessages
|
|
55
|
+
} from "./chunk-56ZIA57D.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 {
|
|
50
65
|
createDefaultRegistry,
|
|
66
|
+
formatTaskFinished,
|
|
67
|
+
formatTaskList,
|
|
68
|
+
formatTaskStarted
|
|
69
|
+
} from "./chunk-OIJYNQFZ.js";
|
|
70
|
+
import {
|
|
71
|
+
MAX_IMAGE_DIMENSION,
|
|
72
|
+
SKIP_DIRS,
|
|
73
|
+
TodoStore,
|
|
74
|
+
bashTool,
|
|
75
|
+
clearSnapshots,
|
|
51
76
|
createIgnorePredicate,
|
|
52
77
|
defaultAgentTasks,
|
|
53
78
|
defaultTaskManager,
|
|
54
|
-
diffTreeNames,
|
|
55
|
-
discoverSkills,
|
|
56
79
|
downsampleImageIfNeeded,
|
|
57
80
|
envFilePath,
|
|
58
|
-
estimateTokens,
|
|
59
81
|
formatAgentTaskUpdate,
|
|
60
|
-
formatTaskFinished,
|
|
61
|
-
formatTaskList,
|
|
62
|
-
formatTaskStarted,
|
|
63
82
|
formatTodos,
|
|
64
|
-
getGitSummaryCached,
|
|
65
83
|
globalConfigPath,
|
|
66
84
|
hydrateSnapshots,
|
|
67
|
-
isAllowedByRules,
|
|
68
|
-
isDangerousCommand,
|
|
69
|
-
isGitRepo,
|
|
70
85
|
listSnapshots,
|
|
71
86
|
listTurnSnapshots,
|
|
72
87
|
loadTodos,
|
|
73
88
|
parseTodoArgs,
|
|
74
|
-
reconcileToolCalls,
|
|
75
89
|
resetTodos,
|
|
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
|
}
|
|
@@ -605,18 +625,18 @@ import fs2 from "fs/promises";
|
|
|
605
625
|
import path3 from "path";
|
|
606
626
|
var MIN_COMPACT_MESSAGES = 4;
|
|
607
627
|
async function compactSession(opts) {
|
|
608
|
-
const { backend, sessionStore, config
|
|
628
|
+
const { backend, sessionStore, config } = 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 {
|
|
@@ -624,32 +644,49 @@ async function compactSession(opts) {
|
|
|
624
644
|
compacted: false
|
|
625
645
|
};
|
|
626
646
|
}
|
|
627
|
-
const next = await applyCompactionSummary(
|
|
647
|
+
const next = await applyCompactionSummary(
|
|
648
|
+
messages,
|
|
649
|
+
compacted,
|
|
650
|
+
config,
|
|
651
|
+
backend.getAuxModel(),
|
|
652
|
+
backend.getAuxTemperature(),
|
|
653
|
+
opts.signal
|
|
654
|
+
);
|
|
655
|
+
if (next === null || opts.signal?.aborted) {
|
|
656
|
+
return { message: "Compaction cancelled \u2014 history left unchanged.", compacted: false };
|
|
657
|
+
}
|
|
628
658
|
await backend.loadMessages(next);
|
|
629
659
|
await sessionStore?.replaceMessages(next);
|
|
630
|
-
const
|
|
660
|
+
const coreNext = toCoreMessages(next);
|
|
661
|
+
const afterTokens = estimateTokens(coreNext);
|
|
631
662
|
return {
|
|
632
663
|
message: `Compacted context: ${messages.length} -> ${next.length} messages (~${formatTokens(beforeTokens)} -> ~${formatTokens(afterTokens)} estimated tokens).`,
|
|
633
664
|
compacted: true,
|
|
634
|
-
messages:
|
|
665
|
+
messages: coreNext
|
|
635
666
|
};
|
|
636
667
|
}
|
|
637
|
-
async function applyCompactionSummary(original, compacted, config, model) {
|
|
638
|
-
if (config.contextCompaction !== "summary"
|
|
668
|
+
async function applyCompactionSummary(original, compacted, config, model, temperature, signal) {
|
|
669
|
+
if (config.contextCompaction !== "summary") {
|
|
639
670
|
return compacted.messages;
|
|
640
671
|
}
|
|
641
|
-
const headCount = compacted.messages[0]?.role === "system" ? 1 : 0;
|
|
642
|
-
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));
|
|
643
674
|
try {
|
|
644
|
-
const summary = await summarizeMessages(dropped, model);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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]
|
|
649
682
|
${summary}`
|
|
650
|
-
|
|
651
|
-
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
return messages;
|
|
686
|
+
}
|
|
687
|
+
return compacted.messages;
|
|
652
688
|
} catch {
|
|
689
|
+
if (signal?.aborted) return null;
|
|
653
690
|
return compacted.messages;
|
|
654
691
|
}
|
|
655
692
|
}
|
|
@@ -735,7 +772,11 @@ function stringifyPart(value) {
|
|
|
735
772
|
import { readFileSync } from "fs";
|
|
736
773
|
|
|
737
774
|
// src/session/search.ts
|
|
775
|
+
import fs3 from "fs/promises";
|
|
776
|
+
import path4 from "path";
|
|
777
|
+
import { StringDecoder } from "string_decoder";
|
|
738
778
|
var SNIPPET_LENGTH = 100;
|
|
779
|
+
var SEARCH_READ_CHUNK_BYTES = 64 * 1024;
|
|
739
780
|
function flattenMessage(message) {
|
|
740
781
|
const content = message.content;
|
|
741
782
|
if (typeof content === "string") return content;
|
|
@@ -762,17 +803,113 @@ function makeSnippet(text, query) {
|
|
|
762
803
|
start = Math.max(0, end - SNIPPET_LENGTH);
|
|
763
804
|
return `${start > 0 ? "\u2026" : ""}${flat.slice(start, end)}${end < flat.length ? "\u2026" : ""}`;
|
|
764
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
|
+
}
|
|
765
906
|
async function searchSessions(query, limit = 10) {
|
|
766
907
|
const hits = [];
|
|
767
908
|
const needle = query.toLowerCase();
|
|
768
909
|
for (const meta of await SessionStore.list()) {
|
|
769
910
|
if (hits.length >= limit) break;
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const text = (await store.messages()).map(flattenMessage).filter((part) => part.length > 0).join("\n");
|
|
773
|
-
if (text.toLowerCase().includes(needle)) {
|
|
774
|
-
hits.push({ meta, snippet: makeSnippet(text, query) });
|
|
775
|
-
}
|
|
911
|
+
if (needle !== "" && !await sessionTextContains(meta.id, needle)) continue;
|
|
912
|
+
hits.push({ meta, snippet: await buildSessionSnippet(meta.id, needle, query) });
|
|
776
913
|
}
|
|
777
914
|
return hits;
|
|
778
915
|
}
|
|
@@ -788,7 +925,7 @@ function formatSearchResults(hits, query) {
|
|
|
788
925
|
|
|
789
926
|
// src/cli/diff-preview.ts
|
|
790
927
|
import { readFile as readFile2 } from "fs/promises";
|
|
791
|
-
import
|
|
928
|
+
import path5 from "path";
|
|
792
929
|
var CONTEXT_LINES = 2;
|
|
793
930
|
var MAX_HUNK_LINES = 15;
|
|
794
931
|
var MAX_TOTAL_LINES = 12;
|
|
@@ -949,7 +1086,7 @@ async function generateDiffPreview(toolName, args, cwd) {
|
|
|
949
1086
|
if (toolName !== "edit_file" && toolName !== "write_file") return null;
|
|
950
1087
|
const filePath = getStringArg(args, "path");
|
|
951
1088
|
if (filePath === void 0) return null;
|
|
952
|
-
const absolute =
|
|
1089
|
+
const absolute = path5.resolve(cwd, filePath);
|
|
953
1090
|
let existing;
|
|
954
1091
|
try {
|
|
955
1092
|
existing = await readFile2(absolute, "utf8");
|
|
@@ -1021,6 +1158,7 @@ ${sections.join("\n\n")}`);
|
|
|
1021
1158
|
description: "Delete stored sessions: this directory by default, every session with --all (the current session is kept)",
|
|
1022
1159
|
usage: "/clear-sessions [--all]",
|
|
1023
1160
|
category: "Sessions",
|
|
1161
|
+
argHints: ["--all"],
|
|
1024
1162
|
async run(args, ctx) {
|
|
1025
1163
|
const arg = args.trim();
|
|
1026
1164
|
if (arg !== "" && arg !== "--all") {
|
|
@@ -1174,6 +1312,7 @@ ${sections.join("\n\n")}`);
|
|
|
1174
1312
|
description: "Set the permission mode (opens a picker when no mode is given)",
|
|
1175
1313
|
usage: "/permission [ask|auto|readonly|yolo]",
|
|
1176
1314
|
category: "Settings",
|
|
1315
|
+
argHints: ["ask", "auto", "readonly", "yolo", "plan"],
|
|
1177
1316
|
async run(args, ctx) {
|
|
1178
1317
|
const arg = args.trim();
|
|
1179
1318
|
if (!arg) {
|
|
@@ -1197,6 +1336,7 @@ ${sections.join("\n\n")}`);
|
|
|
1197
1336
|
description: "Show user memory (MEMORY.md), or add an entry",
|
|
1198
1337
|
usage: "/memory [add <text>]",
|
|
1199
1338
|
category: "Settings",
|
|
1339
|
+
argHints: ["add"],
|
|
1200
1340
|
run(args, ctx) {
|
|
1201
1341
|
const arg = args.trim();
|
|
1202
1342
|
if (arg === "add" || arg.startsWith("add ")) {
|
|
@@ -1276,6 +1416,7 @@ ${content}`);
|
|
|
1276
1416
|
description: "Generate an AGENTS.md for the current project",
|
|
1277
1417
|
usage: "/init [force]",
|
|
1278
1418
|
category: "Settings",
|
|
1419
|
+
argHints: ["force"],
|
|
1279
1420
|
async run(args, ctx) {
|
|
1280
1421
|
ctx.addSystemMessage(await ctx.initProject(args));
|
|
1281
1422
|
}
|
|
@@ -1335,6 +1476,7 @@ ${lines.join("\n")}`);
|
|
|
1335
1476
|
description: "Copy the last assistant reply to the clipboard (`all` for the whole conversation)",
|
|
1336
1477
|
usage: "/copy [all]",
|
|
1337
1478
|
category: "Sessions",
|
|
1479
|
+
argHints: ["all"],
|
|
1338
1480
|
async run(args, ctx) {
|
|
1339
1481
|
const arg = args.trim();
|
|
1340
1482
|
if (arg !== "" && arg !== "all") {
|
|
@@ -1400,8 +1542,8 @@ ${result.diff}${note ? `
|
|
|
1400
1542
|
|
|
1401
1543
|
// src/cli/commands/connect.ts
|
|
1402
1544
|
import { spawn } from "child_process";
|
|
1403
|
-
import
|
|
1404
|
-
import
|
|
1545
|
+
import fs4 from "fs";
|
|
1546
|
+
import path6 from "path";
|
|
1405
1547
|
var PROVIDER_PRESETS = [
|
|
1406
1548
|
{
|
|
1407
1549
|
key: "openai",
|
|
@@ -1541,14 +1683,14 @@ function saveConnection(answers, configPath = globalConfigPath(), envPath = envF
|
|
|
1541
1683
|
promptPrice: 0,
|
|
1542
1684
|
completionPrice: 0
|
|
1543
1685
|
};
|
|
1544
|
-
|
|
1686
|
+
fs4.mkdirSync(path6.dirname(envPath), { recursive: true });
|
|
1545
1687
|
let envContent = "";
|
|
1546
1688
|
try {
|
|
1547
|
-
envContent =
|
|
1689
|
+
envContent = fs4.readFileSync(envPath, "utf8");
|
|
1548
1690
|
} catch {
|
|
1549
1691
|
envContent = "";
|
|
1550
1692
|
}
|
|
1551
|
-
|
|
1693
|
+
fs4.writeFileSync(
|
|
1552
1694
|
envPath,
|
|
1553
1695
|
upsertEnvContent(envContent, provider.apiKeyEnv ?? "", answers.apiKey),
|
|
1554
1696
|
{
|
|
@@ -1557,21 +1699,21 @@ function saveConnection(answers, configPath = globalConfigPath(), envPath = envF
|
|
|
1557
1699
|
}
|
|
1558
1700
|
);
|
|
1559
1701
|
try {
|
|
1560
|
-
|
|
1702
|
+
fs4.chmodSync(envPath, 384);
|
|
1561
1703
|
} catch {
|
|
1562
1704
|
}
|
|
1563
1705
|
if (provider.apiKeyEnv) {
|
|
1564
1706
|
process.env[provider.apiKeyEnv] = answers.apiKey;
|
|
1565
1707
|
}
|
|
1566
|
-
|
|
1708
|
+
fs4.mkdirSync(path6.dirname(configPath), { recursive: true });
|
|
1567
1709
|
let existing = "";
|
|
1568
1710
|
try {
|
|
1569
|
-
existing =
|
|
1711
|
+
existing = fs4.readFileSync(configPath, "utf8");
|
|
1570
1712
|
} catch {
|
|
1571
1713
|
existing = "";
|
|
1572
1714
|
}
|
|
1573
1715
|
const separator = existing === "" ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
1574
|
-
|
|
1716
|
+
fs4.writeFileSync(
|
|
1575
1717
|
configPath,
|
|
1576
1718
|
existing + separator + buildProviderTomlBlock(provider) + buildModelTomlBlock(model),
|
|
1577
1719
|
"utf8"
|
|
@@ -1596,12 +1738,12 @@ ${content}`;
|
|
|
1596
1738
|
function saveDefaultModel(modelName, configPath = globalConfigPath()) {
|
|
1597
1739
|
let content = "";
|
|
1598
1740
|
try {
|
|
1599
|
-
content =
|
|
1741
|
+
content = fs4.readFileSync(configPath, "utf8");
|
|
1600
1742
|
} catch {
|
|
1601
1743
|
content = "";
|
|
1602
1744
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1745
|
+
fs4.mkdirSync(path6.dirname(configPath), { recursive: true });
|
|
1746
|
+
fs4.writeFileSync(configPath, setDefaultModelInToml(content, modelName), "utf8");
|
|
1605
1747
|
}
|
|
1606
1748
|
function openConfigFile(filePath) {
|
|
1607
1749
|
const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", filePath]] : process.platform === "darwin" ? ["open", [filePath]] : ["xdg-open", [filePath]];
|
|
@@ -1616,7 +1758,7 @@ function openConfigFile(filePath) {
|
|
|
1616
1758
|
|
|
1617
1759
|
// src/cli/commands/custom.ts
|
|
1618
1760
|
import { readFileSync as readFileSync2, readdirSync } from "fs";
|
|
1619
|
-
import
|
|
1761
|
+
import path7 from "path";
|
|
1620
1762
|
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
1621
1763
|
var DESCRIPTION_PATTERN = /^<!--\s*description:\s*(.+?)\s*-->\s*$/;
|
|
1622
1764
|
var DEFAULT_DESCRIPTION = "Custom prompt command";
|
|
@@ -1651,14 +1793,14 @@ function scanDir(dir, into) {
|
|
|
1651
1793
|
if (!entry.endsWith(".md")) continue;
|
|
1652
1794
|
const name = entry.slice(0, -".md".length);
|
|
1653
1795
|
if (!NAME_PATTERN.test(name)) continue;
|
|
1654
|
-
const parsed = parseCommandFile(
|
|
1796
|
+
const parsed = parseCommandFile(path7.join(dir, entry), name);
|
|
1655
1797
|
if (parsed) into.set(name, parsed);
|
|
1656
1798
|
}
|
|
1657
1799
|
}
|
|
1658
1800
|
function loadCustomCommands(cwd, home = starHome()) {
|
|
1659
1801
|
const byName = /* @__PURE__ */ new Map();
|
|
1660
|
-
scanDir(
|
|
1661
|
-
scanDir(
|
|
1802
|
+
scanDir(path7.join(home, "commands"), byName);
|
|
1803
|
+
scanDir(path7.join(cwd, ".star", "commands"), byName);
|
|
1662
1804
|
return [...byName.values()];
|
|
1663
1805
|
}
|
|
1664
1806
|
function registerCustomCommands(registry, cwd, home) {
|
|
@@ -1684,8 +1826,8 @@ function registerCustomCommands(registry, cwd, home) {
|
|
|
1684
1826
|
}
|
|
1685
1827
|
|
|
1686
1828
|
// src/cli/commands/doctor.ts
|
|
1687
|
-
import
|
|
1688
|
-
import
|
|
1829
|
+
import fs5 from "fs/promises";
|
|
1830
|
+
import path8 from "path";
|
|
1689
1831
|
var MIN_NODE_MAJOR = 20;
|
|
1690
1832
|
var REGISTRY_URL = "https://registry.npmjs.org/";
|
|
1691
1833
|
var REGISTRY_TIMEOUT_MS = 5e3;
|
|
@@ -1699,7 +1841,7 @@ function nodeMajor(version) {
|
|
|
1699
1841
|
}
|
|
1700
1842
|
async function pathExists(target) {
|
|
1701
1843
|
try {
|
|
1702
|
-
await
|
|
1844
|
+
await fs5.access(target);
|
|
1703
1845
|
return true;
|
|
1704
1846
|
} catch {
|
|
1705
1847
|
return false;
|
|
@@ -1737,7 +1879,7 @@ async function runDoctor(opts) {
|
|
|
1737
1879
|
detail: `${spec.label} (${spec.shell})`
|
|
1738
1880
|
});
|
|
1739
1881
|
}
|
|
1740
|
-
const configPath =
|
|
1882
|
+
const configPath = path8.join(home, "config.toml");
|
|
1741
1883
|
const configExists = await pathExists(configPath);
|
|
1742
1884
|
checks.push({
|
|
1743
1885
|
name: "STAR_HOME / config",
|
|
@@ -1774,14 +1916,14 @@ async function runDoctor(opts) {
|
|
|
1774
1916
|
});
|
|
1775
1917
|
}
|
|
1776
1918
|
}
|
|
1777
|
-
const sessions =
|
|
1919
|
+
const sessions = path8.join(home, "sessions");
|
|
1778
1920
|
let writable = false;
|
|
1779
1921
|
let writeError = "";
|
|
1780
1922
|
try {
|
|
1781
|
-
await
|
|
1782
|
-
const probe =
|
|
1783
|
-
await
|
|
1784
|
-
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);
|
|
1785
1927
|
writable = true;
|
|
1786
1928
|
} catch (error) {
|
|
1787
1929
|
writeError = error instanceof Error ? error.message : String(error);
|
|
@@ -1816,15 +1958,15 @@ function formatDoctorReport(report) {
|
|
|
1816
1958
|
}
|
|
1817
1959
|
|
|
1818
1960
|
// src/cli/commands/init-project.ts
|
|
1819
|
-
import
|
|
1820
|
-
import
|
|
1961
|
+
import fs6 from "fs/promises";
|
|
1962
|
+
import path9 from "path";
|
|
1821
1963
|
import { generateText } from "ai";
|
|
1822
1964
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", "coverage"]);
|
|
1823
1965
|
var MAX_ENTRIES = 20;
|
|
1824
1966
|
var README_HEAD_LINES = 5;
|
|
1825
1967
|
async function fileExists2(target) {
|
|
1826
1968
|
try {
|
|
1827
|
-
await
|
|
1969
|
+
await fs6.access(target);
|
|
1828
1970
|
return true;
|
|
1829
1971
|
} catch {
|
|
1830
1972
|
return false;
|
|
@@ -1832,7 +1974,7 @@ async function fileExists2(target) {
|
|
|
1832
1974
|
}
|
|
1833
1975
|
async function readPackageJson(cwd) {
|
|
1834
1976
|
try {
|
|
1835
|
-
const raw = await
|
|
1977
|
+
const raw = await fs6.readFile(path9.join(cwd, "package.json"), "utf8");
|
|
1836
1978
|
const parsed = JSON.parse(raw);
|
|
1837
1979
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1838
1980
|
return parsed;
|
|
@@ -1849,10 +1991,10 @@ function stringRecord(value) {
|
|
|
1849
1991
|
return out;
|
|
1850
1992
|
}
|
|
1851
1993
|
async function detectPackageManager(cwd) {
|
|
1852
|
-
if (await fileExists2(
|
|
1853
|
-
if (await fileExists2(
|
|
1854
|
-
if (await fileExists2(
|
|
1855
|
-
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";
|
|
1856
1998
|
return null;
|
|
1857
1999
|
}
|
|
1858
2000
|
function inferCommands(pm, scripts) {
|
|
@@ -1867,7 +2009,7 @@ function inferCommands(pm, scripts) {
|
|
|
1867
2009
|
}
|
|
1868
2010
|
async function listEntries(cwd) {
|
|
1869
2011
|
try {
|
|
1870
|
-
const dirents = await
|
|
2012
|
+
const dirents = await fs6.readdir(cwd, { withFileTypes: true });
|
|
1871
2013
|
const entries = [];
|
|
1872
2014
|
for (const dirent of dirents) {
|
|
1873
2015
|
if (dirent.isDirectory()) {
|
|
@@ -1886,7 +2028,7 @@ async function listEntries(cwd) {
|
|
|
1886
2028
|
async function readReadmeHead(cwd) {
|
|
1887
2029
|
for (const name of ["README.md", "readme.md", "README"]) {
|
|
1888
2030
|
try {
|
|
1889
|
-
const raw = await
|
|
2031
|
+
const raw = await fs6.readFile(path9.join(cwd, name), "utf8");
|
|
1890
2032
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).slice(0, README_HEAD_LINES);
|
|
1891
2033
|
} catch {
|
|
1892
2034
|
}
|
|
@@ -1899,14 +2041,14 @@ async function scanProject(cwd) {
|
|
|
1899
2041
|
const scripts = stringRecord(pkg?.scripts);
|
|
1900
2042
|
return {
|
|
1901
2043
|
name: typeof pkg?.name === "string" ? pkg.name : null,
|
|
1902
|
-
dirName:
|
|
2044
|
+
dirName: path9.basename(cwd),
|
|
1903
2045
|
packageManager,
|
|
1904
2046
|
scripts,
|
|
1905
2047
|
dependencies: Object.keys(stringRecord(pkg?.dependencies)),
|
|
1906
2048
|
devDependencies: Object.keys(stringRecord(pkg?.devDependencies)),
|
|
1907
2049
|
commands: inferCommands(packageManager, scripts),
|
|
1908
|
-
hasTsconfig: await fileExists2(
|
|
1909
|
-
isGitRepo: await fileExists2(
|
|
2050
|
+
hasTsconfig: await fileExists2(path9.join(cwd, "tsconfig.json")),
|
|
2051
|
+
isGitRepo: await fileExists2(path9.join(cwd, ".git")),
|
|
1910
2052
|
readmeHead: await readReadmeHead(cwd),
|
|
1911
2053
|
entries: await listEntries(cwd)
|
|
1912
2054
|
};
|
|
@@ -1973,7 +2115,7 @@ Write the AGENTS.md.`
|
|
|
1973
2115
|
}
|
|
1974
2116
|
async function initProject(opts) {
|
|
1975
2117
|
const { cwd, args, model } = opts;
|
|
1976
|
-
const target =
|
|
2118
|
+
const target = path9.join(cwd, "AGENTS.md");
|
|
1977
2119
|
const force = args.split(/\s+/).includes("force");
|
|
1978
2120
|
if (!force && await fileExists2(target)) {
|
|
1979
2121
|
return {
|
|
@@ -2007,7 +2149,7 @@ async function initProject(opts) {
|
|
|
2007
2149
|
content = renderAgentsMd(facts);
|
|
2008
2150
|
note = " No model available; used the template \u2014 review and refine manually.";
|
|
2009
2151
|
}
|
|
2010
|
-
await
|
|
2152
|
+
await fs6.writeFile(target, content);
|
|
2011
2153
|
const lines = content.split("\n").length;
|
|
2012
2154
|
const how = usedLlm ? "generated with model refinement" : "generated from template";
|
|
2013
2155
|
return {
|
|
@@ -2063,7 +2205,7 @@ function parseSlashCommand(input) {
|
|
|
2063
2205
|
}
|
|
2064
2206
|
|
|
2065
2207
|
// src/cli/commands/rewind.ts
|
|
2066
|
-
import
|
|
2208
|
+
import path10 from "path";
|
|
2067
2209
|
function planRewind(snapshots, id) {
|
|
2068
2210
|
const target = snapshots.find((snapshot) => snapshot.id === id);
|
|
2069
2211
|
if (!target) return null;
|
|
@@ -2077,7 +2219,7 @@ function planRewind(snapshots, id) {
|
|
|
2077
2219
|
return { target, affected, messageIndex };
|
|
2078
2220
|
}
|
|
2079
2221
|
function displayPath(filePath, cwd) {
|
|
2080
|
-
const relative =
|
|
2222
|
+
const relative = path10.relative(cwd, filePath);
|
|
2081
2223
|
return relative !== "" && !relative.startsWith("..") ? relative : filePath;
|
|
2082
2224
|
}
|
|
2083
2225
|
function formatTime(timestamp) {
|
|
@@ -2126,12 +2268,27 @@ function didYouMeanSuffix(names) {
|
|
|
2126
2268
|
const listed = names.slice(0, 3).map((name) => `/${name}`);
|
|
2127
2269
|
return ` Did you mean: ${listed.join(", ")}?`;
|
|
2128
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
|
+
}
|
|
2129
2286
|
|
|
2130
2287
|
// src/cli/commands/undo.ts
|
|
2131
2288
|
import { readFile as readFile3 } from "fs/promises";
|
|
2132
|
-
import
|
|
2289
|
+
import path11 from "path";
|
|
2133
2290
|
function displayPath2(filePath, cwd) {
|
|
2134
|
-
const relative =
|
|
2291
|
+
const relative = path11.relative(cwd, filePath);
|
|
2135
2292
|
return relative !== "" && !relative.startsWith("..") ? relative : filePath;
|
|
2136
2293
|
}
|
|
2137
2294
|
async function buildUndoDiffs(snapshots, cwd) {
|
|
@@ -2518,11 +2675,11 @@ import { useRef as useRef4, useState as useState3 } from "react";
|
|
|
2518
2675
|
|
|
2519
2676
|
// src/cli/components/InputBox.tsx
|
|
2520
2677
|
import { Box as Box2, Text as Text2, useStdout } from "ink";
|
|
2521
|
-
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";
|
|
2522
2679
|
|
|
2523
2680
|
// src/cli/path-suggest.ts
|
|
2524
2681
|
import { readdir } from "fs/promises";
|
|
2525
|
-
import
|
|
2682
|
+
import path12 from "path";
|
|
2526
2683
|
var MAX_SCAN_ENTRIES = 2e3;
|
|
2527
2684
|
function extractAtToken(value, cursor) {
|
|
2528
2685
|
const before = value.slice(0, cursor);
|
|
@@ -2537,7 +2694,7 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
|
|
|
2537
2694
|
const dirPart = slash >= 0 ? normalized.slice(0, slash + 1) : "";
|
|
2538
2695
|
const basePrefix = slash >= 0 ? normalized.slice(slash + 1) : normalized;
|
|
2539
2696
|
if (dirPart.split("/").some((segment) => SKIP_DIRS.has(segment))) return [];
|
|
2540
|
-
const dirAbs =
|
|
2697
|
+
const dirAbs = path12.resolve(cwd, dirPart === "" ? "." : dirPart);
|
|
2541
2698
|
const entries = await readdir(dirAbs, { withFileTypes: true }).catch(() => null);
|
|
2542
2699
|
if (!entries) return [];
|
|
2543
2700
|
const ignore = createIgnorePredicate(cwd);
|
|
@@ -2552,7 +2709,7 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
|
|
|
2552
2709
|
if (isDir && SKIP_DIRS.has(entry.name)) continue;
|
|
2553
2710
|
if (entry.name.startsWith(".") && !basePrefix.startsWith(".")) continue;
|
|
2554
2711
|
if (!entry.name.toLowerCase().startsWith(lower)) continue;
|
|
2555
|
-
if (ignore?.(
|
|
2712
|
+
if (ignore?.(path12.join(dirAbs, entry.name), isDir)) continue;
|
|
2556
2713
|
matches.push({ path: `${dirPart}${entry.name}${isDir ? "/" : ""}`, isDir });
|
|
2557
2714
|
}
|
|
2558
2715
|
matches.sort((a, b) => a.path.localeCompare(b.path));
|
|
@@ -2650,7 +2807,7 @@ function pasteToken(id, lineCount) {
|
|
|
2650
2807
|
function stylePasteTokens(segment) {
|
|
2651
2808
|
return segment.replace(PASTE_TOKEN_RE, (match) => `\x1B[2m${match}\x1B[22m`);
|
|
2652
2809
|
}
|
|
2653
|
-
|
|
2810
|
+
var InputBox = memo(function InputBox2({
|
|
2654
2811
|
isStreaming,
|
|
2655
2812
|
disabled,
|
|
2656
2813
|
commands,
|
|
@@ -2783,6 +2940,7 @@ function InputBox({
|
|
|
2783
2940
|
});
|
|
2784
2941
|
const suggestionsEnabled = !isStreaming && !disabled && !suggestionsDismissed && !search;
|
|
2785
2942
|
const suggestions = suggestionsEnabled && commands ? filterCommands(value, commands) : [];
|
|
2943
|
+
const argHints = suggestionsEnabled && commands && cursor === value.length ? filterArgHints(value, commands) : [];
|
|
2786
2944
|
useEffect2(() => {
|
|
2787
2945
|
if (!suggestionsEnabled || !cwd) {
|
|
2788
2946
|
setPathSuggestions([]);
|
|
@@ -2801,8 +2959,8 @@ function InputBox({
|
|
|
2801
2959
|
cancelled = true;
|
|
2802
2960
|
};
|
|
2803
2961
|
}, [value, cursor, cwd, suggestionsEnabled]);
|
|
2804
|
-
const showPathSuggestions = suggestions.length === 0 && pathSuggestions.length > 0;
|
|
2805
|
-
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;
|
|
2806
2964
|
const activeIndex = suggestionCount === 0 ? 0 : Math.min(highlight, suggestionCount - 1);
|
|
2807
2965
|
const completeHighlighted = () => {
|
|
2808
2966
|
if (suggestions.length > 0) {
|
|
@@ -2812,6 +2970,12 @@ function InputBox({
|
|
|
2812
2970
|
edit(text, text.length);
|
|
2813
2971
|
return;
|
|
2814
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
|
+
}
|
|
2815
2979
|
const sugg = pathSuggestions[activeIndex];
|
|
2816
2980
|
const token = extractAtToken(value, cursor);
|
|
2817
2981
|
if (!sugg || !token) return;
|
|
@@ -3064,6 +3228,9 @@ ${value.slice(cursor)}`, cursor + 1);
|
|
|
3064
3228
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` - ${cmd.description}` })
|
|
3065
3229
|
] }, cmd.name);
|
|
3066
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
|
+
) }),
|
|
3067
3234
|
showPathSuggestions && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: pathSuggestions.map(
|
|
3068
3235
|
(sugg, index) => index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: `@${sugg.path}` }, sugg.path) : /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
3069
3236
|
/* @__PURE__ */ jsx2(Text2, { color: "cyan", children: `@${sugg.path}` }),
|
|
@@ -3071,7 +3238,7 @@ ${value.slice(cursor)}`, cursor + 1);
|
|
|
3071
3238
|
] }, sugg.path)
|
|
3072
3239
|
) })
|
|
3073
3240
|
] });
|
|
3074
|
-
}
|
|
3241
|
+
});
|
|
3075
3242
|
|
|
3076
3243
|
// src/cli/components/TextField.tsx
|
|
3077
3244
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -3368,7 +3535,7 @@ function ExitPlanPrompt({ onDecision }) {
|
|
|
3368
3535
|
|
|
3369
3536
|
// src/cli/components/MessageList.tsx
|
|
3370
3537
|
import { Box as Box6, Static, Text as Text7, useStdout as useStdout2 } from "ink";
|
|
3371
|
-
import { memo } from "react";
|
|
3538
|
+
import { memo as memo2, useState as useState5 } from "react";
|
|
3372
3539
|
|
|
3373
3540
|
// src/cli/components/DiffLines.tsx
|
|
3374
3541
|
import { Text as Text6 } from "ink";
|
|
@@ -3403,10 +3570,28 @@ var roleStyles = {
|
|
|
3403
3570
|
system: { label: "\u25CF system", color: "yellow" }
|
|
3404
3571
|
};
|
|
3405
3572
|
var interruptedMarker = (text) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: text.length > 0 ? " [interrupted]" : "[interrupted]" });
|
|
3406
|
-
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 }) {
|
|
3407
3579
|
const { stdout } = useStdout2();
|
|
3408
3580
|
const termWidth = stdout.columns ?? 80;
|
|
3409
|
-
|
|
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
|
+
}
|
|
3410
3595
|
const marginBottom = message.tight ? 0 : 1;
|
|
3411
3596
|
if (message.role === "assistant-cont") {
|
|
3412
3597
|
return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom, children: /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
|
|
@@ -3431,7 +3616,7 @@ var MessageList = memo(function MessageList2({ messages }) {
|
|
|
3431
3616
|
}
|
|
3432
3617
|
if (message.role === "tool") {
|
|
3433
3618
|
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginBottom, children: [
|
|
3434
|
-
/* @__PURE__ */ jsx7(Text7, { color: "magenta", children: message.text }),
|
|
3619
|
+
/* @__PURE__ */ jsx7(Text7, { color: message.dim ? void 0 : "magenta", dimColor: message.dim, children: message.text }),
|
|
3435
3620
|
message.diff && /* @__PURE__ */ jsx7(DiffLines, { lines: message.diff })
|
|
3436
3621
|
] }, message.id);
|
|
3437
3622
|
}
|
|
@@ -3521,73 +3706,7 @@ function RewindConfirmPrompt({
|
|
|
3521
3706
|
|
|
3522
3707
|
// src/cli/components/StatusBar.tsx
|
|
3523
3708
|
import { Box as Box9, Text as Text10, useStdout as useStdout3 } from "ink";
|
|
3524
|
-
import { memo as
|
|
3525
|
-
|
|
3526
|
-
// src/cli/icons.ts
|
|
3527
|
-
var EMOJI_TERM_PROGRAMS = /* @__PURE__ */ new Set([
|
|
3528
|
-
"vscode",
|
|
3529
|
-
"WezTerm",
|
|
3530
|
-
"iTerm.app",
|
|
3531
|
-
"Apple_Terminal",
|
|
3532
|
-
"ghostty",
|
|
3533
|
-
"kitty",
|
|
3534
|
-
"Hyper",
|
|
3535
|
-
"WarpTerminal"
|
|
3536
|
-
]);
|
|
3537
|
-
function resolveIconMode(env = process.env) {
|
|
3538
|
-
const override = env.STAR_ICONS;
|
|
3539
|
-
if (override === "emoji" || override === "plain") return override;
|
|
3540
|
-
if (env.WT_SESSION) return "emoji";
|
|
3541
|
-
if (EMOJI_TERM_PROGRAMS.has(env.TERM_PROGRAM ?? "")) return "emoji";
|
|
3542
|
-
const term = env.TERM ?? "";
|
|
3543
|
-
if (term.includes("kitty") || term.includes("alacritty")) return "emoji";
|
|
3544
|
-
return "plain";
|
|
3545
|
-
}
|
|
3546
|
-
var EMOJI_TOOL_ICONS = {
|
|
3547
|
-
bash: "\u{1F4BB}",
|
|
3548
|
-
read_file: "\u{1F4C4}",
|
|
3549
|
-
write_file: "\u{1F4DD}",
|
|
3550
|
-
edit_file: "\u270F\uFE0F",
|
|
3551
|
-
glob: "\u{1F4C2}",
|
|
3552
|
-
grep: "\u{1F50D}",
|
|
3553
|
-
web_search: "\u{1F50E}",
|
|
3554
|
-
web_fetch: "\u{1F310}",
|
|
3555
|
-
todo_write: "\u{1F4CB}",
|
|
3556
|
-
todo_read: "\u{1F4CB}",
|
|
3557
|
-
task_list: "\u{1F9F5}",
|
|
3558
|
-
task_output: "\u{1F9F5}",
|
|
3559
|
-
task_kill: "\u{1F6D1}",
|
|
3560
|
-
subagent: "\u{1F916}",
|
|
3561
|
-
skill: "\u2728",
|
|
3562
|
-
remember: "\u{1F9E0}"
|
|
3563
|
-
};
|
|
3564
|
-
var PLAIN_TOOL_ICONS = {
|
|
3565
|
-
bash: "$",
|
|
3566
|
-
read_file: "\u2261",
|
|
3567
|
-
write_file: "+",
|
|
3568
|
-
edit_file: "\u270E",
|
|
3569
|
-
glob: "*",
|
|
3570
|
-
grep: "/",
|
|
3571
|
-
web_search: "?",
|
|
3572
|
-
web_fetch: "\u21D3",
|
|
3573
|
-
todo_write: "\u2023",
|
|
3574
|
-
todo_read: "\u2023",
|
|
3575
|
-
task_list: "\xB6",
|
|
3576
|
-
task_output: "\xB6",
|
|
3577
|
-
task_kill: "\xD7",
|
|
3578
|
-
subagent: "\u25C9",
|
|
3579
|
-
skill: "\u2727",
|
|
3580
|
-
remember: "\u203B"
|
|
3581
|
-
};
|
|
3582
|
-
var iconMode = resolveIconMode();
|
|
3583
|
-
function toolIcon(name) {
|
|
3584
|
-
const map = iconMode === "emoji" ? EMOJI_TOOL_ICONS : PLAIN_TOOL_ICONS;
|
|
3585
|
-
return map[name] ?? (iconMode === "emoji" ? "\u{1F527}" : "\u25B8");
|
|
3586
|
-
}
|
|
3587
|
-
var thinkingIcon = iconMode === "emoji" ? "\u{1F4AD}" : "\u2727";
|
|
3588
|
-
var folderIcon = iconMode === "emoji" ? "\u{1F4C1} " : "";
|
|
3589
|
-
|
|
3590
|
-
// src/cli/components/StatusBar.tsx
|
|
3709
|
+
import { memo as memo3 } from "react";
|
|
3591
3710
|
import { jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3592
3711
|
var BG_LABEL_MAX = 40;
|
|
3593
3712
|
var WIDE_COLUMNS = 120;
|
|
@@ -3596,7 +3715,7 @@ function cwdLabel(cwd, wide) {
|
|
|
3596
3715
|
const base = cwd.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
|
|
3597
3716
|
return base && base.length > 0 ? base : cwd;
|
|
3598
3717
|
}
|
|
3599
|
-
var StatusBar =
|
|
3718
|
+
var StatusBar = memo3(function StatusBar2({
|
|
3600
3719
|
cwd,
|
|
3601
3720
|
model,
|
|
3602
3721
|
permissionMode,
|
|
@@ -3654,9 +3773,9 @@ var StatusBar = memo2(function StatusBar2({
|
|
|
3654
3773
|
|
|
3655
3774
|
// src/cli/components/StreamingMessage.tsx
|
|
3656
3775
|
import { Box as Box10, Text as Text11, useStdout as useStdout4 } from "ink";
|
|
3657
|
-
import { memo as
|
|
3776
|
+
import { memo as memo4 } from "react";
|
|
3658
3777
|
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3659
|
-
var StreamingMessage =
|
|
3778
|
+
var StreamingMessage = memo4(function StreamingMessage2({
|
|
3660
3779
|
text,
|
|
3661
3780
|
continuation
|
|
3662
3781
|
}) {
|
|
@@ -3669,6 +3788,24 @@ var StreamingMessage = memo3(function StreamingMessage2({
|
|
|
3669
3788
|
|
|
3670
3789
|
// src/cli/components/ThinkingIndicator.tsx
|
|
3671
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
|
|
3672
3809
|
import { jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3673
3810
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3674
3811
|
var REASONING_TAIL_LENGTH = 200;
|
|
@@ -3678,15 +3815,18 @@ function truncateTail(text, max) {
|
|
|
3678
3815
|
if (flat.length <= max) return flat;
|
|
3679
3816
|
return `\u2026${flat.slice(flat.length - max)}`;
|
|
3680
3817
|
}
|
|
3681
|
-
|
|
3818
|
+
var ThinkingIndicator = memo5(function ThinkingIndicator2({
|
|
3682
3819
|
reasoning,
|
|
3683
|
-
frame
|
|
3820
|
+
frame,
|
|
3684
3821
|
activity,
|
|
3685
|
-
|
|
3822
|
+
startedAt
|
|
3686
3823
|
}) {
|
|
3824
|
+
const [tick, setTick] = useState6(0);
|
|
3825
|
+
useEffect3(() => startTicker((t) => setTick(t)), []);
|
|
3687
3826
|
const tail = reasoning ? truncateTail(reasoning, REASONING_TAIL_LENGTH) : "";
|
|
3688
|
-
const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
3827
|
+
const spinner = SPINNER_FRAMES[(frame ?? tick) % SPINNER_FRAMES.length];
|
|
3689
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));
|
|
3690
3830
|
const elapsed = elapsedSec !== void 0 && elapsedSec >= 3 ? ` (${formatElapsedSeconds(elapsedSec)})` : "";
|
|
3691
3831
|
return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
|
|
3692
3832
|
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
@@ -3700,10 +3840,11 @@ function ThinkingIndicator({
|
|
|
3700
3840
|
tail
|
|
3701
3841
|
] })
|
|
3702
3842
|
] });
|
|
3703
|
-
}
|
|
3843
|
+
});
|
|
3704
3844
|
|
|
3705
3845
|
// src/cli/components/TodoPanel.tsx
|
|
3706
3846
|
import { Box as Box12, Text as Text13 } from "ink";
|
|
3847
|
+
import { memo as memo6 } from "react";
|
|
3707
3848
|
import { jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3708
3849
|
var SYMBOLS = {
|
|
3709
3850
|
pending: "\u25CB",
|
|
@@ -3722,8 +3863,8 @@ function visibleTodoWindow(todos, max = MAX_VISIBLE_TODOS) {
|
|
|
3722
3863
|
hiddenAfter: todos.length - start - items.length
|
|
3723
3864
|
};
|
|
3724
3865
|
}
|
|
3725
|
-
|
|
3726
|
-
if (todos.length === 0) return null;
|
|
3866
|
+
var TodoPanel = memo6(function TodoPanel2({ todos }) {
|
|
3867
|
+
if (todos.length === 0 || todos.every((t) => t.status === "done")) return null;
|
|
3727
3868
|
const { items, hiddenBefore, hiddenAfter } = visibleTodoWindow(todos);
|
|
3728
3869
|
return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
|
|
3729
3870
|
hiddenBefore > 0 && /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
|
|
@@ -3751,7 +3892,7 @@ function TodoPanel({ todos }) {
|
|
|
3751
3892
|
" more"
|
|
3752
3893
|
] })
|
|
3753
3894
|
] });
|
|
3754
|
-
}
|
|
3895
|
+
});
|
|
3755
3896
|
|
|
3756
3897
|
// src/cli/components/ToolCallCard.tsx
|
|
3757
3898
|
function formatToolCard(card) {
|
|
@@ -3769,20 +3910,20 @@ function isDoubleEscape(previous, now, windowMs = DOUBLE_ESC_WINDOW_MS) {
|
|
|
3769
3910
|
}
|
|
3770
3911
|
|
|
3771
3912
|
// src/cli/history.ts
|
|
3772
|
-
import
|
|
3773
|
-
import
|
|
3913
|
+
import fs7 from "fs";
|
|
3914
|
+
import path13 from "path";
|
|
3774
3915
|
var HISTORY_LIMIT = 50;
|
|
3775
3916
|
var JUNK_ENTRY_PATTERN = /^(.)\1{19,}$/;
|
|
3776
3917
|
function historyPath() {
|
|
3777
|
-
return
|
|
3918
|
+
return path13.join(starHome(), "history");
|
|
3778
3919
|
}
|
|
3779
3920
|
function readLines(file) {
|
|
3780
|
-
const raw =
|
|
3921
|
+
const raw = fs7.readFileSync(file, "utf8");
|
|
3781
3922
|
return raw.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.trim().length > 0);
|
|
3782
3923
|
}
|
|
3783
3924
|
function writeHistory(file, lines) {
|
|
3784
|
-
|
|
3785
|
-
|
|
3925
|
+
fs7.mkdirSync(path13.dirname(file), { recursive: true });
|
|
3926
|
+
fs7.writeFileSync(file, `${lines.join("\n")}
|
|
3786
3927
|
`);
|
|
3787
3928
|
}
|
|
3788
3929
|
function isJunkHistoryEntry(line) {
|
|
@@ -3819,8 +3960,8 @@ function appendHistory(entry) {
|
|
|
3819
3960
|
writeHistory(file, [...existing.slice(-(HISTORY_LIMIT - 1)), line]);
|
|
3820
3961
|
return;
|
|
3821
3962
|
}
|
|
3822
|
-
|
|
3823
|
-
|
|
3963
|
+
fs7.mkdirSync(path13.dirname(file), { recursive: true });
|
|
3964
|
+
fs7.appendFileSync(file, `${line}
|
|
3824
3965
|
`);
|
|
3825
3966
|
} catch {
|
|
3826
3967
|
}
|
|
@@ -3894,27 +4035,38 @@ async function executeShellBang(raw, cwd, signal) {
|
|
|
3894
4035
|
};
|
|
3895
4036
|
}
|
|
3896
4037
|
|
|
3897
|
-
// src/cli/ticker.ts
|
|
3898
|
-
var TICK_MS = 100;
|
|
3899
|
-
function nextFlush(prev, next) {
|
|
3900
|
-
if (prev.streamed === next.streamed && prev.reasoning === next.reasoning) return null;
|
|
3901
|
-
return next;
|
|
3902
|
-
}
|
|
3903
|
-
function startTicker(onTick, intervalMs = TICK_MS) {
|
|
3904
|
-
let tick = 0;
|
|
3905
|
-
const timer = setInterval(() => {
|
|
3906
|
-
tick += 1;
|
|
3907
|
-
onTick(tick);
|
|
3908
|
-
}, intervalMs);
|
|
3909
|
-
return () => clearInterval(timer);
|
|
3910
|
-
}
|
|
3911
|
-
|
|
3912
4038
|
// src/cli/update-check.ts
|
|
4039
|
+
import { mkdir, readFile as readFile4, rename, writeFile } from "fs/promises";
|
|
4040
|
+
import path14 from "path";
|
|
3913
4041
|
var REGISTRY_URL2 = "https://registry.npmjs.org/@cryer%2fstar-cli/latest";
|
|
3914
4042
|
var TIMEOUT_MS = 5e3;
|
|
4043
|
+
var THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
3915
4044
|
async function defaultFetcher2(url) {
|
|
3916
4045
|
return fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
3917
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
|
+
}
|
|
3918
4070
|
function isNewerVersion(latest, current) {
|
|
3919
4071
|
const parse2 = (v) => v.split(".").slice(0, 3).map((part) => Number.parseInt(part, 10));
|
|
3920
4072
|
const a = parse2(latest);
|
|
@@ -3927,14 +4079,25 @@ function isNewerVersion(latest, current) {
|
|
|
3927
4079
|
}
|
|
3928
4080
|
return false;
|
|
3929
4081
|
}
|
|
3930
|
-
|
|
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()) {
|
|
3931
4087
|
try {
|
|
4088
|
+
const state = await readState(statePath);
|
|
4089
|
+
if (state && Date.now() - state.lastCheckAt < THROTTLE_MS) {
|
|
4090
|
+
return noticeFor(state.latest, currentVersion);
|
|
4091
|
+
}
|
|
3932
4092
|
const res = await fetcher(REGISTRY_URL2);
|
|
3933
4093
|
if (!res.ok) return null;
|
|
3934
4094
|
const data = await res.json();
|
|
3935
4095
|
const latest = typeof data === "object" && data !== null ? data.version : null;
|
|
3936
|
-
|
|
3937
|
-
|
|
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);
|
|
3938
4101
|
} catch {
|
|
3939
4102
|
return null;
|
|
3940
4103
|
}
|
|
@@ -3983,30 +4146,30 @@ function Repl({
|
|
|
3983
4146
|
initialUsage
|
|
3984
4147
|
}) {
|
|
3985
4148
|
const { exit } = useApp();
|
|
3986
|
-
const [initialDisplay] =
|
|
3987
|
-
const [messages, setMessages] =
|
|
3988
|
-
const [epoch, setEpoch] =
|
|
3989
|
-
const [streamingText, setStreamingText] =
|
|
3990
|
-
const [isStreaming, setIsStreaming] =
|
|
3991
|
-
const [thinking, setThinking] =
|
|
3992
|
-
const [thinkingText, setThinkingText] =
|
|
3993
|
-
const [thoughtSummary, setThoughtSummary] =
|
|
3994
|
-
const [, setUsageVersion] =
|
|
3995
|
-
const [modelName, setModelName] =
|
|
3996
|
-
const [pending, setPending] =
|
|
3997
|
-
const [
|
|
3998
|
-
const [
|
|
3999
|
-
const [
|
|
4000
|
-
const [
|
|
4001
|
-
const [
|
|
4002
|
-
const [
|
|
4003
|
-
const [
|
|
4004
|
-
const [
|
|
4005
|
-
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);
|
|
4006
4168
|
const backendRef = useRef6(backend);
|
|
4007
4169
|
const nextIdRef = useRef6(initialDisplay.length);
|
|
4008
4170
|
const abortRef = useRef6(null);
|
|
4009
4171
|
const busyCommandRef = useRef6(null);
|
|
4172
|
+
const busyAbortRef = useRef6(null);
|
|
4010
4173
|
const streamedRef = useRef6("");
|
|
4011
4174
|
const turnStartedAtRef = useRef6(0);
|
|
4012
4175
|
const lastCommittedLenRef = useRef6(0);
|
|
@@ -4018,6 +4181,7 @@ function Repl({
|
|
|
4018
4181
|
const pendingRef = useRef6(null);
|
|
4019
4182
|
const permissionChainRef = useRef6(Promise.resolve());
|
|
4020
4183
|
const alwaysAllowedRef = useRef6(/* @__PURE__ */ new Set());
|
|
4184
|
+
const alwaysAllowedListRef = useRef6([]);
|
|
4021
4185
|
const prevModeRef = useRef6(null);
|
|
4022
4186
|
const planApprovalRef = useRef6(false);
|
|
4023
4187
|
const pendingRewindRef = useRef6(null);
|
|
@@ -4026,23 +4190,23 @@ function Repl({
|
|
|
4026
4190
|
const modelNameRef = useRef6(model);
|
|
4027
4191
|
const sessionStoreRef = useRef6(sessionStore);
|
|
4028
4192
|
const configRef = useRef6(config);
|
|
4029
|
-
|
|
4193
|
+
useEffect4(() => {
|
|
4030
4194
|
configRef.current = config;
|
|
4031
4195
|
}, [config]);
|
|
4032
4196
|
const usageRef = useRef6(initialUsage ? { ...initialUsage } : emptyUsage());
|
|
4033
4197
|
const turnChunksRef = useRef6(0);
|
|
4034
4198
|
const messagesRef = useRef6(initialDisplay);
|
|
4035
|
-
const [queueItems, setQueueItems] =
|
|
4199
|
+
const [queueItems, setQueueItems] = useState7([]);
|
|
4036
4200
|
const queueRef = useRef6(new PromptQueue());
|
|
4037
|
-
const [pendingImages, setPendingImages] =
|
|
4201
|
+
const [pendingImages, setPendingImages] = useState7([]);
|
|
4038
4202
|
const pendingImagesRef = useRef6([]);
|
|
4039
4203
|
const imageSeqRef = useRef6(1);
|
|
4040
|
-
const [inputRefill, setInputRefill] =
|
|
4204
|
+
const [inputRefill, setInputRefill] = useState7();
|
|
4041
4205
|
const refillSeqRef = useRef6(0);
|
|
4042
4206
|
const lastEscRef = useRef6(null);
|
|
4043
|
-
const [gitBranch, setGitBranch] =
|
|
4044
|
-
const [contextPercent, setContextPercent] =
|
|
4045
|
-
const [initialHistory] =
|
|
4207
|
+
const [gitBranch, setGitBranch] = useState7(null);
|
|
4208
|
+
const [contextPercent, setContextPercent] = useState7(null);
|
|
4209
|
+
const [initialHistory] = useState7(() => loadHistory());
|
|
4046
4210
|
const budgetWarnedRef = useRef6(false);
|
|
4047
4211
|
const budgetExceededRef = useRef6(false);
|
|
4048
4212
|
const runStreamRef = useRef6(
|
|
@@ -4208,7 +4372,7 @@ function Repl({
|
|
|
4208
4372
|
const attachConfirmHandler = useCallback(
|
|
4209
4373
|
(target) => {
|
|
4210
4374
|
target.confirmHandler = (req) => {
|
|
4211
|
-
if (isAllowedByRules(
|
|
4375
|
+
if (isAllowedByRules(alwaysAllowedListRef.current, req)) {
|
|
4212
4376
|
return Promise.resolve(true);
|
|
4213
4377
|
}
|
|
4214
4378
|
const ask = async () => {
|
|
@@ -4228,7 +4392,7 @@ function Repl({
|
|
|
4228
4392
|
},
|
|
4229
4393
|
[setPendingPermission, cwd, pushMessage]
|
|
4230
4394
|
);
|
|
4231
|
-
|
|
4395
|
+
useEffect4(() => {
|
|
4232
4396
|
attachConfirmHandler(backendRef.current);
|
|
4233
4397
|
return () => {
|
|
4234
4398
|
tickerStopRef.current?.();
|
|
@@ -4236,19 +4400,19 @@ function Repl({
|
|
|
4236
4400
|
pendingRef.current?.resolve(false);
|
|
4237
4401
|
};
|
|
4238
4402
|
}, [attachConfirmHandler]);
|
|
4239
|
-
|
|
4403
|
+
useEffect4(() => {
|
|
4240
4404
|
if (process.env.STAR_NO_UPDATE_CHECK === "1") return;
|
|
4241
4405
|
void checkForUpdate(VERSION).then((message) => {
|
|
4242
4406
|
if (message) pushMessage("system", message);
|
|
4243
4407
|
});
|
|
4244
4408
|
}, [pushMessage]);
|
|
4245
4409
|
const resumedAtStart = initialMessages !== void 0;
|
|
4246
|
-
|
|
4410
|
+
useEffect4(() => {
|
|
4247
4411
|
if (!resumedAtStart) return;
|
|
4248
|
-
loadTodos(cwd).then(setTodos).catch(() => {
|
|
4412
|
+
loadTodos(cwd, sessionStoreRef.current?.id).then(setTodos).catch(() => {
|
|
4249
4413
|
});
|
|
4250
4414
|
}, [cwd, resumedAtStart]);
|
|
4251
|
-
|
|
4415
|
+
useEffect4(() => {
|
|
4252
4416
|
const onUpdate = (task) => {
|
|
4253
4417
|
setBgLabels(runningTaskLabels());
|
|
4254
4418
|
pushMessage(
|
|
@@ -4342,6 +4506,11 @@ function Repl({
|
|
|
4342
4506
|
lastEscRef.current = null;
|
|
4343
4507
|
return;
|
|
4344
4508
|
}
|
|
4509
|
+
if (busyCommandRef.current) {
|
|
4510
|
+
lastEscRef.current = null;
|
|
4511
|
+
busyAbortRef.current?.abort();
|
|
4512
|
+
return;
|
|
4513
|
+
}
|
|
4345
4514
|
const busy = abortRef.current !== null || pendingRef.current !== null;
|
|
4346
4515
|
if (busy) {
|
|
4347
4516
|
lastEscRef.current = null;
|
|
@@ -4359,7 +4528,7 @@ function Repl({
|
|
|
4359
4528
|
}
|
|
4360
4529
|
lastEscRef.current = null;
|
|
4361
4530
|
if (key.shift && key.tab) {
|
|
4362
|
-
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)
|
|
4363
4532
|
return;
|
|
4364
4533
|
const current = config.permissionMode;
|
|
4365
4534
|
const index = SESSION_MODE_CYCLE.indexOf(current);
|
|
@@ -4374,6 +4543,7 @@ function Repl({
|
|
|
4374
4543
|
if (decision === "always") {
|
|
4375
4544
|
const rule = buildAllowRule(p.request);
|
|
4376
4545
|
alwaysAllowedRef.current.add(rule);
|
|
4546
|
+
alwaysAllowedListRef.current = [...alwaysAllowedRef.current];
|
|
4377
4547
|
void addAllowRule(rule).then((added) => {
|
|
4378
4548
|
pushMessage(
|
|
4379
4549
|
"system",
|
|
@@ -4404,11 +4574,13 @@ function Repl({
|
|
|
4404
4574
|
providerMetadata: reasoningEffortMetadata(config, name),
|
|
4405
4575
|
temperature: modelConfig.temperature,
|
|
4406
4576
|
streamIdleTimeoutSec: modelConfig.streamIdleTimeoutSec,
|
|
4407
|
-
streamFirstChunkTimeoutSec: modelConfig.streamFirstChunkTimeoutSec
|
|
4577
|
+
streamFirstChunkTimeoutSec: modelConfig.streamFirstChunkTimeoutSec,
|
|
4578
|
+
vision: modelConfig.vision,
|
|
4579
|
+
modelName: name
|
|
4408
4580
|
});
|
|
4409
4581
|
const prev = backendRef.current;
|
|
4410
4582
|
if (prev instanceof AgentLoop) {
|
|
4411
|
-
await loop.loadMessages([...prev.
|
|
4583
|
+
await loop.loadMessages([...prev.getStarMessages()]);
|
|
4412
4584
|
}
|
|
4413
4585
|
attachConfirmHandler(loop);
|
|
4414
4586
|
backendRef.current = loop;
|
|
@@ -4489,9 +4661,10 @@ function Repl({
|
|
|
4489
4661
|
await current.loadMessages(resumed.messages);
|
|
4490
4662
|
current.setSessionStore(store);
|
|
4491
4663
|
sessionStoreRef.current = store;
|
|
4492
|
-
|
|
4664
|
+
setTodoSession(store.id);
|
|
4665
|
+
setTodos(await loadTodos(cwd, store.id));
|
|
4493
4666
|
hydrateSnapshots(await loadSessionSnapshots(store.dir));
|
|
4494
|
-
const display = buildDisplayMessages(resumed.messages);
|
|
4667
|
+
const display = buildDisplayMessages(toCoreMessages(resumed.messages));
|
|
4495
4668
|
nextIdRef.current = display.length;
|
|
4496
4669
|
applyMessages(display);
|
|
4497
4670
|
usageRef.current = resumed.meta.usage ? { ...resumed.meta.usage } : emptyUsage();
|
|
@@ -4538,8 +4711,7 @@ function Repl({
|
|
|
4538
4711
|
flushedRef.current = { ...flushedRef.current, streamed: "" };
|
|
4539
4712
|
setStreamingText("");
|
|
4540
4713
|
};
|
|
4541
|
-
tickerStopRef.current = startTicker((
|
|
4542
|
-
setSpinnerTick(tick);
|
|
4714
|
+
tickerStopRef.current = startTicker(() => {
|
|
4543
4715
|
const uncommitted = streamedRef.current.slice(lastCommittedLenRef.current);
|
|
4544
4716
|
const split = splitCommittableLines(uncommitted, 8);
|
|
4545
4717
|
if (split) {
|
|
@@ -4755,7 +4927,7 @@ function Repl({
|
|
|
4755
4927
|
sessionCostUsd
|
|
4756
4928
|
]
|
|
4757
4929
|
);
|
|
4758
|
-
|
|
4930
|
+
useEffect4(() => {
|
|
4759
4931
|
runStreamRef.current = runStream;
|
|
4760
4932
|
}, [runStream]);
|
|
4761
4933
|
const handlePlanDecision = useCallback(
|
|
@@ -4913,7 +5085,7 @@ ${lines.join("\n")}`;
|
|
|
4913
5085
|
if (!(current instanceof AgentLoop) || !store) {
|
|
4914
5086
|
return "Nothing to fork \u2014 no active session.";
|
|
4915
5087
|
}
|
|
4916
|
-
const messages2 = await store.
|
|
5088
|
+
const messages2 = await store.starMessages();
|
|
4917
5089
|
if (messages2.length === 0) {
|
|
4918
5090
|
return "Nothing to fork \u2014 the current session has no messages yet.";
|
|
4919
5091
|
}
|
|
@@ -4924,6 +5096,7 @@ ${lines.join("\n")}`;
|
|
|
4924
5096
|
await fork.setTitle(title);
|
|
4925
5097
|
current.setSessionStore(fork);
|
|
4926
5098
|
sessionStoreRef.current = fork;
|
|
5099
|
+
setTodoSession(fork.id);
|
|
4927
5100
|
return `Forked into new session ${fork.id} ("${title}") and switched to it. Checkpoints and rewind history do not carry over.`;
|
|
4928
5101
|
},
|
|
4929
5102
|
newSession: async () => {
|
|
@@ -4935,8 +5108,9 @@ ${lines.join("\n")}`;
|
|
|
4935
5108
|
const store = await SessionStore.create(cwd, modelNameRef.current);
|
|
4936
5109
|
current.setSessionStore(store);
|
|
4937
5110
|
sessionStoreRef.current = store;
|
|
4938
|
-
|
|
4939
|
-
|
|
5111
|
+
setTodoSession(store.id);
|
|
5112
|
+
const [first] = current.getStarMessages();
|
|
5113
|
+
await current.loadMessages(first?.message.role === "system" ? [first] : []);
|
|
4940
5114
|
clearSnapshots();
|
|
4941
5115
|
redrawMessages([]);
|
|
4942
5116
|
resetTodos();
|
|
@@ -4981,20 +5155,16 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
|
|
|
4981
5155
|
return `Busy \u2014 /${busyCommandRef.current} is still running.`;
|
|
4982
5156
|
}
|
|
4983
5157
|
busyCommandRef.current = "compact";
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
} catch {
|
|
4988
|
-
summaryModel = null;
|
|
4989
|
-
}
|
|
4990
|
-
tickerStopRef.current = startTicker((tick) => setSpinnerTick(tick));
|
|
5158
|
+
const controller = new AbortController();
|
|
5159
|
+
busyAbortRef.current = controller;
|
|
5160
|
+
turnStartedAtRef.current = Date.now();
|
|
4991
5161
|
setActivity("compacting context\u2026");
|
|
4992
5162
|
try {
|
|
4993
5163
|
const result = await compactSession({
|
|
4994
5164
|
backend: backendRef.current,
|
|
4995
5165
|
sessionStore: sessionStoreRef.current,
|
|
4996
5166
|
config,
|
|
4997
|
-
|
|
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-OO7C6V7M.js.map
|