agent-from-zero 0.30.0 → 0.32.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/dist/agent.js +16 -4
- package/dist/context.js +4 -3
- package/dist/editor.js +15 -4
- package/dist/ink/app.js +435 -0
- package/dist/ink/chat.js +22 -0
- package/dist/ink/commands.js +103 -0
- package/dist/ink/launch.js +15 -0
- package/dist/ink/main.js +4 -0
- package/dist/ink/setup.js +102 -0
- package/dist/ink/sink.js +50 -0
- package/dist/loop.js +50 -52
- package/dist/output.js +50 -0
- package/dist/tui.js +12 -0
- package/dist/ui.js +14 -0
- package/package.json +6 -2
package/dist/output.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ora from "ora"; // the "thinking…" spinner the default sink drives
|
|
2
|
+
import { MarkdownStream } from "./markdown.js"; // the default sink renders the streamed answer as terminal markdown
|
|
3
|
+
import { mark } from "./ui.js"; // the ⏺ answer marker, kept consistent with the rest of the UI
|
|
4
|
+
// ---- the default sink: stdout, identical to the pre-sink behaviour -----------
|
|
5
|
+
class StdoutSpinner {
|
|
6
|
+
s;
|
|
7
|
+
constructor(text) {
|
|
8
|
+
// discardStdin:false — ora would otherwise swallow Ctrl+C while spinning.
|
|
9
|
+
this.s = ora({ text, discardStdin: false }).start();
|
|
10
|
+
}
|
|
11
|
+
set(text) {
|
|
12
|
+
if (this.s.isSpinning)
|
|
13
|
+
this.s.text = text;
|
|
14
|
+
}
|
|
15
|
+
stop() {
|
|
16
|
+
if (this.s.isSpinning)
|
|
17
|
+
this.s.stop();
|
|
18
|
+
}
|
|
19
|
+
get spinning() {
|
|
20
|
+
return this.s.isSpinning;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// The sink the readline REPL (and every non-Ink caller) gets when it passes no
|
|
24
|
+
// `output`. Each method is the exact line the loop used to run inline.
|
|
25
|
+
export const STDOUT_OUTPUT = {
|
|
26
|
+
spinner: (text) => new StdoutSpinner(text),
|
|
27
|
+
reasoning: (line) => process.stdout.write(line + "\n"),
|
|
28
|
+
answer: () => {
|
|
29
|
+
// Lazily build the MarkdownStream on the first token (so a tool-only or
|
|
30
|
+
// empty turn prints nothing), then end with a single trailing newline —
|
|
31
|
+
// exactly the old `if (printedPrefix) process.stdout.write("\n")`.
|
|
32
|
+
let md = null;
|
|
33
|
+
let printed = false;
|
|
34
|
+
return {
|
|
35
|
+
push(token) {
|
|
36
|
+
if (!md)
|
|
37
|
+
md = new MarkdownStream((s) => process.stdout.write(s), { firstPrefix: "\n" + mark.answer, indent: " " });
|
|
38
|
+
printed = true;
|
|
39
|
+
md.push(token);
|
|
40
|
+
},
|
|
41
|
+
end() {
|
|
42
|
+
if (md)
|
|
43
|
+
md.end();
|
|
44
|
+
if (printed)
|
|
45
|
+
process.stdout.write("\n");
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
note: (line) => console.log(line),
|
|
50
|
+
};
|
package/dist/tui.js
CHANGED
|
@@ -107,6 +107,18 @@ export function revealToolCalls() {
|
|
|
107
107
|
process.stdout.write(toolCallLog.join("\n") + "\n");
|
|
108
108
|
return true;
|
|
109
109
|
}
|
|
110
|
+
// Ink variants of the two reveals: RETURN the formatted block (or null) instead
|
|
111
|
+
// of writing to stdout, so the Ink REPL can commit it as a note in its tree.
|
|
112
|
+
export function getReasoning() {
|
|
113
|
+
if (!reasoningTrace.length)
|
|
114
|
+
return null;
|
|
115
|
+
return chalk.dim("💭 model's thinking:\n") + chalk.dim(reasoningTrace.join("\n\n— — —\n\n").replace(/^/gm, " "));
|
|
116
|
+
}
|
|
117
|
+
export function getToolCalls() {
|
|
118
|
+
if (!toolCallLog.length)
|
|
119
|
+
return null;
|
|
120
|
+
return chalk.dim(`tool calls this turn (${toolCallLog.length}):\n`) + toolCallLog.join("\n");
|
|
121
|
+
}
|
|
110
122
|
// ---- cleanup -----------------------------------------------------------------
|
|
111
123
|
// Called on exit. Nothing fancy to restore now (we no longer hijack the bottom
|
|
112
124
|
// of the screen), but keep the hook so callers don't have to change if that
|
package/dist/ui.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk"; // the one place color lives
|
|
2
|
+
import { displayWidth } from "./editor.js"; // CJK-/ANSI-aware width, to pad the sent-message bar to the full row
|
|
2
3
|
// The app's visual theme, in one place. Before this, emoji and colors were
|
|
3
4
|
// sprinkled across agent.ts and loop.ts — no coherent look, and impossible to
|
|
4
5
|
// restyle without hunting. This module owns "what the tool looks like": the
|
|
@@ -124,6 +125,19 @@ export function inputRule() {
|
|
|
124
125
|
export function framedPrompt(planMode) {
|
|
125
126
|
return planMode ? chalk.yellow.bold("⏸ plan ❯ ") : chalk.cyan.bold("❯ ");
|
|
126
127
|
}
|
|
128
|
+
// How a SUBMITTED message is echoed into the scrollback once you hit Enter:
|
|
129
|
+
// a "> …" line on a full-width highlight BAR (like Claude Code), so each turn
|
|
130
|
+
// reads as a distinct sent message scrolling up — not just faint text. The line
|
|
131
|
+
// is padded to the terminal width so the background spans the whole row; if the
|
|
132
|
+
// message is longer than the row it just wraps (the tail line won't be padded,
|
|
133
|
+
// a minor cosmetic). Colors fall back gracefully when the terminal lacks 256.
|
|
134
|
+
export function sentMessage(text) {
|
|
135
|
+
const cols = Math.max(20, process.stdout.columns || 80);
|
|
136
|
+
const content = "> " + text;
|
|
137
|
+
const w = displayWidth(content);
|
|
138
|
+
const padded = w < cols ? content + " ".repeat(cols - w) : content; // fill the row when it fits
|
|
139
|
+
return chalk.bgAnsi256(237).ansi256(252)(padded); // light-grey text on a subtle dark-grey bar
|
|
140
|
+
}
|
|
127
141
|
// ---- the selection menu -----------------------------------------------------
|
|
128
142
|
// Render a numbered list of options with one highlighted. The interactive part
|
|
129
143
|
// (raw-mode arrow keys) lives in menu.ts; this is just the pure drawing, so it's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-from-zero",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Build a Claude Code-style CLI agent from scratch, with AI as your pair programmer — companion code for a 10-day tutorial (DeepSeek by default, any OpenAI-compatible model)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"start": "tsx --env-file=.env src/agent.ts",
|
|
15
|
+
"ink": "tsx --env-file=.env src/ink/main.tsx",
|
|
15
16
|
"typecheck": "tsc --noEmit",
|
|
16
17
|
"eval": "tsx --env-file=.env eval/run.ts",
|
|
17
18
|
"build": "tsc -p tsconfig.build.json",
|
|
18
19
|
"prepare": "npm run build",
|
|
19
|
-
"test": "tsx tests/tools.test.ts && tsx tests/permissions.test.ts && tsx tests/context.test.ts && tsx tests/hooks.test.ts && tsx tests/mcp.test.ts && tsx tests/judge.test.ts && tsx tests/memory.test.ts && tsx tests/cost.test.ts && tsx tests/validate.test.ts && tsx tests/plan.test.ts && tsx tests/diff.test.ts && tsx tests/undo.test.ts && tsx tests/tiering.test.ts && tsx tests/mcp-http.test.ts && tsx tests/changes.test.ts && tsx tests/mentions.test.ts && tsx tests/session.test.ts && tsx tests/ui.test.ts && tsx tests/approval.test.ts && tsx tests/form.test.ts && tsx tests/model.test.ts && tsx tests/reasoning.test.ts && tsx tests/skills.test.ts && tsx tests/markdown.test.ts && tsx tests/editor.test.ts && tsx tests/todos.test.ts && tsx tests/background.test.ts && tsx tests/team.test.ts && tsx tests/board.test.ts && tsx tests/drop.test.ts"
|
|
20
|
+
"test": "tsx tests/tools.test.ts && tsx tests/permissions.test.ts && tsx tests/context.test.ts && tsx tests/hooks.test.ts && tsx tests/mcp.test.ts && tsx tests/judge.test.ts && tsx tests/memory.test.ts && tsx tests/cost.test.ts && tsx tests/validate.test.ts && tsx tests/plan.test.ts && tsx tests/diff.test.ts && tsx tests/undo.test.ts && tsx tests/tiering.test.ts && tsx tests/mcp-http.test.ts && tsx tests/changes.test.ts && tsx tests/mentions.test.ts && tsx tests/session.test.ts && tsx tests/ui.test.ts && tsx tests/approval.test.ts && tsx tests/form.test.ts && tsx tests/model.test.ts && tsx tests/reasoning.test.ts && tsx tests/skills.test.ts && tsx tests/markdown.test.ts && tsx tests/editor.test.ts && tsx tests/todos.test.ts && tsx tests/background.test.ts && tsx tests/team.test.ts && tsx tests/board.test.ts && tsx tests/drop.test.ts && tsx tests/editline.test.ts"
|
|
20
21
|
},
|
|
21
22
|
"engines": {
|
|
22
23
|
"node": ">=20.6"
|
|
@@ -25,14 +26,17 @@
|
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"chalk": "^5.6.2",
|
|
27
28
|
"cli-table3": "^0.6.5",
|
|
29
|
+
"ink": "^5.2.1",
|
|
28
30
|
"marked": "^15.0.12",
|
|
29
31
|
"marked-terminal": "^7.3.0",
|
|
30
32
|
"openai": "^6.42.0",
|
|
31
33
|
"ora": "^9.4.0",
|
|
34
|
+
"react": "^18.3.1",
|
|
32
35
|
"wrap-ansi": "^10.0.0"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@types/node": "^25.9.2",
|
|
39
|
+
"@types/react": "^18.3.31",
|
|
36
40
|
"tsx": "^4.22.4",
|
|
37
41
|
"typescript": "^6.0.3"
|
|
38
42
|
}
|