@vietor/easy-agent 0.4.1 → 0.4.2
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/dist/cmds/builtin.js +12 -59
- package/dist/main.js +14 -6
- package/dist/tui/App.js +11 -13
- package/dist/tui/AppHeader.js +1 -1
- package/dist/tui/PromptOrCommandInput.js +7 -2
- package/dist/util/package.js +25 -0
- package/package.json +2 -2
package/dist/cmds/builtin.js
CHANGED
|
@@ -2,75 +2,28 @@ import { writeFileSync } from "node:fs";
|
|
|
2
2
|
export const exitCommand = {
|
|
3
3
|
name: "exit",
|
|
4
4
|
description: "Exit the conversation",
|
|
5
|
-
async execute(_ctx, host) {
|
|
6
|
-
host.exit();
|
|
7
|
-
},
|
|
8
|
-
};
|
|
9
|
-
export const clearCommand = {
|
|
10
|
-
name: "clear",
|
|
11
|
-
description: "Clear the conversation and log",
|
|
12
5
|
async execute(ctx) {
|
|
13
|
-
ctx.session.
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
export const mcpCommand = {
|
|
17
|
-
name: "mcp",
|
|
18
|
-
description: "List linked MCP servers",
|
|
19
|
-
async execute(ctx, host) {
|
|
20
|
-
const servers = ctx.mcp.list();
|
|
21
|
-
const text = servers.length
|
|
22
|
-
? [
|
|
23
|
-
"MCP servers:",
|
|
24
|
-
...servers.map((s) => `❯ ${s.name} ⋅ ${s.type} ⋅ ${s.status} ∶ ${s.tools.join(", ") || "(no tools)"}`),
|
|
25
|
-
].join("\n")
|
|
26
|
-
: "No MCP servers linked.";
|
|
27
|
-
host.info(text);
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
export const compactCommand = {
|
|
31
|
-
name: "compact",
|
|
32
|
-
description: "Compact the agent context",
|
|
33
|
-
async execute(ctx, host) {
|
|
34
|
-
host.thinking(true);
|
|
35
|
-
try {
|
|
36
|
-
const ok = await ctx.session.compact();
|
|
37
|
-
if (ok)
|
|
38
|
-
host.info("context compacted");
|
|
39
|
-
}
|
|
40
|
-
catch (e) {
|
|
41
|
-
host.error(e.message);
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
44
|
-
host.thinking(false);
|
|
45
|
-
}
|
|
6
|
+
ctx.session.local.set("exitRequested", true);
|
|
46
7
|
},
|
|
47
8
|
};
|
|
48
9
|
export const exportCommand = {
|
|
49
10
|
name: "export",
|
|
50
11
|
description: "Export the conversation to a JSONL file",
|
|
51
|
-
async execute(ctx
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
host.info(`exported to ${file}`);
|
|
63
|
-
}
|
|
64
|
-
catch (e) {
|
|
65
|
-
host.error(e.message);
|
|
66
|
-
}
|
|
12
|
+
async execute(ctx) {
|
|
13
|
+
const d = new Date();
|
|
14
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
15
|
+
const ts = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
16
|
+
const file = `conversation-${ts}.jsonl`;
|
|
17
|
+
const lines = ctx.session
|
|
18
|
+
.export()
|
|
19
|
+
.map((m) => JSON.stringify(m))
|
|
20
|
+
.join("\n");
|
|
21
|
+
writeFileSync(file, lines + "\n", "utf-8");
|
|
22
|
+
ctx.message(`exported to ${file}`);
|
|
67
23
|
},
|
|
68
24
|
};
|
|
69
25
|
export const builtinCommands = [
|
|
70
26
|
exitCommand,
|
|
71
27
|
{ ...exitCommand, name: "quit" },
|
|
72
|
-
clearCommand,
|
|
73
|
-
mcpCommand,
|
|
74
|
-
compactCommand,
|
|
75
28
|
exportCommand,
|
|
76
29
|
];
|
package/dist/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { loadConfig } from "./config.js";
|
|
4
|
-
import { tryLoadSkills, tryReadFileText,
|
|
4
|
+
import { tryLoadSkills, tryReadFileText, startSession, } from "@vietor/easy-agent-core";
|
|
5
5
|
import { builtinCommands } from "./cmds/builtin.js";
|
|
6
6
|
import { startApp } from "./tui/App.js";
|
|
7
7
|
const SYSTEM_PROMPT_BASE = [
|
|
@@ -9,13 +9,21 @@ const SYSTEM_PROMPT_BASE = [
|
|
|
9
9
|
`Output:
|
|
10
10
|
- Be concise and use GitHub-flavored markdown.
|
|
11
11
|
- State what you did and stop once the task is complete; report outcomes faithfully.
|
|
12
|
-
-
|
|
12
|
+
- Reference code as file_path:line_number.`,
|
|
13
|
+
`Environment:
|
|
14
|
+
- Platform: ${process.platform}
|
|
15
|
+
- Working directory: ${process.cwd()}`,
|
|
16
|
+
`Decision making:
|
|
17
|
+
- When a decision belongs to the user, call AskUser and wait for the answer rather than listing options in prose. Ask when there are multiple reasonable approaches, an irreversible or consequential action, or the request is ambiguous; when you have enough to proceed, act without asking.`,
|
|
13
18
|
].join("\n\n");
|
|
14
19
|
export async function main() {
|
|
15
20
|
const config = loadConfig();
|
|
16
|
-
const globalSkills =
|
|
17
|
-
|
|
18
|
-
const
|
|
21
|
+
const globalSkills = tryLoadSkills(join(homedir(), ".agents", "skills"))
|
|
22
|
+
?? tryLoadSkills(join(homedir(), ".claude", "skills"));
|
|
23
|
+
const globalPrompt = tryReadFileText(join(homedir(), ".agents", "AGENTS.md"))
|
|
24
|
+
?? tryReadFileText(join(homedir(), ".claude", "CLAUDE.md"));
|
|
25
|
+
const projectPrompt = tryReadFileText(join(process.cwd(), "AGENTS.md"))
|
|
26
|
+
?? tryReadFileText(join(process.cwd(), "CLAUDE.md"));
|
|
19
27
|
const systemPrompt = [SYSTEM_PROMPT_BASE, globalPrompt, projectPrompt]
|
|
20
28
|
.filter(Boolean)
|
|
21
29
|
.join("\n\n=================\n\n");
|
|
@@ -25,7 +33,7 @@ export async function main() {
|
|
|
25
33
|
mcpServers: config.mcpServers,
|
|
26
34
|
skills: globalSkills,
|
|
27
35
|
commands: builtinCommands,
|
|
28
|
-
|
|
36
|
+
builtinTools: {
|
|
29
37
|
askUser: true,
|
|
30
38
|
todoWrite: true
|
|
31
39
|
}
|
package/dist/tui/App.js
CHANGED
|
@@ -13,9 +13,7 @@ const STREAM_FRAME_MS = 120;
|
|
|
13
13
|
export function App({ session }) {
|
|
14
14
|
const { exit } = useApp();
|
|
15
15
|
const version = useSyncExternalStore(session.subscribe, session.getSnapshot);
|
|
16
|
-
const [
|
|
17
|
-
const [runElapsed, setRunElapsed] = useState(0);
|
|
18
|
-
const [runUsage, setRunUsage] = useState({ prompt: 0, completion: 0 });
|
|
16
|
+
const [runState, setRunState] = useState({ running: false, elapsed: 0, promptTokens: 0, completionTokens: 0 });
|
|
19
17
|
const [streamingText, setStreamingText] = useState("");
|
|
20
18
|
const streamingRef = useRef("");
|
|
21
19
|
const renderTimerRef = useRef(undefined);
|
|
@@ -27,9 +25,7 @@ export function App({ session }) {
|
|
|
27
25
|
streamingRef.current = text;
|
|
28
26
|
scheduleStreamingRender();
|
|
29
27
|
},
|
|
30
|
-
onRunStateChange:
|
|
31
|
-
onRunElapsedChange: (s) => setRunElapsed(s),
|
|
32
|
-
onRunUsageChange: (p, c) => setRunUsage({ prompt: p, completion: c }),
|
|
28
|
+
onRunStateChange: setRunState,
|
|
33
29
|
});
|
|
34
30
|
}, []);
|
|
35
31
|
const scheduleStreamingRender = () => {
|
|
@@ -50,25 +46,27 @@ export function App({ session }) {
|
|
|
50
46
|
session.abort();
|
|
51
47
|
}
|
|
52
48
|
else if (key.ctrl && _input === "c") {
|
|
53
|
-
if (running)
|
|
49
|
+
if (runState.running)
|
|
54
50
|
session.abort();
|
|
55
51
|
else
|
|
56
52
|
exit();
|
|
57
53
|
}
|
|
58
54
|
});
|
|
59
|
-
async function handleCommand(name) {
|
|
60
|
-
await session.executeCommand(name,
|
|
55
|
+
async function handleCommand(name, args) {
|
|
56
|
+
await session.executeCommand(name, args);
|
|
57
|
+
if (session.local.get("exitRequested"))
|
|
58
|
+
exit();
|
|
61
59
|
}
|
|
62
60
|
async function handlePrompt(text) {
|
|
63
61
|
if (session.isCommand(text)) {
|
|
64
|
-
await handleCommand(text);
|
|
62
|
+
await handleCommand(text, "");
|
|
65
63
|
}
|
|
66
64
|
else {
|
|
67
65
|
await session.startPrompt(text);
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
68
|
let runningView = null;
|
|
71
|
-
if (running) {
|
|
69
|
+
if (runState.running) {
|
|
72
70
|
if (pendingQuestion) {
|
|
73
71
|
runningView = (_jsx(QuestionView, { question: pendingQuestion, onAnswer: (ans) => session.submitAnswer(pendingQuestion.id, ans) }));
|
|
74
72
|
}
|
|
@@ -76,10 +74,10 @@ export function App({ session }) {
|
|
|
76
74
|
runningView = (_jsx(Box, { marginTop: 1, paddingLeft: 1, paddingRight: 1, children: _jsx(Markdown, { color: "green", children: streamingText }) }));
|
|
77
75
|
}
|
|
78
76
|
else {
|
|
79
|
-
runningView = (_jsx(Box, { marginTop: 1, paddingLeft: 1, children: _jsx(Spinner, { label: "thinking", elapsed:
|
|
77
|
+
runningView = (_jsx(Box, { marginTop: 1, paddingLeft: 1, children: _jsx(Spinner, { label: "thinking", elapsed: runState.elapsed, promptTokens: runState.promptTokens, completionTokens: runState.completionTokens }) }));
|
|
80
78
|
}
|
|
81
79
|
}
|
|
82
|
-
return (_jsxs(Box, { flexDirection: "column", minWidth: 80, children: [_jsx(AppHeader, {}), _jsx(LogList, { session: session }), session.todos.length > 0 ? _jsx(TodoView, { todos: session.todos }) : null, runningView, !running ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["[CTX ", compactDisplay(session.contextTokens), "] \u00B7 ESC to stop \u00B7 type / for commands"] }), _jsx(PromptOrCommandInput, { commands: allCmds, onCommand: handleCommand, onPrompt: handlePrompt })] })) : null] }));
|
|
80
|
+
return (_jsxs(Box, { flexDirection: "column", minWidth: 80, children: [_jsx(AppHeader, {}), _jsx(LogList, { session: session }), session.todos.length > 0 ? _jsx(TodoView, { todos: session.todos }) : null, runningView, !runState.running ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["[CTX ", compactDisplay(session.contextTokens), "] \u00B7 ESC to stop \u00B7 type / for commands"] }), _jsx(PromptOrCommandInput, { commands: allCmds, onCommand: handleCommand, onPrompt: handlePrompt })] })) : null] }));
|
|
83
81
|
}
|
|
84
82
|
export function startApp(session) {
|
|
85
83
|
process.stdout.write("[2J[H");
|
package/dist/tui/AppHeader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { memo } from "react";
|
|
3
3
|
import { Box, Text } from "ink";
|
|
4
|
-
import { getPackageInfo } from "
|
|
4
|
+
import { getPackageInfo } from "../util/package.js";
|
|
5
5
|
export const AppHeader = memo(function AppHeader() {
|
|
6
6
|
const pkginfo = getPackageInfo();
|
|
7
7
|
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "red", bold: true, children: "Easy Agent" }), _jsxs(Text, { dimColor: true, children: [" v", pkginfo.version] })] }), _jsx(Text, { dimColor: true, children: process.cwd() })] }));
|
|
@@ -6,7 +6,7 @@ const MAX_ITEMS = 4;
|
|
|
6
6
|
export function PromptOrCommandInput({ commands, onCommand, onPrompt }) {
|
|
7
7
|
const [input, setInput] = useState("");
|
|
8
8
|
const showMenu = input.startsWith("/");
|
|
9
|
-
const prefix = showMenu ? input.slice(1) : "";
|
|
9
|
+
const prefix = showMenu ? input.slice(1).split(/\s+/)[0] : "";
|
|
10
10
|
const filtered = useMemo(() => {
|
|
11
11
|
if (!showMenu)
|
|
12
12
|
return [];
|
|
@@ -45,9 +45,14 @@ export function PromptOrCommandInput({ commands, onCommand, onPrompt }) {
|
|
|
45
45
|
if (!text)
|
|
46
46
|
return;
|
|
47
47
|
if (text.startsWith("/")) {
|
|
48
|
+
const [name, ...rest] = text.slice(1).split(/\s+/);
|
|
49
|
+
const args = rest.join(" ");
|
|
48
50
|
if (filtered.length > 0) {
|
|
49
51
|
const cmd = filtered[selectedIndex];
|
|
50
|
-
onCommand(cmd ? cmd.name :
|
|
52
|
+
onCommand(cmd ? cmd.name : name, args);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
onCommand(name, args);
|
|
51
56
|
}
|
|
52
57
|
return;
|
|
53
58
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
const __dirname = import.meta.dirname;
|
|
4
|
+
const MAX_PARENT_TRAVERSAL = 10;
|
|
5
|
+
let _pkg = null;
|
|
6
|
+
function findPackageJson() {
|
|
7
|
+
let current = __dirname;
|
|
8
|
+
for (let i = 0; i < MAX_PARENT_TRAVERSAL; i++) {
|
|
9
|
+
const pkgPath = join(current, "package.json");
|
|
10
|
+
if (existsSync(pkgPath))
|
|
11
|
+
return pkgPath;
|
|
12
|
+
const parent = dirname(current);
|
|
13
|
+
if (parent === current)
|
|
14
|
+
break;
|
|
15
|
+
current = parent;
|
|
16
|
+
}
|
|
17
|
+
throw new Error("Cannot find package.json");
|
|
18
|
+
}
|
|
19
|
+
export function getPackageInfo() {
|
|
20
|
+
if (_pkg === null) {
|
|
21
|
+
const pkgPath = findPackageJson();
|
|
22
|
+
_pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
23
|
+
}
|
|
24
|
+
return _pkg;
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vietor/easy-agent",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"react": "^19.2.7",
|
|
23
23
|
"string-width": "^8.2.1",
|
|
24
24
|
"zod": "^4.4.3",
|
|
25
|
-
"@vietor/easy-agent-core": "0.4.
|
|
25
|
+
"@vietor/easy-agent-core": "0.4.2"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.0.0",
|