@vietor/easy-agent 0.4.6 → 0.4.8
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 +23 -2
- package/dist/config.js +1 -0
- package/dist/tui/App.js +3 -3
- package/dist/tui/Spinner.js +2 -2
- package/dist/tui/TodoView.js +1 -1
- package/package.json +2 -2
- package/dist/tui/ErrorBoundary.js +0 -15
- package/dist/tui/TimelineList.js +0 -9
package/README.md
CHANGED
|
@@ -37,6 +37,25 @@ Create `~/.easy-agent.json` in your home directory:
|
|
|
37
37
|
|
|
38
38
|
`llm.baseUrl`, `llm.apiKey`, and `llm.model` are all required. Point `baseUrl` at any OpenAI-compatible endpoint (OpenAI, Azure OpenAI, local servers, etc.) and set `model` to a model that endpoint serves.
|
|
39
39
|
|
|
40
|
+
#### `wireApi` (optional)
|
|
41
|
+
|
|
42
|
+
Selects the wire protocol the client speaks. Valid values: `"completions"` | `"anthropic"`. Defaults to `"completions"`.
|
|
43
|
+
|
|
44
|
+
- `"completions"` - OpenAI Chat Completions compatible endpoint (the default). `reasoningEffort` is sent as `reasoning_effort`.
|
|
45
|
+
- `"anthropic"` - Anthropic Messages API via the official SDK. Point `baseUrl` at an Anthropic-compatible endpoint (e.g. `https://api.anthropic.com`) and `model` at a Claude model. `reasoningEffort` enables extended thinking (`"high"` = 16k budget, `"max"` = 32k).
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"llm": {
|
|
50
|
+
"baseUrl": "https://api.anthropic.com",
|
|
51
|
+
"apiKey": "your-api-key",
|
|
52
|
+
"model": "claude-sonnet-5",
|
|
53
|
+
"wireApi": "anthropic",
|
|
54
|
+
"reasoningEffort": "high"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
40
59
|
#### `reasoningEffort` (optional)
|
|
41
60
|
|
|
42
61
|
Controls the depth of model reasoning. Valid values: `"high"` | `"max"`. Defaults to `"high"`. When set to `"max"` the model spends more tokens on deeper reasoning before responding — useful for complex logic, math, or multi-step analysis. The current setting is shown in the TUI header (e.g. ` · reasoning max`). Requires a model that supports the `reasoning_effort` parameter.
|
|
@@ -85,7 +104,7 @@ Both global and project files are loaded and concatenated into the system prompt
|
|
|
85
104
|
|
|
86
105
|
### Skills (optional)
|
|
87
106
|
|
|
88
|
-
Skills are reusable prompts that register themselves as slash commands. Create a subdirectory for each skill under `~/.easy-agent/skills/`
|
|
107
|
+
Skills are reusable prompts that register themselves as slash commands. Create a subdirectory for each skill under `~/.easy-agent/skills/` with a `SKILL.md` file (falls back to `~/.claude/skills/`):
|
|
89
108
|
|
|
90
109
|
```
|
|
91
110
|
~/.easy-agent/skills/
|
|
@@ -121,7 +140,9 @@ easy-agent --resume # list all saved sessions for this directory
|
|
|
121
140
|
|
|
122
141
|
Type a prompt and press Enter. The agent streams its reply and calls tools as needed, showing each tool call and a one-line preview of its result. It iterates until the task is done (capped at 50 tool rounds per turn).
|
|
123
142
|
|
|
124
|
-
The TUI
|
|
143
|
+
The TUI header shows the model name, version, and configured reasoning effort (e.g. `deepseek-v4-flash · reasoning high`). While the agent is running, a spinner displays the thinking and reply elapsed time alongside token counts (e.g. `⠋ thinking · think 2s · reply 0s · ↑1.2k · ↓0.4k`). If the LLM emits extended thinking blocks, a collapsible reasoning panel appears — press <kbd>t</kbd> to expand/collapse it.
|
|
144
|
+
|
|
145
|
+
A status bar at the bottom shows the context token usage with a progress bar and percentage, along with keyboard shortcut hints: <kbd>ESC</kbd> to abort a running task, <kbd>t</kbd> to toggle reasoning (when available), and <kbd>/</kbd> for commands.
|
|
125
146
|
|
|
126
147
|
### Built-in tools
|
|
127
148
|
|
package/dist/config.js
CHANGED
|
@@ -8,6 +8,7 @@ const LLMConfig = z.object({
|
|
|
8
8
|
apiKey: z.string(),
|
|
9
9
|
model: z.string(),
|
|
10
10
|
reasoningEffort: z.enum(["high", "max"]).default("high"),
|
|
11
|
+
wireApi: z.enum(["completions", "anthropic"]).default("completions"),
|
|
11
12
|
});
|
|
12
13
|
const StdioServerConfig = z.object({
|
|
13
14
|
type: z.literal("stdio").optional(),
|
package/dist/tui/App.js
CHANGED
|
@@ -14,7 +14,7 @@ export function App({ session }) {
|
|
|
14
14
|
const { exit } = useApp();
|
|
15
15
|
const { columns } = useWindowSize();
|
|
16
16
|
const view = useSyncExternalStore(session.subscribe, session.getSnapshot);
|
|
17
|
-
const [runState, setRunState] = useState({ running: false, elapsed: 0, thinkingElapsed: 0, replyElapsed: 0,
|
|
17
|
+
const [runState, setRunState] = useState({ running: false, elapsed: 0, thinkingElapsed: 0, replyElapsed: 0, inputTokens: 0, outputTokens: 0 });
|
|
18
18
|
const [streamingText, setStreamingText] = useState("");
|
|
19
19
|
const streamingRef = useRef("");
|
|
20
20
|
const renderTimerRef = useRef(undefined);
|
|
@@ -101,10 +101,10 @@ export function App({ session }) {
|
|
|
101
101
|
}
|
|
102
102
|
else {
|
|
103
103
|
const spinnerLabel = streamingText ? "replying" : "thinking";
|
|
104
|
-
runningView = (_jsxs(_Fragment, { children: [reasoningText ? renderReasoning(reasoningText, showReasoning) : null, streamingText ? (_jsx(Box, { marginTop: 1, paddingLeft: 1, paddingRight: 1, borderStyle: "single", borderTop: false, borderRight: false, borderBottom: false, borderColor: "gray", children: _jsx(Markdown, { children: streamingText }) })) : null, _jsx(Box, { marginTop: 1, paddingLeft: 1, children: _jsx(Spinner, { label: spinnerLabel, thinkingElapsed: runState.thinkingElapsed, replyElapsed: runState.replyElapsed,
|
|
104
|
+
runningView = (_jsxs(_Fragment, { children: [reasoningText ? renderReasoning(reasoningText, showReasoning) : null, streamingText ? (_jsx(Box, { marginTop: 1, paddingLeft: 1, paddingRight: 1, borderStyle: "single", borderTop: false, borderRight: false, borderBottom: false, borderColor: "gray", children: _jsx(Markdown, { children: streamingText }) })) : null, _jsx(Box, { marginTop: 1, paddingLeft: 1, children: _jsx(Spinner, { label: spinnerLabel, thinkingElapsed: runState.thinkingElapsed, replyElapsed: runState.replyElapsed, inputTokens: runState.inputTokens, outputTokens: runState.outputTokens }) })] }));
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
return (_jsxs(Box, { width: columns, flexDirection: "column", children: [_jsx(AppHeader, { cwd: session.cwd, model: session.model, reasoningEffort: session.reasoningEffort }), view.timeline.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: view.timeline.map((entry, i) => (_jsx(TimelineView, { entry: entry }, i))) })) : null, view.todos.length > 0 ? _jsx(TodoView, { todos: view.todos }) : null,
|
|
107
|
+
return (_jsxs(Box, { width: columns, flexDirection: "column", children: [_jsx(AppHeader, { cwd: session.cwd, model: session.model, reasoningEffort: session.reasoningEffort }), view.timeline.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: view.timeline.map((entry, i) => (_jsx(TimelineView, { entry: entry }, i))) })) : null, runningView, view.todos.length > 0 ? _jsx(TodoView, { todos: view.todos }) : null, !runState.running ? (_jsx(PromptOrCommandInput, { commands: allCmds, onCommand: handleCommand, onPrompt: handlePrompt })) : null, _jsx(StatusBar, { contextTokens: session.contextTokens, contextLimit: session.compactThreshold, running: runState.running, questionPending: !!pendingQuestion, reasoningAvailable: !!reasoningText })] }));
|
|
108
108
|
}
|
|
109
109
|
function renderReasoning(text, expanded) {
|
|
110
110
|
const lines = text.split("\n");
|
package/dist/tui/Spinner.js
CHANGED
|
@@ -3,11 +3,11 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { Text } from "ink";
|
|
4
4
|
import { timeDisplay, compactDisplay } from "../util/format.js";
|
|
5
5
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
6
|
-
export function Spinner({ label, thinkingElapsed, replyElapsed,
|
|
6
|
+
export function Spinner({ label, thinkingElapsed, replyElapsed, inputTokens, outputTokens, }) {
|
|
7
7
|
const [frame, setFrame] = useState(0);
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
const id = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
|
|
10
10
|
return () => clearInterval(id);
|
|
11
11
|
}, []);
|
|
12
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs(Text, { children: [" ", label] }), _jsxs(Text, { dimColor: true, children: [" \u00B7 think ", timeDisplay(thinkingElapsed), " \u00B7 reply ", timeDisplay(replyElapsed), " \u00B7 \u2191", compactDisplay(
|
|
12
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs(Text, { children: [" ", label] }), _jsxs(Text, { dimColor: true, children: [" \u00B7 think ", timeDisplay(thinkingElapsed), " \u00B7 reply ", timeDisplay(replyElapsed), " \u00B7 \u2191", compactDisplay(inputTokens), " \u00B7 \u2193", compactDisplay(outputTokens)] })] }));
|
|
13
13
|
}
|
package/dist/tui/TodoView.js
CHANGED
|
@@ -14,5 +14,5 @@ const COLORS = {
|
|
|
14
14
|
export const TodoView = memo(function TodoView({ todos }) {
|
|
15
15
|
const done = todos.filter((t) => t.status === "completed").length;
|
|
16
16
|
const headerColor = done === todos.length ? "green" : "cyan";
|
|
17
|
-
return (_jsxs(Box, { flexDirection: "column",
|
|
17
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "gray" }), _jsx(Text, { color: headerColor, children: `Tasks [${done}/${todos.length}]` }), todos.map((t, i) => (_jsx(Text, { color: COLORS[t.status], strikethrough: t.status === "completed", children: `${ICONS[t.status]} ${t.content}` }, i)))] }));
|
|
18
18
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vietor/easy-agent",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"react": "^19.2.7",
|
|
24
24
|
"string-width": "^8.2.1",
|
|
25
25
|
"zod": "^4.4.3",
|
|
26
|
-
"@vietor/easy-agent-core": "0.4.
|
|
26
|
+
"@vietor/easy-agent-core": "0.4.8"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.0.0",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Component } from "react";
|
|
3
|
-
import { Box, Text } from "ink";
|
|
4
|
-
export class ErrorBoundary extends Component {
|
|
5
|
-
state = { error: null };
|
|
6
|
-
static getDerivedStateFromError(error) {
|
|
7
|
-
return { error };
|
|
8
|
-
}
|
|
9
|
-
render() {
|
|
10
|
-
if (this.state.error) {
|
|
11
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: "red", bold: true, children: "\u2717 TUI Error" }), _jsx(Text, { dimColor: true, children: this.state.error.message })] }));
|
|
12
|
-
}
|
|
13
|
-
return this.props.children;
|
|
14
|
-
}
|
|
15
|
-
}
|
package/dist/tui/TimelineList.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { memo, useSyncExternalStore } from "react";
|
|
3
|
-
import { Box } from "ink";
|
|
4
|
-
import { TimelineView } from "./TimelineView.js";
|
|
5
|
-
export const TimelineList = memo(function TimelineList({ session }) {
|
|
6
|
-
const view = useSyncExternalStore(session.subscribe, session.getSnapshot);
|
|
7
|
-
const entries = view.timeline;
|
|
8
|
-
return (_jsx(Box, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: entries.map((entry, i) => (_jsx(TimelineView, { entry: entry }, i))) }));
|
|
9
|
-
});
|