@phren/agent 0.1.7 → 0.1.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/dist/commands/info.js +1 -0
- package/dist/index.js +1 -0
- package/dist/tui/components/App.js +4 -4
- package/dist/tui/components/CodeBlock.js +8 -0
- package/dist/tui/components/ToolCall.js +10 -4
- package/dist/tui/hooks/useKeyboardShortcuts.js +22 -1
- package/dist/tui/ink-entry.js +15 -2
- package/package.json +2 -2
package/dist/commands/info.js
CHANGED
|
@@ -25,6 +25,7 @@ export function helpCommand(_parts, _ctx) {
|
|
|
25
25
|
/compact Compact conversation to save context space
|
|
26
26
|
/context Show context window usage and provider info
|
|
27
27
|
/mode Toggle input mode (steering ↔ queue)
|
|
28
|
+
/verbose Toggle verbose tool output (show first 5 lines)
|
|
28
29
|
/spawn <name> <task> Spawn a background agent
|
|
29
30
|
/agents List running agents
|
|
30
31
|
/session Show session info (id, duration, stats)
|
package/dist/index.js
CHANGED
|
@@ -268,6 +268,7 @@ export async function runAgentCli(raw) {
|
|
|
268
268
|
const costStr = result.totalCost ? `, ${result.totalCost}` : "";
|
|
269
269
|
process.stderr.write(`\nDone: ${result.turns} turns, ${result.toolCalls} tool calls${costStr}\n`);
|
|
270
270
|
}
|
|
271
|
+
process.stdout.write("\x07"); // bell on completion
|
|
271
272
|
// End session with summary + memory intelligence
|
|
272
273
|
if (phrenCtx && sessionId) {
|
|
273
274
|
const summary = result.finalText.slice(0, 500);
|
|
@@ -7,7 +7,7 @@ import { ThinkingIndicator } from "./ThinkingIndicator.js";
|
|
|
7
7
|
import { SteerQueue } from "./SteerQueue.js";
|
|
8
8
|
import { InputArea, PermissionsLine } from "./InputArea.js";
|
|
9
9
|
import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts.js";
|
|
10
|
-
export function App({ state, completedMessages, streamingText, completedToolCalls, thinking, thinkStartTime, thinkElapsed, steerQueue, running, showBanner, inputHistory, onSubmit, onPermissionCycle, onCancelTurn, onExit, }) {
|
|
10
|
+
export function App({ state, completedMessages, streamingText, completedToolCalls, thinking, thinkStartTime, thinkElapsed, steerQueue, running, showBanner, inputHistory, verbose, onSubmit, onPermissionCycle, onCancelTurn, onExit, }) {
|
|
11
11
|
const { exit } = useApp();
|
|
12
12
|
const [inputValue, setInputValue] = useState("");
|
|
13
13
|
const [bashMode, setBashMode] = useState(false);
|
|
@@ -54,15 +54,15 @@ export function App({ state, completedMessages, streamingText, completedToolCall
|
|
|
54
54
|
return (_jsx(Box, { flexDirection: "column", children: _jsx(Banner, { version: state.version }) }, "banner"));
|
|
55
55
|
}
|
|
56
56
|
if (item.kind === "user") {
|
|
57
|
-
return (_jsx(Box, { flexDirection: "column",
|
|
57
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { bold: true, children: ["\u276f", " ", item.text] }) }, item.id));
|
|
58
58
|
}
|
|
59
59
|
if (item.kind === "assistant") {
|
|
60
60
|
const aMsg = item;
|
|
61
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1,
|
|
61
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [aMsg.text ? (_jsxs(Box, { children: [_jsxs(Text, { color: "magenta", children: ["\u25c6", " "] }), _jsx(Text, { children: aMsg.text })] })) : null, aMsg.toolCalls?.map((tc, i) => (_jsx(ToolCall, { ...tc, verbose: verbose }, i)))] }, item.id));
|
|
62
62
|
}
|
|
63
63
|
if (item.kind === "status") {
|
|
64
64
|
return _jsx(Text, { dimColor: true, children: item.text }, item.id);
|
|
65
65
|
}
|
|
66
66
|
return null;
|
|
67
|
-
} }), _jsxs(Box, { flexDirection: "column", children: [completedToolCalls.map((tc, i) => (_jsx(ToolCall, { ...tc }, `tc-${i}`))), streamingText !== "" && (_jsxs(Box, {
|
|
67
|
+
} }), _jsxs(Box, { flexDirection: "column", children: [completedToolCalls.map((tc, i) => (_jsx(ToolCall, { ...tc, verbose: verbose }, `tc-${i}`))), streamingText !== "" && (_jsxs(Box, { children: [_jsxs(Text, { color: "magenta", children: ["\u25c6", " "] }), _jsx(Text, { children: streamingText })] })), thinking && _jsx(ThinkingIndicator, { startTime: thinkStartTime }), thinkElapsed !== null && (_jsxs(Text, { dimColor: true, children: [" ", "\u25c6", " thought for ", thinkElapsed, "s"] })), ctrlCCount > 0 && !running && (_jsxs(Text, { dimColor: true, children: [" ", "Press Ctrl+C again to exit."] })), _jsx(SteerQueue, { items: steerQueue }), _jsx(InputArea, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit, bashMode: bashMode, focus: true }), _jsx(PermissionsLine, { mode: state.permMode })] })] }));
|
|
68
68
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { highlightCode } from '../../multi/syntax-highlight.js';
|
|
4
|
+
export function CodeBlock({ code, language }) {
|
|
5
|
+
const lang = language || 'generic';
|
|
6
|
+
const highlighted = highlightCode(code, lang);
|
|
7
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: '```' + lang }), _jsx(Text, { children: highlighted }), _jsx(Text, { dimColor: true, children: '```' })] }));
|
|
8
|
+
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { formatToolInput, formatDuration
|
|
4
|
-
|
|
3
|
+
import { formatToolInput, formatDuration } from "../tool-render.js";
|
|
4
|
+
const VERBOSE_LINES = 5;
|
|
5
|
+
export function ToolCall({ name, input, output, isError, durationMs, verbose }) {
|
|
5
6
|
const preview = formatToolInput(name, input);
|
|
6
7
|
const dur = formatDuration(durationMs);
|
|
8
|
+
if (!verbose) {
|
|
9
|
+
// Non-verbose: header only, no output body
|
|
10
|
+
return (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: _jsxs(Box, { children: [_jsxs(Text, { color: isError ? "red" : "green", children: [isError ? "\u2717" : "\u2192", " "] }), _jsx(Text, { bold: true, children: name }), _jsxs(Text, { color: "gray", children: [" ", preview] }), _jsxs(Text, { dimColor: true, children: [" ", dur] })] }) }));
|
|
11
|
+
}
|
|
12
|
+
// Verbose: header + first 5 lines of output + overflow count
|
|
7
13
|
const allLines = output.split("\n").filter(Boolean);
|
|
8
|
-
const shown = allLines.slice(0,
|
|
9
|
-
const overflow = allLines.length -
|
|
14
|
+
const shown = allLines.slice(0, VERBOSE_LINES);
|
|
15
|
+
const overflow = allLines.length - VERBOSE_LINES;
|
|
10
16
|
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isError ? "red" : "green", children: [isError ? "\u2717" : "\u2192", " "] }), _jsx(Text, { bold: true, children: name }), _jsxs(Text, { color: "gray", children: [" ", preview] }), _jsxs(Text, { dimColor: true, children: [" ", dur] })] }), shown.map((line, i) => (_jsx(Text, { dimColor: true, children: " " + line.slice(0, 120) }, i))), overflow > 0 && (_jsx(Text, { dimColor: true, children: " ... +" + overflow + " lines" }))] }));
|
|
11
17
|
}
|
|
@@ -3,7 +3,7 @@ const SLASH_COMMANDS = [
|
|
|
3
3
|
"/help", "/turns", "/clear", "/cwd", "/files", "/cost", "/plan", "/undo",
|
|
4
4
|
"/context", "/model", "/provider", "/preset", "/session", "/history",
|
|
5
5
|
"/compact", "/diff", "/git", "/mem", "/ask", "/spawn", "/agents",
|
|
6
|
-
"/mode", "/exit", "/quit",
|
|
6
|
+
"/mode", "/verbose", "/exit", "/quit",
|
|
7
7
|
];
|
|
8
8
|
export function useKeyboardShortcuts(opts) {
|
|
9
9
|
useInput((input, key) => {
|
|
@@ -17,6 +17,11 @@ export function useKeyboardShortcuts(opts) {
|
|
|
17
17
|
opts.onExit();
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
+
// Ctrl+L -- clear screen
|
|
21
|
+
if (key.ctrl && input === "l") {
|
|
22
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
20
25
|
// Shift+Tab -- cycle permission mode
|
|
21
26
|
if (key.shift && key.tab) {
|
|
22
27
|
opts.onCyclePermissions();
|
|
@@ -74,6 +79,22 @@ export function useKeyboardShortcuts(opts) {
|
|
|
74
79
|
opts.onSetInput(history[history.length - 1 - newIndex]);
|
|
75
80
|
return;
|
|
76
81
|
}
|
|
82
|
+
// Ctrl+W or Ctrl+Backspace -- delete last word
|
|
83
|
+
if ((key.ctrl && input === "w") || (key.ctrl && key.backspace) || input === "\x1b\x7f") {
|
|
84
|
+
const val = opts.inputValue;
|
|
85
|
+
const trimmed = val.replace(/\s+$/, ""); // strip trailing spaces
|
|
86
|
+
const lastSpace = trimmed.lastIndexOf(" ");
|
|
87
|
+
opts.onSetInput(lastSpace >= 0 ? trimmed.slice(0, lastSpace + 1) : "");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Ctrl+U -- clear entire line
|
|
91
|
+
if (key.ctrl && input === "u") {
|
|
92
|
+
opts.onSetInput("");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Ctrl+A -- move cursor to start (clear and re-set — Ink TextInput doesn't expose cursor)
|
|
96
|
+
// Ctrl+E -- move cursor to end (no-op, cursor is always at end in Ink TextInput)
|
|
97
|
+
// These are limited by ink-text-input which doesn't support cursor positioning
|
|
77
98
|
// Tab -- slash command completion when input starts with /
|
|
78
99
|
if (key.tab && !key.shift && !opts.isRunning) {
|
|
79
100
|
const val = opts.inputValue;
|
package/dist/tui/ink-entry.js
CHANGED
|
@@ -21,6 +21,7 @@ export async function startInkTui(config, spawner) {
|
|
|
21
21
|
const steerQueueBuf = [];
|
|
22
22
|
const inputHistory = [];
|
|
23
23
|
let running = false;
|
|
24
|
+
let verbose = false;
|
|
24
25
|
let msgCounter = 0;
|
|
25
26
|
// Mutable render state — updated then pushed to React via rerender()
|
|
26
27
|
const completedMessages = [];
|
|
@@ -48,7 +49,7 @@ export async function startInkTui(config, spawner) {
|
|
|
48
49
|
function update() {
|
|
49
50
|
if (!rerender)
|
|
50
51
|
return;
|
|
51
|
-
rerender(_jsx(App, { state: getAppState(), completedMessages: [...completedMessages], streamingText: streamingText, completedToolCalls: [...currentToolCalls], thinking: thinking, thinkStartTime: thinkStartTime, thinkElapsed: thinkElapsed, steerQueue: [...steerQueueBuf], running: running, showBanner: true, inputHistory: [...inputHistory], onSubmit: handleSubmit, onPermissionCycle: handlePermissionCycle, onCancelTurn: handleCancelTurn, onExit: handleExit }));
|
|
52
|
+
rerender(_jsx(App, { state: getAppState(), completedMessages: [...completedMessages], streamingText: streamingText, completedToolCalls: [...currentToolCalls], thinking: thinking, thinkStartTime: thinkStartTime, thinkElapsed: thinkElapsed, steerQueue: [...steerQueueBuf], running: running, showBanner: true, inputHistory: [...inputHistory], verbose: verbose, onSubmit: handleSubmit, onPermissionCycle: handlePermissionCycle, onCancelTurn: handleCancelTurn, onExit: handleExit }));
|
|
52
53
|
}
|
|
53
54
|
function handlePermissionCycle() {
|
|
54
55
|
const next = nextPermissionMode(config.registry.permissionConfig.mode);
|
|
@@ -146,6 +147,12 @@ export async function startInkTui(config, spawner) {
|
|
|
146
147
|
update();
|
|
147
148
|
return;
|
|
148
149
|
}
|
|
150
|
+
if (line === "/verbose") {
|
|
151
|
+
verbose = !verbose;
|
|
152
|
+
completedMessages.push({ id: nextId(), kind: "status", text: `Verbose: ${verbose ? "on" : "off"}` });
|
|
153
|
+
update();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
149
156
|
// Slash commands — capture stderr output and display as status message
|
|
150
157
|
if (line.startsWith("/")) {
|
|
151
158
|
if (slashCommands.tryHandleCommand(line)) {
|
|
@@ -261,13 +268,19 @@ export async function startInkTui(config, spawner) {
|
|
|
261
268
|
runAgentTurn(queued);
|
|
262
269
|
}
|
|
263
270
|
}
|
|
271
|
+
// Enable bracketed paste
|
|
272
|
+
process.stdout.write("\x1b[?2004h");
|
|
264
273
|
// Clear screen before initial render — start clean
|
|
265
274
|
process.stdout.write("\x1b[2J\x1b[H");
|
|
275
|
+
const projectName = config.phrenCtx?.project ?? "phren";
|
|
276
|
+
process.title = "phren";
|
|
277
|
+
process.stdout.write(`\x1b]0;phren \xb7 ${projectName}\x07`); // set terminal window title
|
|
266
278
|
// Initial render
|
|
267
|
-
const app = render(_jsx(App, { state: getAppState(), completedMessages: [], streamingText: "", completedToolCalls: [], thinking: false, thinkStartTime: 0, thinkElapsed: null, steerQueue: [], running: false, showBanner: true, inputHistory: [], onSubmit: handleSubmit, onPermissionCycle: handlePermissionCycle, onCancelTurn: handleCancelTurn, onExit: handleExit }), { exitOnCtrlC: false });
|
|
279
|
+
const app = render(_jsx(App, { state: getAppState(), completedMessages: [], streamingText: "", completedToolCalls: [], thinking: false, thinkStartTime: 0, thinkElapsed: null, steerQueue: [], running: false, showBanner: true, inputHistory: [], verbose: verbose, onSubmit: handleSubmit, onPermissionCycle: handlePermissionCycle, onCancelTurn: handleCancelTurn, onExit: handleExit }), { exitOnCtrlC: false });
|
|
268
280
|
rerender = app.rerender;
|
|
269
281
|
const done = new Promise((r) => { resolveSession = r; });
|
|
270
282
|
app.waitUntilExit().then(() => {
|
|
283
|
+
process.stdout.write("\x1b[?2004l"); // disable bracketed paste
|
|
271
284
|
if (resolveSession)
|
|
272
285
|
resolveSession(session);
|
|
273
286
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phren/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Coding agent with persistent memory — powered by phren",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"ink-spinner": "^5.0.0",
|
|
18
18
|
"ink-text-input": "^6.0.0",
|
|
19
19
|
"react": "^19.2.4",
|
|
20
|
-
"@phren/cli": "0.1.
|
|
20
|
+
"@phren/cli": "0.1.8"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20.0.0"
|