@cryer/star-cli 0.2.0 → 0.2.1
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 +24 -3
- package/dist/{chunk-RLQXGX3N.js → chunk-MJ2O7NWI.js} +240 -85
- package/dist/chunk-MJ2O7NWI.js.map +1 -0
- package/dist/{loop-XIILV7GY.js → loop-R3RQCNFT.js} +2 -2
- package/dist/main.js +160 -139
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-RLQXGX3N.js.map +0 -1
- /package/dist/{loop-XIILV7GY.js.map → loop-R3RQCNFT.js.map} +0 -0
package/README.md
CHANGED
|
@@ -73,7 +73,14 @@ apiKeyEnv = "OPENAI_API_KEY"
|
|
|
73
73
|
name = "gpt"
|
|
74
74
|
provider = "openai"
|
|
75
75
|
model = "gpt-4o"
|
|
76
|
-
# optional per-model
|
|
76
|
+
# optional per-model context window — overrides the top-level contextMaxTokens
|
|
77
|
+
# for compaction and the ctx % in the status bar
|
|
78
|
+
# contextMaxTokens = 272000
|
|
79
|
+
# optional per-model pricing in USD per 1M tokens — enables the $ estimate in
|
|
80
|
+
# /cost and /usage. promptPrice prices input tokens (system prompt, history,
|
|
81
|
+
# @file contents, tool results — resent every turn, so the bulk of usage);
|
|
82
|
+
# completionPrice prices output tokens (the model's replies and tool calls —
|
|
83
|
+
# less volume, usually the pricier rate). BOTH fields are required together.
|
|
77
84
|
promptPrice = 2.5
|
|
78
85
|
completionPrice = 10
|
|
79
86
|
|
|
@@ -135,6 +142,7 @@ star --clear-sessions all delete every stored session
|
|
|
135
142
|
| `/rewind [n]` | list file-change checkpoints, or rewind to just before checkpoint `n`: restore every file changed since then and retract the matching conversation messages (asks for confirmation first) |
|
|
136
143
|
| `/init [force]` | scan the project and generate an AGENTS.md (LLM-polished when a model is available) |
|
|
137
144
|
| `/doctor` | environment self-check (Node, shell, config, API key status, sessions dir writability) |
|
|
145
|
+
| `/skills` | list available skills (project scope overrides user scope) |
|
|
138
146
|
| `/commit [instructions]` | analyze uncommitted changes and let the agent stage + commit them with a Conventional Commits message (git add/commit runs through the normal permission gate) |
|
|
139
147
|
| `/diff` | show uncommitted changes client-side: `git status --short` plus a colored staged/unstaged diff (no model call; huge diffs are truncated at 2000 lines) |
|
|
140
148
|
| `/copy [all]` | copy the last assistant reply to the clipboard (`all`: the whole conversation as plain text) |
|
|
@@ -146,7 +154,7 @@ Keys: `ESC` / `Ctrl+C` interrupts the current stream; on a permission prompt: `y
|
|
|
146
154
|
|
|
147
155
|
While the model is working, a spinner (`- \ | /`) shows `star is thinking…`; reasoning models also stream a dimmed tail of their thinking (last ~200 chars), which collapses to a one-line summary once the answer starts.
|
|
148
156
|
|
|
149
|
-
The status bar shows the working directory (full path on wide terminals), the git branch, the model, the permission mode, context usage as a percentage of `contextMaxTokens
|
|
157
|
+
The status bar shows the working directory (full path on wide terminals), the git branch, the model, the permission mode, context usage as a percentage of the model's context window (its own `contextMaxTokens` when set, else the top-level one), the session cost when the model has pricing configured, and total tokens.
|
|
150
158
|
|
|
151
159
|
## !shell passthrough
|
|
152
160
|
|
|
@@ -217,6 +225,19 @@ Review the following code and list concrete issues: $ARGUMENTS
|
|
|
217
225
|
|
|
218
226
|
The REPL also checks npm for a newer release on startup (async, non-blocking; disable with `STAR_NO_UPDATE_CHECK=1`).
|
|
219
227
|
|
|
228
|
+
## Skills
|
|
229
|
+
|
|
230
|
+
Skills are reusable instruction packs the agent loads on demand. Drop a `SKILL.md` into `.star/skills/<name>/` (project) or `~/.star-cli/skills/<name>/` (user); project skills override user skills of the same name. Each `SKILL.md` starts with frontmatter (`name` optional, defaults to the directory name; `description` required), followed by the instructions body:
|
|
231
|
+
|
|
232
|
+
```markdown
|
|
233
|
+
---
|
|
234
|
+
description: Review code for common issues
|
|
235
|
+
---
|
|
236
|
+
Check error handling, naming, and test coverage. Report concrete findings with file:line references.
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Available skills are listed in the system prompt (name + description only, so they cost almost no tokens until used). When a request matches one, the agent calls the read-level `skill` tool to load the full body — supporting files next to the `SKILL.md` (scripts, templates, examples) are resolved relative to the skill's directory. Skills added mid-session are picked up on the next turn; `/skills` lists what's currently visible. Bodies are capped at 32KB.
|
|
240
|
+
|
|
220
241
|
## @file mentions
|
|
221
242
|
|
|
222
243
|
Prefix a path with `@` in any prompt to attach its content (REPL and print mode alike):
|
|
@@ -237,7 +258,7 @@ Missing, binary, oversized (>100KB for text, >5MB for images), or sensitive file
|
|
|
237
258
|
|
|
238
259
|
## Built-in tools
|
|
239
260
|
|
|
240
|
-
`read_file`, `write_file`, `edit_file`, `glob`, `grep`, `bash`, `web_fetch`, `web_search` (DuckDuckGo, no API key needed), `todo_read`, `todo_write`, `task_list`, `task_output`, `task_kill`, `subagent` — each declares a permission level (`read` / `write` / `exec`) enforced by the permission gate. Hard safety rules (dangerous shell commands, paths outside the working directory, secret files like `.env` / private keys) are denied in `ask` / `auto` / `readonly` and cannot be overridden by allow-rules.
|
|
261
|
+
`read_file`, `write_file`, `edit_file`, `glob`, `grep`, `bash`, `web_fetch`, `web_search` (DuckDuckGo, no API key needed), `todo_read`, `todo_write`, `task_list`, `task_output`, `task_kill`, `subagent`, `skill` — each declares a permission level (`read` / `write` / `exec`) enforced by the permission gate. Hard safety rules (dangerous shell commands, paths outside the working directory, secret files like `.env` / private keys) are denied in `ask` / `auto` / `readonly` and cannot be overridden by allow-rules.
|
|
241
262
|
|
|
242
263
|
The `subagent` tool (`exec` level, so it is hidden in plan mode and denied in readonly) spawns a child agent loop with the same built-in tools to handle a focused, self-contained subtask — research, exploration, or an isolated change — and returns the child's final report as the tool result. Subagents run one level deep (a subagent cannot spawn further subagents), share the parent's permission mode and confirmation prompt, and their conversation is not persisted to the session.
|
|
243
264
|
|
|
@@ -10,23 +10,31 @@ import { generateText } from "ai";
|
|
|
10
10
|
var CHARS_PER_TOKEN = 4;
|
|
11
11
|
var MESSAGE_OVERHEAD = 4;
|
|
12
12
|
var IMAGE_PART_TOKENS = 1024;
|
|
13
|
+
var CJK_RE = /[\u3000-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]/;
|
|
14
|
+
function textWeight(text) {
|
|
15
|
+
let cjk = 0;
|
|
16
|
+
for (const ch of text) {
|
|
17
|
+
if (CJK_RE.test(ch)) cjk++;
|
|
18
|
+
}
|
|
19
|
+
return text.length + cjk * (CHARS_PER_TOKEN - 1);
|
|
20
|
+
}
|
|
13
21
|
function contentCharLength(message) {
|
|
14
22
|
if (message.role === "tool") {
|
|
15
23
|
let chars2 = 0;
|
|
16
24
|
for (const part of message.content) {
|
|
17
|
-
chars2 += JSON.stringify(part.result ?? null)
|
|
25
|
+
chars2 += textWeight(JSON.stringify(part.result ?? null));
|
|
18
26
|
}
|
|
19
27
|
return chars2;
|
|
20
28
|
}
|
|
21
29
|
if (typeof message.content === "string") {
|
|
22
|
-
return message.content
|
|
30
|
+
return textWeight(message.content);
|
|
23
31
|
}
|
|
24
32
|
let chars = 0;
|
|
25
33
|
for (const part of message.content) {
|
|
26
34
|
if (part.type === "text" || part.type === "reasoning") {
|
|
27
|
-
chars += part.text
|
|
35
|
+
chars += textWeight(part.text);
|
|
28
36
|
} else if (part.type === "tool-call") {
|
|
29
|
-
chars += part.toolName
|
|
37
|
+
chars += textWeight(part.toolName) + textWeight(JSON.stringify(part.args ?? null));
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
40
|
return chars;
|
|
@@ -720,14 +728,150 @@ ${body}`;
|
|
|
720
728
|
return block;
|
|
721
729
|
}
|
|
722
730
|
|
|
731
|
+
// src/agent/skills.ts
|
|
732
|
+
import { readFileSync as readFileSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
733
|
+
import path5 from "path";
|
|
734
|
+
import { z } from "zod";
|
|
735
|
+
|
|
736
|
+
// src/config/paths.ts
|
|
737
|
+
import os from "os";
|
|
738
|
+
import path4 from "path";
|
|
739
|
+
function starHome() {
|
|
740
|
+
return process.env.STAR_HOME ?? path4.join(os.homedir(), ".star-cli");
|
|
741
|
+
}
|
|
742
|
+
function globalConfigPath() {
|
|
743
|
+
return path4.join(starHome(), "config.toml");
|
|
744
|
+
}
|
|
745
|
+
function sessionsDir() {
|
|
746
|
+
return path4.join(starHome(), "sessions");
|
|
747
|
+
}
|
|
748
|
+
function projectConfigPath(cwd) {
|
|
749
|
+
return path4.join(cwd, ".star", "config.toml");
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/agent/skills.ts
|
|
753
|
+
var SKILL_FILE = "SKILL.md";
|
|
754
|
+
var SKILL_MAX_CHARS = 32 * 1024;
|
|
755
|
+
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
756
|
+
function parseFrontmatter(raw) {
|
|
757
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
758
|
+
if (!match) return null;
|
|
759
|
+
const meta = {};
|
|
760
|
+
for (const line of (match[1] ?? "").split(/\r?\n/)) {
|
|
761
|
+
const kv = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
762
|
+
if (kv?.[1]) meta[kv[1]] = (kv[2] ?? "").trim();
|
|
763
|
+
}
|
|
764
|
+
return { meta, body: raw.slice(match[0].length).trim() };
|
|
765
|
+
}
|
|
766
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
767
|
+
function parseSkillFile(filePath, dirName, scope) {
|
|
768
|
+
let mtimeMs;
|
|
769
|
+
try {
|
|
770
|
+
mtimeMs = statSync2(filePath).mtimeMs;
|
|
771
|
+
} catch {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
const hit = cache2.get(filePath);
|
|
775
|
+
if (hit && hit.mtimeMs === mtimeMs) return hit.skill;
|
|
776
|
+
let skill = null;
|
|
777
|
+
try {
|
|
778
|
+
const parsed = parseFrontmatter(readFileSync2(filePath, "utf8"));
|
|
779
|
+
const name = parsed?.meta.name || dirName;
|
|
780
|
+
const description = parsed?.meta.description;
|
|
781
|
+
if (parsed && NAME_PATTERN.test(name) && description) {
|
|
782
|
+
skill = { name, description, filePath, dir: path5.dirname(filePath), scope };
|
|
783
|
+
}
|
|
784
|
+
} catch {
|
|
785
|
+
skill = null;
|
|
786
|
+
}
|
|
787
|
+
cache2.set(filePath, { mtimeMs, skill });
|
|
788
|
+
return skill;
|
|
789
|
+
}
|
|
790
|
+
function scanDir(base, scope, into) {
|
|
791
|
+
let entries;
|
|
792
|
+
try {
|
|
793
|
+
entries = readdirSync(base);
|
|
794
|
+
} catch {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
for (const entry of entries.sort()) {
|
|
798
|
+
if (!NAME_PATTERN.test(entry)) continue;
|
|
799
|
+
const parsed = parseSkillFile(path5.join(base, entry, SKILL_FILE), entry, scope);
|
|
800
|
+
if (parsed) into.set(parsed.name, parsed);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function discoverSkills(cwd, home = starHome()) {
|
|
804
|
+
const byName = /* @__PURE__ */ new Map();
|
|
805
|
+
scanDir(path5.join(home, "skills"), "user", byName);
|
|
806
|
+
scanDir(path5.join(cwd, ".star", "skills"), "project", byName);
|
|
807
|
+
return [...byName.values()];
|
|
808
|
+
}
|
|
809
|
+
function formatSkillsBlock(skills) {
|
|
810
|
+
const lines = skills.map((s) => `- ${s.name}: ${s.description}`);
|
|
811
|
+
return [
|
|
812
|
+
"# Available skills",
|
|
813
|
+
"When the user's request matches one of these skills, call the skill tool with its name to load the full instructions before proceeding:",
|
|
814
|
+
...lines
|
|
815
|
+
].join("\n");
|
|
816
|
+
}
|
|
817
|
+
function loadSkillBody(skill) {
|
|
818
|
+
let parsed = null;
|
|
819
|
+
try {
|
|
820
|
+
parsed = parseFrontmatter(readFileSync2(skill.filePath, "utf8"));
|
|
821
|
+
} catch {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
if (!parsed?.body) return null;
|
|
825
|
+
if (parsed.body.length > SKILL_MAX_CHARS) {
|
|
826
|
+
return `${parsed.body.slice(0, SKILL_MAX_CHARS)}
|
|
827
|
+
|
|
828
|
+
[skill truncated to ${SKILL_MAX_CHARS} characters]`;
|
|
829
|
+
}
|
|
830
|
+
return parsed.body;
|
|
831
|
+
}
|
|
832
|
+
function createSkillTool(deps) {
|
|
833
|
+
return {
|
|
834
|
+
name: "skill",
|
|
835
|
+
description: "Load the full instructions of a skill by name. Available skills are listed in the system prompt when present; call this before starting work that matches one of them.",
|
|
836
|
+
permission: "read",
|
|
837
|
+
parameters: z.object({
|
|
838
|
+
name: z.string().describe("Name of the skill to load, as listed in the system prompt.")
|
|
839
|
+
}),
|
|
840
|
+
async execute(args) {
|
|
841
|
+
const skills = discoverSkills(deps.cwd, deps.home);
|
|
842
|
+
const skill = skills.find((s) => s.name === args.name);
|
|
843
|
+
if (!skill) {
|
|
844
|
+
const available = skills.map((s) => s.name).join(", ") || "none";
|
|
845
|
+
return {
|
|
846
|
+
content: `Unknown skill: "${args.name}". Available skills: ${available}`,
|
|
847
|
+
isError: true
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
const body = loadSkillBody(skill);
|
|
851
|
+
if (!body) {
|
|
852
|
+
return { content: `Skill "${skill.name}" is empty or unreadable.`, isError: true };
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
content: [
|
|
856
|
+
`# Skill: ${skill.name}`,
|
|
857
|
+
"",
|
|
858
|
+
body,
|
|
859
|
+
"",
|
|
860
|
+
`Base directory for relative file references in this skill: ${skill.dir}`
|
|
861
|
+
].join("\n")
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
723
867
|
// src/agent/subagent.ts
|
|
724
|
-
import { z as
|
|
868
|
+
import { z as z12 } from "zod";
|
|
725
869
|
|
|
726
870
|
// src/tools/bash.ts
|
|
727
871
|
import { spawn as spawn3 } from "child_process";
|
|
728
872
|
import { existsSync } from "fs";
|
|
729
|
-
import
|
|
730
|
-
import { z } from "zod";
|
|
873
|
+
import path6 from "path";
|
|
874
|
+
import { z as z2 } from "zod";
|
|
731
875
|
|
|
732
876
|
// src/tasks/manager.ts
|
|
733
877
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -846,11 +990,11 @@ var MAX_OUTPUT = 3e4;
|
|
|
846
990
|
var DEFAULT_TIMEOUT = 120;
|
|
847
991
|
var MAX_TIMEOUT = 600;
|
|
848
992
|
function findOnPath(exe, exclude) {
|
|
849
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
993
|
+
for (const dir of (process.env.PATH ?? "").split(path6.delimiter)) {
|
|
850
994
|
if (!dir || exclude?.(dir)) {
|
|
851
995
|
continue;
|
|
852
996
|
}
|
|
853
|
-
const full =
|
|
997
|
+
const full = path6.join(dir, exe);
|
|
854
998
|
if (existsSync(full)) {
|
|
855
999
|
return full;
|
|
856
1000
|
}
|
|
@@ -868,7 +1012,7 @@ function resolveShell() {
|
|
|
868
1012
|
}
|
|
869
1013
|
const gitExe = findOnPath("git.exe");
|
|
870
1014
|
if (gitExe) {
|
|
871
|
-
const sibling =
|
|
1015
|
+
const sibling = path6.join(path6.dirname(path6.dirname(gitExe)), "bin", "bash.exe");
|
|
872
1016
|
if (existsSync(sibling)) {
|
|
873
1017
|
return { shell: sibling, wrap: (c) => ["-c", c], label: "bash" };
|
|
874
1018
|
}
|
|
@@ -885,11 +1029,11 @@ function resolveShell() {
|
|
|
885
1029
|
const comspec = process.env.ComSpec ?? "cmd.exe";
|
|
886
1030
|
return { shell: comspec, wrap: (c) => ["/d", "/s", "/c", c], label: "cmd" };
|
|
887
1031
|
}
|
|
888
|
-
var schema =
|
|
889
|
-
command:
|
|
890
|
-
timeout:
|
|
891
|
-
description:
|
|
892
|
-
run_in_background:
|
|
1032
|
+
var schema = z2.object({
|
|
1033
|
+
command: z2.string().describe("Shell command to execute"),
|
|
1034
|
+
timeout: z2.number().positive().max(MAX_TIMEOUT).optional().describe("Timeout in seconds (default 120, max 600)"),
|
|
1035
|
+
description: z2.string().optional().describe("Short description of what the command does"),
|
|
1036
|
+
run_in_background: z2.boolean().optional().describe(
|
|
893
1037
|
"Run the command in the background and return a task id immediately (default false). Use task_output/task_kill to inspect or stop it."
|
|
894
1038
|
)
|
|
895
1039
|
});
|
|
@@ -992,13 +1136,13 @@ Command timed out after ${timeoutSeconds}s`,
|
|
|
992
1136
|
|
|
993
1137
|
// src/tools/fs/edit.ts
|
|
994
1138
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
995
|
-
import
|
|
996
|
-
import { z as
|
|
997
|
-
var schema2 =
|
|
998
|
-
path:
|
|
999
|
-
old_string:
|
|
1000
|
-
new_string:
|
|
1001
|
-
replace_all:
|
|
1139
|
+
import path7 from "path";
|
|
1140
|
+
import { z as z3 } from "zod";
|
|
1141
|
+
var schema2 = z3.object({
|
|
1142
|
+
path: z3.string().describe("File path, absolute or relative to the working directory"),
|
|
1143
|
+
old_string: z3.string().min(1).describe("Exact text to replace"),
|
|
1144
|
+
new_string: z3.string().describe("Replacement text"),
|
|
1145
|
+
replace_all: z3.boolean().optional().describe("Replace every occurrence instead of requiring a unique match")
|
|
1002
1146
|
});
|
|
1003
1147
|
var editFileTool = {
|
|
1004
1148
|
name: "edit_file",
|
|
@@ -1006,7 +1150,7 @@ var editFileTool = {
|
|
|
1006
1150
|
permission: "write",
|
|
1007
1151
|
parameters: schema2,
|
|
1008
1152
|
async execute(args, ctx) {
|
|
1009
|
-
const filePath =
|
|
1153
|
+
const filePath = path7.resolve(ctx.cwd, args.path);
|
|
1010
1154
|
let content;
|
|
1011
1155
|
try {
|
|
1012
1156
|
content = await readFile2(filePath, "utf8");
|
|
@@ -1049,12 +1193,12 @@ var editFileTool = {
|
|
|
1049
1193
|
};
|
|
1050
1194
|
|
|
1051
1195
|
// src/tools/fs/glob.ts
|
|
1052
|
-
import
|
|
1053
|
-
import { z as
|
|
1196
|
+
import path9 from "path";
|
|
1197
|
+
import { z as z4 } from "zod";
|
|
1054
1198
|
|
|
1055
1199
|
// src/tools/fs/util.ts
|
|
1056
1200
|
import { readdir, stat } from "fs/promises";
|
|
1057
|
-
import
|
|
1201
|
+
import path8 from "path";
|
|
1058
1202
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
1059
1203
|
async function walkFiles(root, skipDirs = SKIP_DIRS) {
|
|
1060
1204
|
const out = [];
|
|
@@ -1064,7 +1208,7 @@ async function walkFiles(root, skipDirs = SKIP_DIRS) {
|
|
|
1064
1208
|
return;
|
|
1065
1209
|
}
|
|
1066
1210
|
for (const entry of entries) {
|
|
1067
|
-
const abs =
|
|
1211
|
+
const abs = path8.join(dir, entry.name);
|
|
1068
1212
|
const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
1069
1213
|
if (entry.isDirectory()) {
|
|
1070
1214
|
if (!skipDirs.has(entry.name)) {
|
|
@@ -1120,7 +1264,7 @@ function matchesGlob(pattern, relPath) {
|
|
|
1120
1264
|
return globToRegExp(normalized).test(relPath);
|
|
1121
1265
|
}
|
|
1122
1266
|
function isSensitivePath2(filePath) {
|
|
1123
|
-
const base =
|
|
1267
|
+
const base = path8.basename(filePath);
|
|
1124
1268
|
if (base === ".env.example" || base === ".env.sample" || base === ".env.template") {
|
|
1125
1269
|
return false;
|
|
1126
1270
|
}
|
|
@@ -1135,11 +1279,11 @@ function isSensitivePath2(filePath) {
|
|
|
1135
1279
|
|
|
1136
1280
|
// src/tools/fs/glob.ts
|
|
1137
1281
|
var MAX_RESULTS = 100;
|
|
1138
|
-
var schema3 =
|
|
1139
|
-
pattern:
|
|
1282
|
+
var schema3 = z4.object({
|
|
1283
|
+
pattern: z4.string().describe(
|
|
1140
1284
|
"Glob pattern, e.g. 'src/**/*.ts'. Supports **, * and ?. A bare pattern like '*.ts' matches basenames recursively."
|
|
1141
1285
|
),
|
|
1142
|
-
path:
|
|
1286
|
+
path: z4.string().optional().describe("Directory to search, defaults to the working directory")
|
|
1143
1287
|
});
|
|
1144
1288
|
var globTool = {
|
|
1145
1289
|
name: "glob",
|
|
@@ -1147,7 +1291,7 @@ var globTool = {
|
|
|
1147
1291
|
permission: "read",
|
|
1148
1292
|
parameters: schema3,
|
|
1149
1293
|
async execute(args, ctx) {
|
|
1150
|
-
const root =
|
|
1294
|
+
const root = path9.resolve(ctx.cwd, args.path ?? ".");
|
|
1151
1295
|
const files = await walkFiles(root);
|
|
1152
1296
|
const matched = files.filter((f) => matchesGlob(args.pattern, f.rel)).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RESULTS);
|
|
1153
1297
|
if (matched.length === 0) {
|
|
@@ -1159,14 +1303,14 @@ var globTool = {
|
|
|
1159
1303
|
|
|
1160
1304
|
// src/tools/fs/grep.ts
|
|
1161
1305
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1162
|
-
import
|
|
1163
|
-
import { z as
|
|
1306
|
+
import path10 from "path";
|
|
1307
|
+
import { z as z5 } from "zod";
|
|
1164
1308
|
var MAX_MATCHES = 250;
|
|
1165
|
-
var schema4 =
|
|
1166
|
-
pattern:
|
|
1167
|
-
path:
|
|
1168
|
-
glob:
|
|
1169
|
-
ignoreCase:
|
|
1309
|
+
var schema4 = z5.object({
|
|
1310
|
+
pattern: z5.string().describe("Regular expression to search for"),
|
|
1311
|
+
path: z5.string().optional().describe("File or directory to search, defaults to the working directory"),
|
|
1312
|
+
glob: z5.string().optional().describe("Glob pattern to filter which files are searched, e.g. '*.ts'"),
|
|
1313
|
+
ignoreCase: z5.boolean().optional().describe("Case-insensitive matching")
|
|
1170
1314
|
});
|
|
1171
1315
|
var grepTool = {
|
|
1172
1316
|
name: "grep",
|
|
@@ -1180,12 +1324,12 @@ var grepTool = {
|
|
|
1180
1324
|
} catch (err) {
|
|
1181
1325
|
return { content: `Invalid regular expression: ${err.message}`, isError: true };
|
|
1182
1326
|
}
|
|
1183
|
-
const root =
|
|
1327
|
+
const root = path10.resolve(ctx.cwd, args.path ?? ".");
|
|
1184
1328
|
let files;
|
|
1185
1329
|
try {
|
|
1186
1330
|
const st = await stat2(root);
|
|
1187
1331
|
if (st.isFile()) {
|
|
1188
|
-
files = [{ abs: root, rel:
|
|
1332
|
+
files = [{ abs: root, rel: path10.basename(root), mtimeMs: st.mtimeMs }];
|
|
1189
1333
|
} else {
|
|
1190
1334
|
files = await walkFiles(root);
|
|
1191
1335
|
}
|
|
@@ -1232,14 +1376,14 @@ var grepTool = {
|
|
|
1232
1376
|
|
|
1233
1377
|
// src/tools/fs/read.ts
|
|
1234
1378
|
import { readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
1235
|
-
import
|
|
1236
|
-
import { z as
|
|
1379
|
+
import path11 from "path";
|
|
1380
|
+
import { z as z6 } from "zod";
|
|
1237
1381
|
var DEFAULT_LIMIT = 2e3;
|
|
1238
1382
|
var MAX_CHARS = 100 * 1024;
|
|
1239
|
-
var schema5 =
|
|
1240
|
-
path:
|
|
1241
|
-
offset:
|
|
1242
|
-
limit:
|
|
1383
|
+
var schema5 = z6.object({
|
|
1384
|
+
path: z6.string().describe("File path, absolute or relative to the working directory"),
|
|
1385
|
+
offset: z6.number().int().positive().optional().describe("1-based line number to start reading from"),
|
|
1386
|
+
limit: z6.number().int().positive().optional().describe("Maximum number of lines to read (default 2000)")
|
|
1243
1387
|
});
|
|
1244
1388
|
var readFileTool = {
|
|
1245
1389
|
name: "read_file",
|
|
@@ -1247,7 +1391,7 @@ var readFileTool = {
|
|
|
1247
1391
|
permission: "read",
|
|
1248
1392
|
parameters: schema5,
|
|
1249
1393
|
async execute(args, ctx) {
|
|
1250
|
-
const filePath =
|
|
1394
|
+
const filePath = path11.resolve(ctx.cwd, args.path);
|
|
1251
1395
|
if (isSensitivePath2(filePath)) {
|
|
1252
1396
|
return { content: `Refused to read sensitive file: ${args.path}`, isError: true };
|
|
1253
1397
|
}
|
|
@@ -1293,11 +1437,11 @@ var readFileTool = {
|
|
|
1293
1437
|
|
|
1294
1438
|
// src/tools/fs/write.ts
|
|
1295
1439
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
1296
|
-
import
|
|
1297
|
-
import { z as
|
|
1298
|
-
var schema6 =
|
|
1299
|
-
path:
|
|
1300
|
-
content:
|
|
1440
|
+
import path12 from "path";
|
|
1441
|
+
import { z as z7 } from "zod";
|
|
1442
|
+
var schema6 = z7.object({
|
|
1443
|
+
path: z7.string().describe("File path, absolute or relative to the working directory"),
|
|
1444
|
+
content: z7.string().describe("Full content to write to the file")
|
|
1301
1445
|
});
|
|
1302
1446
|
var writeFileTool = {
|
|
1303
1447
|
name: "write_file",
|
|
@@ -1305,10 +1449,10 @@ var writeFileTool = {
|
|
|
1305
1449
|
permission: "write",
|
|
1306
1450
|
parameters: schema6,
|
|
1307
1451
|
async execute(args, ctx) {
|
|
1308
|
-
const filePath =
|
|
1452
|
+
const filePath = path12.resolve(ctx.cwd, args.path);
|
|
1309
1453
|
const snapshot = await captureSnapshot(filePath, "write_file");
|
|
1310
1454
|
try {
|
|
1311
|
-
await mkdir2(
|
|
1455
|
+
await mkdir2(path12.dirname(filePath), { recursive: true });
|
|
1312
1456
|
await writeFile3(filePath, args.content, "utf8");
|
|
1313
1457
|
} catch (err) {
|
|
1314
1458
|
return { content: `Failed to write ${args.path}: ${err.message}`, isError: true };
|
|
@@ -1319,7 +1463,7 @@ var writeFileTool = {
|
|
|
1319
1463
|
};
|
|
1320
1464
|
|
|
1321
1465
|
// src/tools/tasks.ts
|
|
1322
|
-
import { z as
|
|
1466
|
+
import { z as z8 } from "zod";
|
|
1323
1467
|
|
|
1324
1468
|
// src/tasks/format.ts
|
|
1325
1469
|
function formatDuration(startedAt, endedAt) {
|
|
@@ -1365,14 +1509,14 @@ function formatTaskFinished(task) {
|
|
|
1365
1509
|
}
|
|
1366
1510
|
|
|
1367
1511
|
// src/tools/tasks.ts
|
|
1368
|
-
var idSchema =
|
|
1369
|
-
id:
|
|
1512
|
+
var idSchema = z8.object({
|
|
1513
|
+
id: z8.string().describe("Task id, e.g. task-1")
|
|
1370
1514
|
});
|
|
1371
1515
|
var taskListTool = {
|
|
1372
1516
|
name: "task_list",
|
|
1373
1517
|
description: "List background shell tasks with status, runtime, and exit code.",
|
|
1374
1518
|
permission: "read",
|
|
1375
|
-
parameters:
|
|
1519
|
+
parameters: z8.object({}),
|
|
1376
1520
|
execute() {
|
|
1377
1521
|
return Promise.resolve({ content: formatTaskList(defaultTaskManager.list()) });
|
|
1378
1522
|
}
|
|
@@ -1415,19 +1559,19 @@ var taskKillTool = {
|
|
|
1415
1559
|
|
|
1416
1560
|
// src/tools/todo.ts
|
|
1417
1561
|
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
1418
|
-
import
|
|
1419
|
-
import { z as
|
|
1420
|
-
var todoItemSchema =
|
|
1421
|
-
id:
|
|
1422
|
-
title:
|
|
1423
|
-
status:
|
|
1562
|
+
import path13 from "path";
|
|
1563
|
+
import { z as z9 } from "zod";
|
|
1564
|
+
var todoItemSchema = z9.object({
|
|
1565
|
+
id: z9.number().int(),
|
|
1566
|
+
title: z9.string(),
|
|
1567
|
+
status: z9.enum(["pending", "in_progress", "done"])
|
|
1424
1568
|
});
|
|
1425
|
-
var writeSchema =
|
|
1426
|
-
todos:
|
|
1569
|
+
var writeSchema = z9.object({
|
|
1570
|
+
todos: z9.array(todoItemSchema).describe("The full todo list, replacing the current one")
|
|
1427
1571
|
});
|
|
1428
|
-
var readSchema =
|
|
1572
|
+
var readSchema = z9.object({});
|
|
1429
1573
|
function fileFor(cwd) {
|
|
1430
|
-
return
|
|
1574
|
+
return path13.join(cwd, ".star", "todos.json");
|
|
1431
1575
|
}
|
|
1432
1576
|
var TodoStore = class {
|
|
1433
1577
|
items = /* @__PURE__ */ new Map();
|
|
@@ -1456,7 +1600,7 @@ var TodoStore = class {
|
|
|
1456
1600
|
}
|
|
1457
1601
|
}
|
|
1458
1602
|
async save(cwd) {
|
|
1459
|
-
await mkdir3(
|
|
1603
|
+
await mkdir3(path13.join(cwd, ".star"), { recursive: true });
|
|
1460
1604
|
await writeFile4(fileFor(cwd), `${JSON.stringify(this.list(), null, 2)}
|
|
1461
1605
|
`);
|
|
1462
1606
|
}
|
|
@@ -1509,13 +1653,13 @@ function createTodoTools(store = defaultStore) {
|
|
|
1509
1653
|
}
|
|
1510
1654
|
|
|
1511
1655
|
// src/tools/web/fetch.ts
|
|
1512
|
-
import { z as
|
|
1656
|
+
import { z as z10 } from "zod";
|
|
1513
1657
|
var DEFAULT_MAX_CHARS = 2e4;
|
|
1514
1658
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
1515
1659
|
var TIMEOUT_MS = 3e4;
|
|
1516
|
-
var schema7 =
|
|
1517
|
-
url:
|
|
1518
|
-
maxChars:
|
|
1660
|
+
var schema7 = z10.object({
|
|
1661
|
+
url: z10.string().describe("The http(s) URL to fetch"),
|
|
1662
|
+
maxChars: z10.number().int().positive().optional().describe("Maximum characters of text to return (default 20000)")
|
|
1519
1663
|
});
|
|
1520
1664
|
function decodeEntities(s) {
|
|
1521
1665
|
return s.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n))).replace(/&#x([0-9a-fA-F]+);/gi, (_, n) => String.fromCodePoint(Number.parseInt(n, 16))).replace(/&(quot|lt|gt|nbsp);/g, (m) => {
|
|
@@ -1643,15 +1787,15 @@ var webFetchTool = {
|
|
|
1643
1787
|
};
|
|
1644
1788
|
|
|
1645
1789
|
// src/tools/web/search.ts
|
|
1646
|
-
import { z as
|
|
1790
|
+
import { z as z11 } from "zod";
|
|
1647
1791
|
var TIMEOUT_MS2 = 15e3;
|
|
1648
1792
|
var DEFAULT_MAX_RESULTS = 5;
|
|
1649
1793
|
var MAX_RESULTS_LIMIT = 10;
|
|
1650
1794
|
var SEARCH_URL = "https://html.duckduckgo.com/html/?q=";
|
|
1651
1795
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
1652
|
-
var schema8 =
|
|
1653
|
-
query:
|
|
1654
|
-
maxResults:
|
|
1796
|
+
var schema8 = z11.object({
|
|
1797
|
+
query: z11.string().min(1).describe("The search query"),
|
|
1798
|
+
maxResults: z11.number().int().min(1).max(MAX_RESULTS_LIMIT).optional().describe(
|
|
1655
1799
|
`Maximum number of results to return (default ${DEFAULT_MAX_RESULTS}, max ${MAX_RESULTS_LIMIT})`
|
|
1656
1800
|
)
|
|
1657
1801
|
});
|
|
@@ -1810,12 +1954,12 @@ function createSubagentTool(deps) {
|
|
|
1810
1954
|
name: "subagent",
|
|
1811
1955
|
description: "Spawn a subagent with its own agent loop to handle a focused subtask (research, exploration, or an isolated change). The subagent has the same tools but cannot spawn further subagents. Returns the subagent's final report. Prefer this for self-contained work that would otherwise clutter the main conversation.",
|
|
1812
1956
|
permission: "exec",
|
|
1813
|
-
parameters:
|
|
1814
|
-
prompt:
|
|
1815
|
-
description:
|
|
1957
|
+
parameters: z12.object({
|
|
1958
|
+
prompt: z12.string().describe("Complete, self-contained instructions for the subagent."),
|
|
1959
|
+
description: z12.string().optional().describe("Short label for the subtask, shown in the result header.")
|
|
1816
1960
|
}),
|
|
1817
1961
|
async execute(args, ctx) {
|
|
1818
|
-
const { AgentLoop: AgentLoop2 } = await import("./loop-
|
|
1962
|
+
const { AgentLoop: AgentLoop2 } = await import("./loop-R3RQCNFT.js");
|
|
1819
1963
|
const child = new AgentLoop2({
|
|
1820
1964
|
model: deps.model,
|
|
1821
1965
|
registry: createDefaultRegistry(),
|
|
@@ -1896,6 +2040,7 @@ var AgentLoop = class {
|
|
|
1896
2040
|
if (opts.sessionStore) {
|
|
1897
2041
|
this.bindSnapshotHooks(opts.sessionStore);
|
|
1898
2042
|
}
|
|
2043
|
+
opts.registry?.register(createSkillTool({ cwd: opts.cwd }));
|
|
1899
2044
|
const depth = opts.subagentDepth ?? 0;
|
|
1900
2045
|
if (opts.registry && depth < MAX_SUBAGENT_DEPTH) {
|
|
1901
2046
|
opts.registry.register(
|
|
@@ -2029,7 +2174,8 @@ var AgentLoop = class {
|
|
|
2029
2174
|
const { config, registry, cwd } = this.opts;
|
|
2030
2175
|
const aiTools = this.buildAiTools();
|
|
2031
2176
|
for (let step = 0; step < config.maxSteps; step++) {
|
|
2032
|
-
const
|
|
2177
|
+
const maxTokens = this.opts.contextMaxTokens ?? config.contextMaxTokens;
|
|
2178
|
+
const compacted = compactMessages([...this.messages], maxTokens);
|
|
2033
2179
|
if (compacted.compacted) {
|
|
2034
2180
|
this.messages = await this.applyCompactionSummary(compacted);
|
|
2035
2181
|
}
|
|
@@ -2140,7 +2286,9 @@ var AgentLoop = class {
|
|
|
2140
2286
|
// (branch, dirty count, recent commits) is refreshed here too, so the model
|
|
2141
2287
|
// always sees the current repo state; any git failure is silently skipped.
|
|
2142
2288
|
// Project memory (AGENTS.md in the cwd) is appended as a delimited block;
|
|
2143
|
-
// readProjectMemory caches by mtime, so this stays cheap per turn.
|
|
2289
|
+
// readProjectMemory caches by mtime, so this stays cheap per turn. The
|
|
2290
|
+
// skills listing is refreshed here as well, so skills added mid-session
|
|
2291
|
+
// show up in the next turn's system prompt.
|
|
2144
2292
|
syncSystemMessage() {
|
|
2145
2293
|
const base = this.opts.system;
|
|
2146
2294
|
const plan = this.opts.config.permissionMode === "plan";
|
|
@@ -2149,6 +2297,8 @@ var AgentLoop = class {
|
|
|
2149
2297
|
if (plan) parts.push(PLAN_MODE_PROMPT.trim());
|
|
2150
2298
|
const memory = readProjectMemory(this.opts.cwd);
|
|
2151
2299
|
if (memory) parts.push(memory);
|
|
2300
|
+
const skills = discoverSkills(this.opts.cwd);
|
|
2301
|
+
if (skills.length > 0) parts.push(formatSkillsBlock(skills));
|
|
2152
2302
|
const git = getGitSummary(this.opts.cwd);
|
|
2153
2303
|
if (git) parts.push(formatGitSummary(git));
|
|
2154
2304
|
if (parts.length === 0) return;
|
|
@@ -2297,6 +2447,11 @@ export {
|
|
|
2297
2447
|
hydrateSnapshots,
|
|
2298
2448
|
undoTurnSnapshots,
|
|
2299
2449
|
rewindToSnapshot,
|
|
2450
|
+
starHome,
|
|
2451
|
+
globalConfigPath,
|
|
2452
|
+
sessionsDir,
|
|
2453
|
+
projectConfigPath,
|
|
2454
|
+
discoverSkills,
|
|
2300
2455
|
defaultTaskManager,
|
|
2301
2456
|
resolveShell,
|
|
2302
2457
|
bashTool,
|
|
@@ -2311,4 +2466,4 @@ export {
|
|
|
2311
2466
|
PLAN_MODE_PROMPT,
|
|
2312
2467
|
AgentLoop
|
|
2313
2468
|
};
|
|
2314
|
-
//# sourceMappingURL=chunk-
|
|
2469
|
+
//# sourceMappingURL=chunk-MJ2O7NWI.js.map
|