agent-sh 0.4.0 → 0.6.0
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 +37 -115
- package/dist/agent/agent-loop.d.ts +86 -0
- package/dist/agent/agent-loop.js +704 -0
- package/dist/agent/conversation-state.d.ts +27 -0
- package/dist/agent/conversation-state.js +59 -0
- package/dist/agent/index.d.ts +11 -0
- package/dist/agent/index.js +9 -0
- package/dist/agent/skills.d.ts +25 -0
- package/dist/agent/skills.js +186 -0
- package/dist/agent/subagent.d.ts +37 -0
- package/dist/agent/subagent.js +119 -0
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +103 -0
- package/dist/agent/tool-registry.d.ts +15 -0
- package/dist/agent/tool-registry.js +30 -0
- package/dist/agent/tools/bash.d.ts +7 -0
- package/dist/agent/tools/bash.js +71 -0
- package/dist/agent/tools/display.d.ts +13 -0
- package/dist/agent/tools/display.js +70 -0
- package/dist/agent/tools/edit-file.d.ts +2 -0
- package/dist/agent/tools/edit-file.js +148 -0
- package/dist/agent/tools/glob.d.ts +2 -0
- package/dist/agent/tools/glob.js +87 -0
- package/dist/agent/tools/grep.d.ts +2 -0
- package/dist/agent/tools/grep.js +168 -0
- package/dist/agent/tools/list-skills.d.ts +2 -0
- package/dist/agent/tools/list-skills.js +28 -0
- package/dist/agent/tools/ls.d.ts +2 -0
- package/dist/agent/tools/ls.js +72 -0
- package/dist/agent/tools/read-file.d.ts +10 -0
- package/dist/agent/tools/read-file.js +101 -0
- package/dist/agent/tools/user-shell.d.ts +13 -0
- package/dist/agent/tools/user-shell.js +84 -0
- package/dist/agent/tools/write-file.d.ts +2 -0
- package/dist/agent/tools/write-file.js +82 -0
- package/dist/agent/types.d.ts +78 -0
- package/dist/agent/types.js +1 -0
- package/dist/core.d.ts +22 -14
- package/dist/core.js +256 -36
- package/dist/event-bus.d.ts +98 -17
- package/dist/event-bus.js +10 -1
- package/dist/extension-loader.d.ts +1 -1
- package/dist/extension-loader.js +10 -1
- package/dist/extensions/command-suggest.d.ts +10 -0
- package/dist/extensions/command-suggest.js +41 -0
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +161 -64
- package/dist/extensions/tui-renderer.js +426 -126
- package/dist/index.js +110 -129
- package/dist/input-handler.js +78 -9
- package/dist/output-parser.d.ts +7 -0
- package/dist/output-parser.js +27 -0
- package/dist/settings.d.ts +53 -2
- package/dist/settings.js +46 -3
- package/dist/shell.js +35 -28
- package/dist/types.d.ts +33 -6
- package/dist/utils/box-frame.d.ts +3 -1
- package/dist/utils/box-frame.js +12 -5
- package/dist/utils/diff.js +10 -0
- package/dist/utils/llm-client.d.ts +45 -0
- package/dist/utils/llm-client.js +60 -0
- package/dist/utils/markdown.d.ts +1 -0
- package/dist/utils/markdown.js +25 -3
- package/dist/utils/stream-transform.js +20 -47
- package/dist/utils/tool-display.d.ts +4 -0
- package/dist/utils/tool-display.js +35 -8
- package/examples/extensions/claude-code-bridge/README.md +35 -0
- package/examples/extensions/claude-code-bridge/index.ts +194 -0
- package/examples/extensions/claude-code-bridge/package.json +11 -0
- package/examples/extensions/openrouter.ts +87 -0
- package/examples/extensions/pi-bridge/README.md +35 -0
- package/examples/extensions/pi-bridge/index.ts +263 -0
- package/examples/extensions/pi-bridge/package.json +13 -0
- package/examples/extensions/secret-guard.ts +100 -0
- package/examples/extensions/subagents.ts +87 -0
- package/package.json +3 -5
- package/dist/acp-client.d.ts +0 -105
- package/dist/acp-client.js +0 -684
- package/dist/extensions/shell-exec.d.ts +0 -24
- package/dist/extensions/shell-exec.js +0 -188
- package/dist/mcp-server.d.ts +0 -13
- package/dist/mcp-server.js +0 -234
- package/examples/pi-agent-sh.ts +0 -166
|
@@ -2,90 +2,187 @@
|
|
|
2
2
|
* Slash commands extension.
|
|
3
3
|
*
|
|
4
4
|
* Registers built-in slash commands on the event bus:
|
|
5
|
+
* - Listens for "command:register" to accept commands from extensions
|
|
5
6
|
* - Responds to "autocomplete:request" pipe for /-prefixed completions
|
|
6
7
|
* - Handles "command:execute" events and dispatches to matching handler
|
|
7
8
|
* - Uses "ui:info"/"ui:error" for user feedback (no direct TUI dependency)
|
|
9
|
+
*
|
|
10
|
+
* Argument completion is composable: any extension can onPipe("autocomplete:request")
|
|
11
|
+
* and check payload.command / payload.commandArgs to add completions for any command.
|
|
8
12
|
*/
|
|
9
|
-
import { execSync } from "node:child_process";
|
|
10
13
|
import { palette as p } from "../utils/palette.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
bus.emit("ui:info", { message: "Session cleared." });
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
bus.emit("ui:error", {
|
|
31
|
-
message: `Failed to reset session: ${err instanceof Error ? err.message : String(err)}`,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
},
|
|
14
|
+
import { discoverSkills, loadSkillContent } from "../agent/skills.js";
|
|
15
|
+
export default function activate({ bus, contextManager }) {
|
|
16
|
+
const commands = new Map();
|
|
17
|
+
const register = (cmd) => {
|
|
18
|
+
const name = cmd.name.startsWith("/") ? cmd.name : `/${cmd.name}`;
|
|
19
|
+
commands.set(name, { ...cmd, name });
|
|
20
|
+
};
|
|
21
|
+
// ── Built-in commands ─────────────────────────────────────────
|
|
22
|
+
register({
|
|
23
|
+
name: "/help",
|
|
24
|
+
description: "Show available commands",
|
|
25
|
+
handler: () => {
|
|
26
|
+
const maxLen = Math.max(...[...commands.values()].map(c => c.name.length));
|
|
27
|
+
const pad = maxLen + 2;
|
|
28
|
+
const lines = [...commands.values()].map((c) => ` ${p.accent}${c.name.padEnd(pad)}${p.reset} ${c.description}`);
|
|
29
|
+
bus.emit("ui:info", { message: "Available commands:\n" + lines.join("\n") });
|
|
35
30
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
bus.emit("ui:error", { message: "Failed to copy to clipboard." });
|
|
56
|
-
}
|
|
57
|
-
},
|
|
31
|
+
});
|
|
32
|
+
register({
|
|
33
|
+
name: "/model",
|
|
34
|
+
description: "Cycle to next model, or switch to a specific one",
|
|
35
|
+
handler: (args) => {
|
|
36
|
+
const name = args.trim();
|
|
37
|
+
if (!name) {
|
|
38
|
+
const { models, active } = bus.emitPipe("config:get-models", { models: [], active: null });
|
|
39
|
+
const current = models.find((m) => m.model === active);
|
|
40
|
+
const label = current
|
|
41
|
+
? `${current.model}${current.provider ? ` [${current.provider}]` : ""}`
|
|
42
|
+
: active ?? "none";
|
|
43
|
+
bus.emit("ui:info", { message: `Model: ${label}` });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
bus.emit("config:switch-model", { model: name });
|
|
47
|
+
}
|
|
58
48
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
});
|
|
50
|
+
register({
|
|
51
|
+
name: "/thinking",
|
|
52
|
+
description: "Set thinking/reasoning effort level",
|
|
53
|
+
handler: (args) => {
|
|
54
|
+
const level = args.trim();
|
|
55
|
+
if (!level) {
|
|
56
|
+
const { level: current, levels, supported } = bus.emitPipe("config:get-thinking", { level: "off", levels: [], supported: true });
|
|
57
|
+
const status = supported ? current : `${current} (not supported by current model)`;
|
|
58
|
+
bus.emit("ui:info", { message: `Thinking: ${status} (options: ${levels.join(", ")})` });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
bus.emit("config:set-thinking", { level });
|
|
62
|
+
}
|
|
65
63
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
});
|
|
65
|
+
register({
|
|
66
|
+
name: "/backend",
|
|
67
|
+
description: "List or switch agent backend",
|
|
68
|
+
handler: (args) => {
|
|
69
|
+
const name = args.trim();
|
|
70
|
+
if (!name) {
|
|
71
|
+
bus.emit("config:list-backends", {});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
bus.emit("config:switch-backend", { name });
|
|
75
|
+
}
|
|
72
76
|
},
|
|
73
|
-
|
|
74
|
-
//
|
|
77
|
+
});
|
|
78
|
+
// ── Extension registration ────────────────────────────────────
|
|
79
|
+
bus.on("command:register", (cmd) => {
|
|
80
|
+
register(cmd);
|
|
81
|
+
});
|
|
82
|
+
// ── Skill commands (/skill:<name>) ────────────────────────────
|
|
83
|
+
const getSkills = () => {
|
|
84
|
+
const cwd = contextManager?.getCwd() ?? process.cwd();
|
|
85
|
+
return discoverSkills(cwd);
|
|
86
|
+
};
|
|
87
|
+
const handleSkillCommand = (skillName, args) => {
|
|
88
|
+
const skills = getSkills();
|
|
89
|
+
const skill = skills.find(s => s.name === skillName);
|
|
90
|
+
if (!skill) {
|
|
91
|
+
bus.emit("ui:error", { message: `Unknown skill: ${skillName}` });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const content = loadSkillContent(skill);
|
|
95
|
+
if (!content) {
|
|
96
|
+
bus.emit("ui:error", { message: `Failed to load skill: ${skillName}` });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const query = args.trim()
|
|
100
|
+
? `${content}\n\n${args.trim()}`
|
|
101
|
+
: content;
|
|
102
|
+
bus.emit("agent:submit", { query });
|
|
103
|
+
};
|
|
104
|
+
// ── Autocomplete: command names ───────────────────────────────
|
|
75
105
|
bus.onPipe("autocomplete:request", (payload) => {
|
|
76
106
|
if (!payload.buffer.startsWith("/"))
|
|
77
107
|
return payload;
|
|
108
|
+
// Argument completion is handled by separate pipe handlers below
|
|
109
|
+
if (payload.command)
|
|
110
|
+
return payload;
|
|
78
111
|
const prefix = payload.buffer.toLowerCase();
|
|
79
|
-
const matching = commands
|
|
112
|
+
const matching = [...commands.values()]
|
|
80
113
|
.filter((c) => c.name.toLowerCase().startsWith(prefix))
|
|
81
114
|
.map((c) => ({ name: c.name, description: c.description }));
|
|
115
|
+
// Skill commands
|
|
116
|
+
if (prefix.startsWith("/skill:") || "/skill:".startsWith(prefix)) {
|
|
117
|
+
const skills = getSkills();
|
|
118
|
+
for (const skill of skills) {
|
|
119
|
+
const name = `/skill:${skill.name}`;
|
|
120
|
+
if (name.toLowerCase().startsWith(prefix)) {
|
|
121
|
+
matching.push({ name, description: skill.description });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
82
125
|
if (matching.length === 0)
|
|
83
126
|
return payload;
|
|
84
127
|
return { ...payload, items: [...payload.items, ...matching] };
|
|
85
128
|
});
|
|
86
|
-
//
|
|
129
|
+
// ── Autocomplete: /model arguments ─────────────────────────────
|
|
130
|
+
bus.onPipe("autocomplete:request", (payload) => {
|
|
131
|
+
if (payload.command !== "/model")
|
|
132
|
+
return payload;
|
|
133
|
+
const partial = (payload.commandArgs ?? "").toLowerCase();
|
|
134
|
+
const { models, active } = bus.emitPipe("config:get-models", { models: [], active: null });
|
|
135
|
+
const items = models
|
|
136
|
+
.filter((m) => m.model.toLowerCase().includes(partial))
|
|
137
|
+
.slice(0, 15)
|
|
138
|
+
.map((m) => ({
|
|
139
|
+
name: `/model ${m.model}`,
|
|
140
|
+
description: `${m.provider ? `[${m.provider}]` : ""}${m.model === active ? " (active)" : ""}`,
|
|
141
|
+
}));
|
|
142
|
+
if (items.length === 0)
|
|
143
|
+
return payload;
|
|
144
|
+
return { ...payload, items: [...payload.items, ...items] };
|
|
145
|
+
});
|
|
146
|
+
// ── Autocomplete: /thinking arguments ─────────────────────────
|
|
147
|
+
bus.onPipe("autocomplete:request", (payload) => {
|
|
148
|
+
if (payload.command !== "/thinking")
|
|
149
|
+
return payload;
|
|
150
|
+
const partial = (payload.commandArgs ?? "").toLowerCase();
|
|
151
|
+
const { level: current, levels } = bus.emitPipe("config:get-thinking", { level: "off", levels: [], supported: true });
|
|
152
|
+
const items = levels
|
|
153
|
+
.filter((l) => l.startsWith(partial))
|
|
154
|
+
.map((l) => ({
|
|
155
|
+
name: `/thinking ${l}`,
|
|
156
|
+
description: l === current ? "(active)" : "",
|
|
157
|
+
}));
|
|
158
|
+
if (items.length === 0)
|
|
159
|
+
return payload;
|
|
160
|
+
return { ...payload, items: [...payload.items, ...items] };
|
|
161
|
+
});
|
|
162
|
+
// ── Autocomplete: /backend arguments ──────────────────────────
|
|
163
|
+
bus.onPipe("autocomplete:request", (payload) => {
|
|
164
|
+
if (payload.command !== "/backend")
|
|
165
|
+
return payload;
|
|
166
|
+
const partial = (payload.commandArgs ?? "").toLowerCase();
|
|
167
|
+
const { names, active } = bus.emitPipe("config:get-backends", { names: [], active: null });
|
|
168
|
+
const items = names
|
|
169
|
+
.filter((n) => n.toLowerCase().startsWith(partial))
|
|
170
|
+
.map((n) => ({
|
|
171
|
+
name: `/backend ${n}`,
|
|
172
|
+
description: n === active ? "(active)" : "",
|
|
173
|
+
}));
|
|
174
|
+
if (items.length === 0)
|
|
175
|
+
return payload;
|
|
176
|
+
return { ...payload, items: [...payload.items, ...items] };
|
|
177
|
+
});
|
|
178
|
+
// ── Dispatch ──────────────────────────────────────────────────
|
|
87
179
|
bus.on("command:execute", (e) => {
|
|
88
|
-
|
|
180
|
+
if (e.name.startsWith("/skill:")) {
|
|
181
|
+
const skillName = e.name.slice("/skill:".length);
|
|
182
|
+
handleSkillCommand(skillName, e.args);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const cmd = commands.get(e.name);
|
|
89
186
|
if (cmd) {
|
|
90
187
|
const result = cmd.handler(e.args);
|
|
91
188
|
if (result instanceof Promise) {
|