@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.
@@ -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", marginTop: 1, children: _jsxs(Text, { bold: true, children: ["\u276f", " ", item.text] }) }, item.id));
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, paddingLeft: 2, 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 }, i)))] }, item.id));
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, { paddingLeft: 2, 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: !running }), _jsx(PermissionsLine, { mode: state.permMode })] })] }));
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, COMPACT_LINES } from "../tool-render.js";
4
- export function ToolCall({ name, input, output, isError, durationMs }) {
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, COMPACT_LINES);
9
- const overflow = allLines.length - COMPACT_LINES;
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;
@@ -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.7",
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.7"
20
+ "@phren/cli": "0.1.8"
21
21
  },
22
22
  "engines": {
23
23
  "node": ">=20.0.0"