agent-from-zero 0.34.2 → 0.35.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 +147 -0
- package/dist/account-status.js +172 -0
- package/dist/agent-progress.js +39 -0
- package/dist/agent.js +159 -34
- package/dist/auto-budget.js +15 -0
- package/dist/auto-context.js +125 -0
- package/dist/auto-debug.js +87 -0
- package/dist/auto-providers.js +154 -0
- package/dist/auto-review.js +113 -0
- package/dist/auto.js +295 -0
- package/dist/background.js +2 -1
- package/dist/cli.js +83 -0
- package/dist/clipboard.js +88 -0
- package/dist/completion.js +65 -0
- package/dist/config.js +8 -0
- package/dist/context.js +9 -3
- package/dist/cost.js +4 -0
- package/dist/effort.js +81 -0
- package/dist/follow-up.js +22 -0
- package/dist/form.js +8 -6
- package/dist/images.js +43 -0
- package/dist/ink/activity.js +62 -0
- package/dist/ink/agents.js +24 -0
- package/dist/ink/app.js +384 -171
- package/dist/ink/chat.js +12 -1
- package/dist/ink/commands.js +16 -3
- package/dist/ink/completion.js +43 -0
- package/dist/ink/setup.js +9 -4
- package/dist/ink/sink.js +16 -6
- package/dist/ink/viewport.js +15 -0
- package/dist/loop.js +251 -106
- package/dist/mcp.js +1 -0
- package/dist/menu.js +50 -8
- package/dist/output.js +1 -1
- package/dist/pasted-text.js +32 -0
- package/dist/permissions.js +467 -12
- package/dist/print.js +16 -0
- package/dist/prompt.js +7 -4
- package/dist/status.js +80 -0
- package/dist/team.js +9 -3
- package/dist/tools.js +13 -1
- package/dist/tui.js +31 -15
- package/dist/ui.js +4 -6
- package/package.json +4 -3
package/dist/mcp.js
CHANGED
|
@@ -543,6 +543,7 @@ function registerTools(name, specs, client) {
|
|
|
543
543
|
for (const spec of specs) {
|
|
544
544
|
const toolName = `mcp__${name}__${spec.name}`; // namespaced, collision-proof
|
|
545
545
|
const tool = {
|
|
546
|
+
annotations: spec.annotations,
|
|
546
547
|
definition: {
|
|
547
548
|
type: "function",
|
|
548
549
|
function: {
|
package/dist/menu.js
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
import readline from "node:readline"; // raw-mode keypress events + the interface to pause
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { reduceEditor, truncateToWidth } from "./editor.js";
|
|
2
4
|
import { renderMenu, MENU_HINT } from "./ui.js"; // the pure drawing + the footer hint
|
|
3
5
|
import { initFormState, reduceForm, renderForm, collectAnswers } from "./form.js"; // the multi-question form
|
|
6
|
+
// Navigation owns arrows; printable text belongs to this editor. Sending a
|
|
7
|
+
// follow-up cancels the pending choice, never implicitly approves it.
|
|
8
|
+
function promptDraft(submit, redraw, cancel) {
|
|
9
|
+
let state = { buffer: "", cursor: 0 };
|
|
10
|
+
let sending = false;
|
|
11
|
+
let error = "";
|
|
12
|
+
return {
|
|
13
|
+
render: () => submit ? `\n${truncateToWidth(`Follow-up ❯ ${state.buffer.slice(0, state.cursor)}${chalk.inverse(state.buffer[state.cursor] ?? " ")}${state.buffer.slice(state.cursor + 1)}`, Math.max(10, (process.stdout.columns || 80) - 1))}\n${truncateToWidth(error || "Type a follow-up · Enter sends text; empty input selects", Math.max(10, (process.stdout.columns || 80) - 1))}` : "",
|
|
14
|
+
handle(str, key) {
|
|
15
|
+
if (sending)
|
|
16
|
+
return true;
|
|
17
|
+
if (!submit || ["up", "down", "tab", "escape"].includes(key.name ?? "") || (key.ctrl && key.name === "c"))
|
|
18
|
+
return false;
|
|
19
|
+
const next = reduceEditor(state, str, key);
|
|
20
|
+
if (next.action === "submit" && state.buffer.trim()) {
|
|
21
|
+
sending = true;
|
|
22
|
+
void Promise.resolve().then(() => submit(state.buffer.trim())).then((accepted) => { if (accepted)
|
|
23
|
+
cancel();
|
|
24
|
+
else
|
|
25
|
+
redraw(); })
|
|
26
|
+
.catch(() => { error = "Could not submit follow-up; draft retained."; redraw(); }).finally(() => { sending = false; });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (next.action === "edit") {
|
|
30
|
+
state = next.state;
|
|
31
|
+
error = "";
|
|
32
|
+
redraw();
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return next.action !== "submit";
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
4
39
|
// An arrow-key selection menu. The whole point: an approval is one keystroke
|
|
5
40
|
// (↑/↓ then Enter), not "type y, then N, then realize you meant the other".
|
|
6
41
|
// Selecting costs the user less than typing.
|
|
@@ -14,7 +49,8 @@ import { initFormState, reduceForm, renderForm, collectAnswers } from "./form.js
|
|
|
14
49
|
// callers treat -1 as "no" (fail closed for an approval).
|
|
15
50
|
export function promptSelect(rl, // the session's line reader, paused while we own the keys
|
|
16
51
|
options, // the choices, top to bottom
|
|
17
|
-
input = process.stdin
|
|
52
|
+
input = process.stdin, // injectable for tests
|
|
53
|
+
onFollowUp) {
|
|
18
54
|
return new Promise((resolve) => {
|
|
19
55
|
if (!input.isTTY)
|
|
20
56
|
return resolve(-1); // no interactive selection without a terminal — caller falls back
|
|
@@ -24,11 +60,11 @@ input = process.stdin) {
|
|
|
24
60
|
// Redraw the menu in place: move the cursor back up over the previous
|
|
25
61
|
// render (options + the one footer line), clear downward, reprint. The
|
|
26
62
|
// question above the menu stays put.
|
|
27
|
-
const lineCount = options.length + 1
|
|
63
|
+
const lineCount = options.length + 1 + (onFollowUp ? 2 : 0);
|
|
28
64
|
const draw = (first) => {
|
|
29
65
|
if (!first)
|
|
30
66
|
process.stdout.write(`\x1b[${lineCount}A`); // up to the menu's top
|
|
31
|
-
process.stdout.write(`\x1b[J${renderMenu(options, selected)}\n${MENU_HINT}\n`);
|
|
67
|
+
process.stdout.write(`\x1b[J${renderMenu(options, selected)}\n${MENU_HINT}${draft.render()}\n`);
|
|
32
68
|
};
|
|
33
69
|
const finish = (result) => {
|
|
34
70
|
if (done)
|
|
@@ -44,9 +80,12 @@ input = process.stdin) {
|
|
|
44
80
|
rl.resume(); // the line reader takes over again
|
|
45
81
|
resolve(result);
|
|
46
82
|
};
|
|
47
|
-
const
|
|
83
|
+
const draft = promptDraft(onFollowUp, () => draw(false), () => finish(-1));
|
|
84
|
+
const onKey = (str, key) => {
|
|
48
85
|
if (!key || done)
|
|
49
86
|
return;
|
|
87
|
+
if (draft.handle(str, key))
|
|
88
|
+
return;
|
|
50
89
|
switch (key.name) {
|
|
51
90
|
case "up":
|
|
52
91
|
case "k":
|
|
@@ -88,18 +127,18 @@ input = process.stdin) {
|
|
|
88
127
|
// the user answers each and submits, and we hand back organized answers. Same
|
|
89
128
|
// raw-mode borrow/return dance as promptSelect; the state machine is in form.ts.
|
|
90
129
|
// Returns the answers, or null if cancelled / not a TTY.
|
|
91
|
-
export function promptForm(rl, questions, input = process.stdin) {
|
|
130
|
+
export function promptForm(rl, questions, input = process.stdin, onFollowUp) {
|
|
92
131
|
return new Promise((resolve) => {
|
|
93
132
|
if (!input.isTTY || !questions.length)
|
|
94
133
|
return resolve(null); // no form without a terminal or questions
|
|
95
134
|
let state = initFormState(questions);
|
|
96
135
|
let done = false;
|
|
97
136
|
const wasRaw = input.isRaw === true; // restore this on exit, not false (see promptSelect)
|
|
98
|
-
const lineCount = renderForm(questions, state).split("\n").length
|
|
137
|
+
const lineCount = renderForm(questions, state).split("\n").length + (onFollowUp ? 2 : 0);
|
|
99
138
|
const draw = (first) => {
|
|
100
139
|
if (!first)
|
|
101
140
|
process.stdout.write(`\x1b[${lineCount}A`); // up over the whole form
|
|
102
|
-
process.stdout.write(`\x1b[J${renderForm(questions, state)}\n`);
|
|
141
|
+
process.stdout.write(`\x1b[J${renderForm(questions, state)}${draft.render()}\n`);
|
|
103
142
|
};
|
|
104
143
|
const finish = (result) => {
|
|
105
144
|
if (done)
|
|
@@ -115,9 +154,12 @@ export function promptForm(rl, questions, input = process.stdin) {
|
|
|
115
154
|
rl.resume();
|
|
116
155
|
resolve(result);
|
|
117
156
|
};
|
|
118
|
-
const
|
|
157
|
+
const draft = promptDraft(onFollowUp, () => draw(false), () => finish(null));
|
|
158
|
+
const onKey = (str, key) => {
|
|
119
159
|
if (!key || done)
|
|
120
160
|
return;
|
|
161
|
+
if (draft.handle(str, key))
|
|
162
|
+
return;
|
|
121
163
|
if (key.name === "up" || key.name === "k") {
|
|
122
164
|
state = reduceForm(questions, state, "up").state;
|
|
123
165
|
draw(false);
|
package/dist/output.js
CHANGED
|
@@ -24,7 +24,7 @@ class StdoutSpinner {
|
|
|
24
24
|
// `output`. Each method is the exact line the loop used to run inline.
|
|
25
25
|
export const STDOUT_OUTPUT = {
|
|
26
26
|
spinner: (text) => new StdoutSpinner(text),
|
|
27
|
-
reasoning: (
|
|
27
|
+
reasoning: () => { },
|
|
28
28
|
answer: () => {
|
|
29
29
|
// Lazily build the MarkdownStream on the first token (so a tool-only or
|
|
30
30
|
// empty turn prints nothing), then end with a single trailing newline —
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Terminal-independent storage for folded text. Labels are presentation only:
|
|
2
|
+
// expand exactly once before hooks, permission context or model submission.
|
|
3
|
+
// Keep entries for the session so recalling an earlier draft never loses text.
|
|
4
|
+
export class PastedTextStore {
|
|
5
|
+
entries = new Map();
|
|
6
|
+
sequence = 0;
|
|
7
|
+
fold(text) {
|
|
8
|
+
const lines = text.split(/\r\n|\r|\n/).length;
|
|
9
|
+
if (lines < 4 && text.length < 1000)
|
|
10
|
+
return text;
|
|
11
|
+
const size = lines > 1 ? `${lines - 1} lines` : `${Array.from(text).length} chars`;
|
|
12
|
+
const label = `[Pasted text #${++this.sequence} +${size}]`;
|
|
13
|
+
this.entries.set(label, { label, text });
|
|
14
|
+
return label;
|
|
15
|
+
}
|
|
16
|
+
expand(input) {
|
|
17
|
+
return input.replace(/\[Pasted text #\d+ \+\d+ (?:lines|chars)\]/g, (label) => this.entries.get(label)?.text ?? label);
|
|
18
|
+
}
|
|
19
|
+
// A capsule is one editing unit: arrows jump over it and Backspace removes
|
|
20
|
+
// it whole, including when a mouse/other editor leaves the caret inside it.
|
|
21
|
+
at(input, cursor, direction) {
|
|
22
|
+
for (const match of input.matchAll(/\[Pasted text #\d+ \+\d+ (?:lines|chars)\]/g)) {
|
|
23
|
+
if (!this.entries.has(match[0]))
|
|
24
|
+
continue;
|
|
25
|
+
const start = match.index;
|
|
26
|
+
const end = start + match[0].length;
|
|
27
|
+
if (direction === "left" ? start < cursor && cursor <= end : start <= cursor && cursor < end)
|
|
28
|
+
return { start, end };
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|