@phren/agent 0.0.3 → 0.1.1

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.
@@ -117,7 +117,7 @@ export class CodexProvider {
117
117
  maxOutputTokens;
118
118
  model;
119
119
  constructor(model, maxOutputTokens) {
120
- this.model = model ?? "gpt-5.2-codex";
120
+ this.model = model ?? "gpt-5.3-codex";
121
121
  this.maxOutputTokens = maxOutputTokens ?? 8192;
122
122
  }
123
123
  async chat(system, messages, tools) {
@@ -126,7 +126,6 @@ export class CodexProvider {
126
126
  model: this.model,
127
127
  instructions: system,
128
128
  input: toResponsesInput(system, messages),
129
- max_output_tokens: this.maxOutputTokens,
130
129
  store: false,
131
130
  stream: true,
132
131
  };
@@ -187,7 +186,6 @@ export class CodexProvider {
187
186
  model: this.model,
188
187
  instructions: system,
189
188
  input: toResponsesInput(system, messages),
190
- max_output_tokens: this.maxOutputTokens,
191
189
  store: false,
192
190
  stream: true,
193
191
  include: ["reasoning.encrypted_content"],
@@ -15,6 +15,17 @@ export function buildSystemPrompt(phrenContext, priorSummary) {
15
15
  "- `phren_add_finding` saves insights for future sessions. Good findings: non-obvious patterns, decisions with rationale, error resolutions, architecture constraints. Bad findings: narration of what you did, obvious facts, secrets.",
16
16
  "- `phren_get_tasks` shows tracked work items. Complete tasks with `phren_complete_task` when done.",
17
17
  "",
18
+ "## Self-configuration",
19
+ "You ARE phren-agent. You can configure phren itself via shell commands:",
20
+ "- `phren init` — set up phren (MCP server, hooks, profiles)",
21
+ "- `phren add <path>` — register a project directory",
22
+ "- `phren config proactivity <level>` — set proactivity (high/medium/low)",
23
+ "- `phren config policy set <key> <value>` — configure retention, TTL, decay",
24
+ "- `phren hooks enable <tool>` — enable hooks for claude/copilot/cursor/codex",
25
+ "- `phren doctor --fix` — diagnose and self-heal",
26
+ "- `phren status` — check health",
27
+ "If the user asks you to configure phren, set up a project, or fix their install, use the shell tool to run these commands.",
28
+ "",
18
29
  "## Rules",
19
30
  "- Never write secrets, API keys, or PII to files or findings.",
20
31
  "- Prefer `edit_file` over `write_file` for existing files.",
package/dist/tui.js CHANGED
@@ -9,7 +9,11 @@ import { handleCommand } from "./commands.js";
9
9
  import { renderMarkdown } from "./multi/markdown.js";
10
10
  import { decodeDiffPayload, renderInlineDiff, DIFF_MARKER } from "./multi/diff-renderer.js";
11
11
  import * as os from "os";
12
+ import { execSync } from "node:child_process";
12
13
  import { loadInputMode, saveInputMode, savePermissionMode } from "./settings.js";
14
+ import { createRequire } from "node:module";
15
+ const _require = createRequire(import.meta.url);
16
+ const AGENT_VERSION = _require("../package.json").version;
13
17
  // ── ANSI helpers ─────────────────────────────────────────────────────────────
14
18
  const ESC = "\x1b[";
15
19
  const s = {
@@ -144,6 +148,7 @@ export async function startTui(config, spawner) {
144
148
  let menuListCount = 0;
145
149
  let menuFilterActive = false;
146
150
  let menuFilterBuf = "";
151
+ let ctrlCCount = 0;
147
152
  // ── Menu rendering ─────────────────────────────────────────────────────
148
153
  async function renderMenu() {
149
154
  const mod = await loadMenuModule();
@@ -170,7 +175,7 @@ export async function startTui(config, spawner) {
170
175
  menuFilterBuf = "";
171
176
  w.write("\x1b[?1049l"); // leave alternate screen (restores chat)
172
177
  statusBar();
173
- prompt();
178
+ prompt(true); // skip newline — alt screen restore already positioned cursor
174
179
  }
175
180
  // Print status bar
176
181
  function statusBar() {
@@ -179,12 +184,31 @@ export async function startTui(config, spawner) {
179
184
  const bar = renderStatusBar(config.provider.name, config.phrenCtx?.project ?? null, session.turns, costStr, config.registry.permissionConfig.mode, spawner?.listAgents().length);
180
185
  w.write(`${ESC}s${ESC}H${bar}${ESC}u`); // save cursor, move to top, print, restore
181
186
  }
182
- // Print prompt
183
- function prompt() {
187
+ // Print prompt — bordered input bar at bottom
188
+ let bashMode = false;
189
+ function prompt(skipNewline = false) {
190
+ if (!isTTY)
191
+ return;
184
192
  const mode = config.registry.permissionConfig.mode;
185
193
  const modeIcon = mode === "full-auto" ? "●" : mode === "auto-confirm" ? "◐" : "○";
186
194
  const modeColor = mode === "full-auto" ? s.yellow : mode === "auto-confirm" ? s.green : s.cyan;
187
- w.write(`\n${modeColor(modeIcon)} ${s.dim("▸")} `);
195
+ const rows = process.stdout.rows || 24;
196
+ const c = cols();
197
+ if (!skipNewline)
198
+ w.write("\n");
199
+ // Separator line + prompt on last 2 rows
200
+ const permLabel = PERMISSION_LABELS[mode];
201
+ // 3 bottom rows: separator, permission line, input
202
+ const sepLine = s.dim("─".repeat(c));
203
+ const permLine = ` ${modeColor(`${modeIcon} ${permLabel} permissions`)} ${s.dim("(shift+tab to cycle)")}`;
204
+ w.write(`${ESC}${rows - 2};1H${ESC}2K${sepLine}`);
205
+ w.write(`${ESC}${rows - 1};1H${ESC}2K${permLine}`);
206
+ if (bashMode) {
207
+ w.write(`${ESC}${rows};1H${ESC}2K${s.yellow("!")} `);
208
+ }
209
+ else {
210
+ w.write(`${ESC}${rows};1H${ESC}2K${modeColor(modeIcon)} ${s.dim("▸")} `);
211
+ }
188
212
  }
189
213
  // Terminal cleanup: restore state on exit
190
214
  function cleanupTerminal() {
@@ -197,10 +221,9 @@ export async function startTui(config, spawner) {
197
221
  }
198
222
  }
199
223
  process.on("exit", cleanupTerminal);
200
- // Setup: alternate screen not needed just reserve top line for status
224
+ // Setup: clear screen, status bar at top, content area clean
201
225
  if (isTTY) {
202
- w.write("\n"); // make room for status bar
203
- w.write(`${ESC}1;1H`); // move to top
226
+ w.write(`${ESC}2J${ESC}H`); // clear entire screen + home
204
227
  statusBar();
205
228
  w.write(`${ESC}2;1H`); // move below status bar
206
229
  // Startup banner
@@ -218,7 +241,7 @@ export async function startTui(config, spawner) {
218
241
  if (artLines.length > 0) {
219
242
  // Art on left, info on right
220
243
  const info = [
221
- `${s.brand("◆ phren agent")} ${s.dim("v0.0.1")}`,
244
+ `${s.brand("◆ phren agent")} ${s.dim(`v${AGENT_VERSION}`)}`,
222
245
  `${s.dim(config.provider.name)}${project ? s.dim(` · ${project}`) : ""}`,
223
246
  `${s.dim(cwd)}`,
224
247
  ``,
@@ -236,7 +259,7 @@ export async function startTui(config, spawner) {
236
259
  }
237
260
  else {
238
261
  // Fallback: text-only banner
239
- w.write(`\n ${s.brand("◆ phren agent")} ${s.dim("v0.0.1")}\n`);
262
+ w.write(`\n ${s.brand("◆ phren agent")} ${s.dim(`v${AGENT_VERSION}`)}\n`);
240
263
  w.write(` ${s.dim(config.provider.name)}${project ? s.dim(` · ${project}`) : ""} ${s.dim(cwd)}\n`);
241
264
  w.write(` ${modeColor(`${permMode === "full-auto" ? "●" : permMode === "auto-confirm" ? "◐" : "○"} ${permMode}`)} ${s.dim("permissions (shift+tab to cycle)")}\n\n`);
242
265
  w.write(` ${s.dim("Tab")} memory ${s.dim("Shift+Tab")} perms ${s.dim("/help")} cmds ${s.dim("Ctrl+D")} exit\n\n`);
@@ -305,13 +328,12 @@ export async function startTui(config, spawner) {
305
328
  process.stdin.on("keypress", (_ch, key) => {
306
329
  if (!key)
307
330
  return;
308
- // Ctrl+D — always exit
331
+ // Ctrl+D — clean exit
309
332
  if (key.ctrl && key.name === "d") {
310
333
  if (tuiMode === "menu")
311
- w.write("\x1b[?1049l"); // leave alt screen
312
- if (process.stdin.isTTY)
313
- process.stdin.setRawMode(false);
314
- w.write(s.dim("\nSession ended.\n"));
334
+ w.write("\x1b[?1049l");
335
+ cleanupTerminal();
336
+ w.write(`\n${s.dim(`${session.turns} turns, ${session.toolCalls} tool calls.`)}\n`);
315
337
  resolve(session);
316
338
  return;
317
339
  }
@@ -345,16 +367,57 @@ export async function startTui(config, spawner) {
345
367
  return;
346
368
  }
347
369
  // ── Chat mode keys ──────────────────────────────────────────────────
348
- // Ctrl+Ccancel current or clear line
370
+ // Escapeexit bash mode, or clear input
371
+ if (key.name === "escape") {
372
+ if (bashMode) {
373
+ bashMode = false;
374
+ inputLine = "";
375
+ prompt(true);
376
+ return;
377
+ }
378
+ if (inputLine) {
379
+ inputLine = "";
380
+ prompt(true);
381
+ return;
382
+ }
383
+ }
384
+ // Ctrl+C — progressive: cancel → warn → quit
349
385
  if (key.ctrl && key.name === "c") {
350
386
  if (running) {
387
+ // Cancel current agent turn
351
388
  pendingInput = null;
352
389
  w.write(s.yellow("\n [interrupted]\n"));
390
+ ctrlCCount = 0;
391
+ return;
353
392
  }
354
- else {
393
+ if (bashMode) {
394
+ bashMode = false;
355
395
  inputLine = "";
356
- w.write("\n");
357
- prompt();
396
+ prompt(true);
397
+ ctrlCCount = 0;
398
+ return;
399
+ }
400
+ if (inputLine) {
401
+ // Clear input
402
+ inputLine = "";
403
+ prompt(true);
404
+ ctrlCCount = 0;
405
+ return;
406
+ }
407
+ // Nothing to cancel — progressive quit
408
+ ctrlCCount++;
409
+ if (ctrlCCount === 1) {
410
+ w.write(s.dim("\n Press Ctrl+C again to exit.\n"));
411
+ prompt(true);
412
+ // Reset after 2 seconds
413
+ setTimeout(() => { ctrlCCount = 0; }, 2000);
414
+ }
415
+ else {
416
+ // Actually quit
417
+ if (process.stdin.isTTY)
418
+ process.stdin.setRawMode(false);
419
+ w.write(s.dim("\nSession ended.\n"));
420
+ resolve(session);
358
421
  }
359
422
  return;
360
423
  }
@@ -367,6 +430,40 @@ export async function startTui(config, spawner) {
367
430
  prompt();
368
431
  return;
369
432
  }
433
+ // Bash mode: ! prefix runs shell directly
434
+ if (line.startsWith("!") || bashMode) {
435
+ const cmd = bashMode ? line : line.slice(1).trim();
436
+ bashMode = false;
437
+ if (cmd) {
438
+ // Handle cd specially — change process cwd
439
+ const cdMatch = cmd.match(/^cd\s+(.*)/);
440
+ if (cdMatch) {
441
+ try {
442
+ const target = cdMatch[1].trim().replace(/^~/, os.homedir());
443
+ const resolved = require("path").resolve(process.cwd(), target);
444
+ process.chdir(resolved);
445
+ w.write(s.dim(process.cwd()) + "\n");
446
+ }
447
+ catch (err) {
448
+ w.write(s.red(err.message) + "\n");
449
+ }
450
+ }
451
+ else {
452
+ try {
453
+ const output = execSync(cmd, { encoding: "utf-8", timeout: 30_000, cwd: process.cwd(), stdio: ["ignore", "pipe", "pipe"] });
454
+ w.write(output);
455
+ if (!output.endsWith("\n"))
456
+ w.write("\n");
457
+ }
458
+ catch (err) {
459
+ const e = err;
460
+ w.write(s.red(e.stderr || e.message || "Command failed") + "\n");
461
+ }
462
+ }
463
+ }
464
+ prompt();
465
+ return;
466
+ }
370
467
  // Slash commands
371
468
  if (line === "/mode") {
372
469
  inputMode = inputMode === "steering" ? "queue" : "steering";
@@ -375,7 +472,14 @@ export async function startTui(config, spawner) {
375
472
  prompt();
376
473
  return;
377
474
  }
378
- if (handleCommand(line, { session, contextLimit, undoStack: [] })) {
475
+ if (handleCommand(line, {
476
+ session,
477
+ contextLimit,
478
+ undoStack: [],
479
+ providerName: config.provider.name,
480
+ currentModel: config.provider.model,
481
+ spawner,
482
+ })) {
379
483
  prompt();
380
484
  return;
381
485
  }
@@ -400,6 +504,12 @@ export async function startTui(config, spawner) {
400
504
  }
401
505
  // Regular character
402
506
  if (key.sequence && !key.ctrl && !key.meta) {
507
+ // ! at start of empty input toggles bash mode
508
+ if (key.sequence === "!" && inputLine === "" && !bashMode) {
509
+ bashMode = true;
510
+ prompt(true);
511
+ return;
512
+ }
403
513
  inputLine += key.sequence;
404
514
  w.write(key.sequence);
405
515
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phren/agent",
3
- "version": "0.0.3",
3
+ "version": "0.1.1",
4
4
  "description": "Coding agent with persistent memory — powered by phren",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "dist"
14
14
  ],
15
15
  "dependencies": {
16
- "@phren/cli": "0.0.58"
16
+ "@phren/cli": "0.1.1"
17
17
  },
18
18
  "engines": {
19
19
  "node": ">=20.0.0"