u-foo 2.3.17 → 2.3.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.3.17",
3
+ "version": "2.3.19",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
@@ -1,3 +1,9 @@
1
+ const os = require("os");
2
+ const { version: packageVersion } = require("../../package.json");
3
+
4
+ const ANSI_RESET = "\x1b[0m";
5
+ const CLAUDE_ORANGE = "\x1b[38;2;217;119;87m";
6
+
1
7
  function createAgentViewController(options = {}) {
2
8
  const {
3
9
  screen,
@@ -17,6 +23,7 @@ function createAgentViewController(options = {}) {
17
23
  setAgentListWindowStart = () => {},
18
24
  getAgentLabel = (id) => id,
19
25
  getAgentStates = () => ({}),
26
+ getProjectRoot = () => process.cwd(),
20
27
  setDashboardView = () => {},
21
28
  setScreenGrabKeys = (value) => {
22
29
  if (screen) screen.grabKeys = Boolean(value);
@@ -51,6 +58,8 @@ function createAgentViewController(options = {}) {
51
58
  let busInputValue = "";
52
59
  let busInputCursor = 0;
53
60
  let busLogLines = [];
61
+ let busStartupAgentId = "";
62
+ let busStartupLineCount = 0;
54
63
  const originalRender = screen.render.bind(screen);
55
64
  let renderFrozen = false;
56
65
 
@@ -72,6 +81,10 @@ function createAgentViewController(options = {}) {
72
81
  .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
73
82
  }
74
83
 
84
+ function hasAnsi(text = "") {
85
+ return /\x1b(?:\][^\x07\x1b]*(?:\x07|\x1b\\)|\[[0-9;?]*[ -/]*[@-~])/.test(String(text || ""));
86
+ }
87
+
75
88
  function clamp(value, min, max) {
76
89
  const normalized = Number.isFinite(value) ? Math.floor(value) : min;
77
90
  return Math.max(min, Math.min(max, normalized));
@@ -137,6 +150,39 @@ function createAgentViewController(options = {}) {
137
150
  return `${truncateToWidth(clean, normalizedWidth - 1).trimEnd()}…`;
138
151
  }
139
152
 
153
+ function fitAnsiText(text = "", width = 1) {
154
+ const normalizedWidth = Math.max(1, width);
155
+ const raw = String(text || "").replace(/\r/g, "");
156
+ if (!hasAnsi(raw)) return fitText(raw, normalizedWidth);
157
+ if (displayWidth(raw) <= normalizedWidth) {
158
+ return padToWidth(raw, normalizedWidth);
159
+ }
160
+ if (normalizedWidth <= 1) return "…";
161
+
162
+ const ansiPattern = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[ -/]*[@-~]/g;
163
+ let out = "";
164
+ let cells = 0;
165
+ let index = 0;
166
+ while (index < raw.length && cells < normalizedWidth - 1) {
167
+ ansiPattern.lastIndex = index;
168
+ const match = ansiPattern.exec(raw);
169
+ if (match && match.index === index) {
170
+ out += match[0];
171
+ index += match[0].length;
172
+ continue;
173
+ }
174
+ const char = Array.from(raw.slice(index))[0] || "";
175
+ if (!char) break;
176
+ const charWidth = charDisplayWidth(char);
177
+ if (cells + charWidth > normalizedWidth - 1) break;
178
+ out += char;
179
+ cells += charWidth;
180
+ index += char.length;
181
+ }
182
+ const suffix = `${ANSI_RESET}…`;
183
+ return padToWidth(`${out}${suffix}`, normalizedWidth);
184
+ }
185
+
140
186
  function horizontalLine(width = 80) {
141
187
  return "─".repeat(Math.max(1, width));
142
188
  }
@@ -145,6 +191,11 @@ function createAgentViewController(options = {}) {
145
191
  return fitText(text, Math.max(1, width));
146
192
  }
147
193
 
194
+ function logLine(text = "", width = 80) {
195
+ const normalizedWidth = Math.max(1, width);
196
+ return hasAnsi(text) ? fitAnsiText(text, normalizedWidth) : plainLine(text, normalizedWidth);
197
+ }
198
+
148
199
  function sliceDisplayCells(text = "", startCell = 0, maxCells = 1) {
149
200
  const targetStart = Math.max(0, startCell);
150
201
  const targetWidth = Math.max(1, maxCells);
@@ -170,6 +221,7 @@ function createAgentViewController(options = {}) {
170
221
 
171
222
  function wrapTextLine(text = "", width = 80) {
172
223
  const inner = Math.max(1, width);
224
+ if (hasAnsi(text)) return [String(text || "")];
173
225
  const clean = stripAnsi(String(text || ""));
174
226
  if (!clean) return [""];
175
227
  const lines = [];
@@ -202,31 +254,116 @@ function createAgentViewController(options = {}) {
202
254
  processStdout.write(`\x1b[${row};1H\x1b[2K${content}`);
203
255
  }
204
256
 
205
- function buildInternalStartupLines(agentLabel = "", width = 80) {
257
+ function forceScreenRepaint() {
258
+ if (typeof screen.realloc === "function") {
259
+ screen.realloc();
260
+ return;
261
+ }
262
+ if (typeof screen.alloc === "function") {
263
+ screen.alloc(true);
264
+ }
265
+ }
266
+
267
+ function compactProjectPath(projectRoot = "") {
268
+ const raw = String(projectRoot || process.cwd() || "").trim();
269
+ const home = os.homedir();
270
+ if (home && (raw === home || raw.startsWith(`${home}/`))) {
271
+ return `~${raw.slice(home.length)}`;
272
+ }
273
+ return raw || ".";
274
+ }
275
+
276
+ function borderedLines(lines = [], innerWidth = 56) {
277
+ const contentWidth = Math.max(1, innerWidth);
278
+ const out = [`╭${"─".repeat(contentWidth + 2)}╮`];
279
+ for (const line of lines) {
280
+ out.push(`│ ${fitText(line, contentWidth)} │`);
281
+ }
282
+ out.push(`╰${"─".repeat(contentWidth + 2)}╯`);
283
+ return out;
284
+ }
285
+
286
+ function normalizeAgentKind(agentId = "") {
287
+ const text = String(agentId || "").trim().toLowerCase();
288
+ if (text.startsWith("codex:") || text === "codex") return "codex";
289
+ if (text.startsWith("claude:") || text.startsWith("claude-code:") || text === "claude" || text === "claude-code") {
290
+ return "claude";
291
+ }
292
+ return "internal";
293
+ }
294
+
295
+ function buildClaudeStartupLines(agentLabel = "", width = 80) {
296
+ const label = String(agentLabel || "").trim();
297
+ const projectPath = compactProjectPath(getProjectRoot());
298
+ const product = "ClaudeCode";
299
+ const detail = label ? `${label} · managed headless` : "managed headless";
300
+ const iconTop = `${CLAUDE_ORANGE}▐▛███▜▌${ANSI_RESET}`;
301
+ const iconMiddle = `${CLAUDE_ORANGE}▝▜█████▛▘${ANSI_RESET}`;
302
+ const iconBottom = `${CLAUDE_ORANGE}▘▘▝▝${ANSI_RESET}`;
303
+ const lines = [
304
+ ` ${iconTop} ${product}v${packageVersion}`,
305
+ `${iconMiddle}${detail}`,
306
+ ` ${iconBottom} ${projectPath}`,
307
+ "",
308
+ ];
309
+ if (width < 44) return lines;
310
+ return lines.map((line) => fitAnsiText(line, Math.min(58, Math.max(1, width))));
311
+ }
312
+
313
+ function buildCodexStartupLines(agentLabel = "", width = 80) {
206
314
  const label = String(agentLabel || "").trim();
207
- if (width < 48) {
315
+ const projectPath = compactProjectPath(getProjectRoot());
316
+ if (width < 36) {
208
317
  return [
209
- `Welcome to ufoo internal`,
210
- label ? `agent ${label}` : "agent internal",
318
+ `>_ OpenAI Codex`,
319
+ label ? `model: ${label}` : "model: managed headless",
320
+ `directory: ${projectPath}`,
211
321
  "",
212
322
  ];
213
323
  }
324
+ const innerWidth = Math.min(56, Math.max(24, width - 4));
214
325
  return [
215
- "Welcome to ufoo internal",
216
- "································",
217
- " ░░░░░░ ██╗ ██╗",
218
- " ░░░ ░░░░░░░░░░ ██║ ██║",
219
- " ░░░░░░░░░░░░░░░░ ╚██████╔╝",
220
- label ? `agent ${label}` : "agent internal",
326
+ ...borderedLines([
327
+ `>_ OpenAI Codex (ufoo v${packageVersion})`,
328
+ "",
329
+ `model: ${label ? `${label} · managed headless` : "managed headless"}`,
330
+ `directory: ${projectPath}`,
331
+ ], innerWidth),
221
332
  "",
222
333
  ];
223
334
  }
224
335
 
336
+ function buildInternalStartupLines(agentId = "", agentLabel = "", width = 80) {
337
+ const kind = normalizeAgentKind(agentId);
338
+ if (kind === "codex") return buildCodexStartupLines(agentLabel || agentId, width);
339
+ return buildClaudeStartupLines(agentLabel || agentId, width);
340
+ }
341
+
342
+ function staticStartupLines(agentId = "", agentLabel = "", width = 80) {
343
+ const lines = buildInternalStartupLines(agentId, agentLabel, width);
344
+ if (lines.length > 0 && String(lines[lines.length - 1] || "") === "") {
345
+ return lines.slice(0, -1);
346
+ }
347
+ return lines;
348
+ }
349
+
225
350
  function resetBusView(agentId) {
226
351
  busInputValue = "";
227
352
  busInputCursor = 0;
353
+ busStartupAgentId = agentId || "";
228
354
  const label = getAgentLabel(agentId);
229
- busLogLines = buildInternalStartupLines(label, getCols());
355
+ const startupLines = staticStartupLines(agentId, label, getCols());
356
+ busLogLines = startupLines.concat("");
357
+ busStartupLineCount = startupLines.length;
358
+ }
359
+
360
+ function refreshBusStartupLines(width = getCols()) {
361
+ if (!busStartupAgentId || busStartupLineCount <= 0) return;
362
+ const label = getAgentLabel(busStartupAgentId);
363
+ const startupLines = staticStartupLines(busStartupAgentId, label, width);
364
+ const tailLines = busLogLines.slice(busStartupLineCount);
365
+ busLogLines = startupLines.concat(tailLines.length > 0 ? tailLines : [""]);
366
+ busStartupLineCount = startupLines.length;
230
367
  }
231
368
 
232
369
  function appendBusLog(text = "") {
@@ -265,6 +402,7 @@ function createAgentViewController(options = {}) {
265
402
  const rows = getRows();
266
403
  const cols = getCols();
267
404
  const width = Math.max(20, cols);
405
+ refreshBusStartupLines(width);
268
406
  const inputTop = Math.max(4, rows - 3);
269
407
  const logContentTop = 1;
270
408
  const logContentBottom = Math.max(logContentTop, inputTop - 1);
@@ -273,7 +411,7 @@ function createAgentViewController(options = {}) {
273
411
  processStdout.write("\x1b[?25l");
274
412
  const visibleLines = getWrappedBusLogLines(width).slice(-logContentHeight);
275
413
  for (let i = 0; i < logContentHeight; i += 1) {
276
- writeAt(logContentTop + i, plainLine(visibleLines[i] || "", width));
414
+ writeAt(logContentTop + i, logLine(visibleLines[i] || "", width));
277
415
  }
278
416
 
279
417
  writeAt(inputTop, horizontalLine(width));
@@ -379,12 +517,14 @@ function createAgentViewController(options = {}) {
379
517
  busInputValue = "";
380
518
  busInputCursor = 0;
381
519
  busLogLines = [];
520
+ busStartupAgentId = "";
521
+ busStartupLineCount = 0;
382
522
 
383
523
  currentView = "main";
384
524
  viewingAgent = null;
385
525
 
386
526
  processStdout.write(`\x1b[1;${rows}r`);
387
- processStdout.write("\x1b[2J\x1b[H");
527
+ processStdout.write("\x1b[?25h");
388
528
 
389
529
  if (detachedChildren) {
390
530
  for (const child of detachedChildren) screen.append(child);
@@ -396,9 +536,7 @@ function createAgentViewController(options = {}) {
396
536
  setDashboardView("agents");
397
537
  setSelectedAgentIndex(-1);
398
538
  setScreenGrabKeys(false);
399
- if (typeof screen.alloc === "function") {
400
- screen.alloc();
401
- }
539
+ forceScreenRepaint();
402
540
  clearTargetAgent();
403
541
  renderDashboard();
404
542
  focusInput();
package/src/chat/index.js CHANGED
@@ -1543,6 +1543,7 @@ async function runChat(projectRoot, options = {}) {
1543
1543
  }
1544
1544
  return states;
1545
1545
  },
1546
+ getProjectRoot: () => activeProjectRoot,
1546
1547
  setDashboardView: (value) => {
1547
1548
  dashboardView = value;
1548
1549
  },