@ouro.bot/cli 0.1.0-alpha.444 → 0.1.0-alpha.445

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/changelog.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.445",
6
+ "changes": [
7
+ "`ouro up` no longer smears old lines across the screen when the live frame shrinks. The boot checklist and daemon-start polling now share one overwrite primitive, so long waits keep breathing without duplicating the masthead or leaving stale rows behind.",
8
+ "`ouro status` is back to being a compact runtime cockpit instead of a generic wizard board. The TTY command now routes through the dense status deck again, so operators get the fast, scannable view they actually need.",
9
+ "The boot surface itself is lighter and truer: the masthead only uses the wide wordmark when it really fits, the checklist stays in plan order while a middle step is active, and the current-work panel now reads like a real boot sequencer instead of a stack of `Overview` cards."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.444",
6
14
  "changes": [
@@ -5756,17 +5756,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
5756
5756
  }
5757
5757
  const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
5758
5758
  const message = command.kind === "daemon.status"
5759
- ? (() => {
5760
- const payload = (0, cli_render_1.parseStatusPayload)(response.data);
5761
- if (payload && ttyBoardEnabled(deps)) {
5762
- return (0, human_command_screens_1.renderHouseStatusScreen)({
5763
- payload,
5764
- isTTY: true,
5765
- columns: deps.stdoutColumns ?? process.stdout.columns,
5766
- }).trimEnd();
5767
- }
5768
- return (0, cli_render_1.formatDaemonStatusOutput)(response, fallbackMessage);
5769
- })()
5759
+ ? (0, cli_render_1.formatDaemonStatusOutput)(response, fallbackMessage)
5770
5760
  : fallbackMessage;
5771
5761
  deps.writeStdout(message);
5772
5762
  return message;
@@ -7,7 +7,6 @@ exports.renderAgentPickerScreen = renderAgentPickerScreen;
7
7
  exports.resolveNamedAgentSelection = resolveNamedAgentSelection;
8
8
  exports.renderHumanReadinessBoard = renderHumanReadinessBoard;
9
9
  exports.renderHumanCommandBoard = renderHumanCommandBoard;
10
- exports.renderHouseStatusScreen = renderHouseStatusScreen;
11
10
  const runtime_1 = require("../../nerves/runtime");
12
11
  const terminal_ui_1 = require("./terminal-ui");
13
12
  function renderScreenEvent(screen) {
@@ -155,82 +154,3 @@ function renderHumanCommandBoard(options) {
155
154
  prompt: options.prompt,
156
155
  });
157
156
  }
158
- function renderHouseStatusScreen(options) {
159
- renderScreenEvent("house-status");
160
- const sections = [
161
- {
162
- title: "Runtime",
163
- lines: [
164
- `Daemon: ${options.payload.overview.daemon}`,
165
- `Health: ${options.payload.overview.health}`,
166
- `Outlook: ${options.payload.overview.outlookUrl}`,
167
- `Updated: ${options.payload.overview.lastUpdated}`,
168
- ],
169
- },
170
- ];
171
- if (options.payload.agents.length > 0) {
172
- sections.push({
173
- title: "Agents",
174
- lines: options.payload.agents.map((agent) => `${agent.name} — ${agent.enabled ? "enabled" : "disabled"}`),
175
- });
176
- }
177
- if (options.payload.providers.length > 0) {
178
- sections.push({
179
- title: "Providers",
180
- lines: options.payload.providers.map((provider) => {
181
- const detail = [provider.readiness, provider.detail, provider.source, provider.credential].filter(Boolean).join("; ");
182
- return `${provider.agent} ${provider.lane} — ${provider.provider} / ${provider.model}${detail ? ` — ${detail}` : ""}`;
183
- }),
184
- });
185
- }
186
- if (options.payload.senses.length > 0) {
187
- sections.push({
188
- title: "Senses",
189
- lines: options.payload.senses.map((sense) => {
190
- const status = sense.enabled ? sense.status : "disabled";
191
- return `${sense.agent} — ${sense.label ?? sense.sense} — ${status}${sense.detail ? ` — ${sense.detail}` : ""}`;
192
- }),
193
- });
194
- }
195
- if (options.payload.workers.length > 0) {
196
- sections.push({
197
- title: "Workers",
198
- lines: options.payload.workers.map((worker) => {
199
- const details = [`restarts: ${worker.restartCount}`];
200
- if (worker.pid !== null)
201
- details.unshift(`pid ${worker.pid}`);
202
- if (worker.lastExitCode !== null)
203
- details.push(`exit=${worker.lastExitCode}`);
204
- if (worker.lastSignal !== null)
205
- details.push(`signal=${worker.lastSignal}`);
206
- if (worker.errorReason)
207
- details.push(`error: ${worker.errorReason}`);
208
- if (worker.fixHint)
209
- details.push(`fix: ${worker.fixHint}`);
210
- return `${worker.agent} — ${worker.worker} — ${worker.status} — ${details.join("; ")}`;
211
- }),
212
- });
213
- }
214
- if (options.payload.sync.length > 0) {
215
- sections.push({
216
- title: "Git sync",
217
- lines: options.payload.sync.map((row) => {
218
- if (!row.enabled)
219
- return `${row.agent} — disabled`;
220
- if (row.gitInitialized === false)
221
- return `${row.agent} — needs git init`;
222
- if (row.remoteUrl)
223
- return `${row.agent} — ${row.remote} -> ${row.remoteUrl}`;
224
- return `${row.agent} — local only`;
225
- }),
226
- });
227
- }
228
- return renderHumanCommandBoard({
229
- title: "Ouro status",
230
- subtitle: "Current runtime status for this machine.",
231
- summary: "What is running, what is stopped, and what needs attention.",
232
- isTTY: options.isTTY,
233
- columns: options.columns,
234
- sections,
235
- });
236
- }
@@ -17,6 +17,7 @@ exports.renderWaitingForDaemon = renderWaitingForDaemon;
17
17
  exports.pollDaemonStartup = pollDaemonStartup;
18
18
  const cli_render_1 = require("./cli-render");
19
19
  const runtime_1 = require("../../nerves/runtime");
20
+ const terminal_ui_1 = require("./terminal-ui");
20
21
  // ── Constants ──
21
22
  const SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
22
23
  const STABILITY_THRESHOLD_MS = 5_000;
@@ -248,16 +249,7 @@ function colorStatus(status) {
248
249
  return `${statusColor}${status}${RESET}`;
249
250
  }
250
251
  function renderStartupLines(lines, prevLineCount, isTTY) {
251
- if (!isTTY)
252
- return lines.join("\n") + "\n";
253
- let output = "";
254
- if (prevLineCount > 0) {
255
- output += `\x1b[${prevLineCount}A`;
256
- }
257
- for (const line of lines) {
258
- output += `\x1b[2K${line}\n`;
259
- }
260
- return output;
252
+ return (0, terminal_ui_1.renderOverwriteFrame)(lines, prevLineCount, isTTY);
261
253
  }
262
254
  /* v8 ignore start -- process liveness check: uses real process.kill(0), tested via deployment @preserve */
263
255
  function defaultIsProcessAlive(pid) {
@@ -4,6 +4,7 @@ exports.stripAnsi = stripAnsi;
4
4
  exports.visibleLength = visibleLength;
5
5
  exports.padAnsi = padAnsi;
6
6
  exports.wrapPlain = wrapPlain;
7
+ exports.renderOverwriteFrame = renderOverwriteFrame;
7
8
  exports.renderOuroMasthead = renderOuroMasthead;
8
9
  exports.formatActionActorLabel = formatActionActorLabel;
9
10
  exports.renderTerminalBoard = renderTerminalBoard;
@@ -16,6 +17,7 @@ const SCALE = "\x1b[38;2;45;148;71m";
16
17
  const GLOW = "\x1b[38;2;74;227;108m";
17
18
  const BONE = "\x1b[38;2;237;242;238m";
18
19
  const MIST = "\x1b[38;2;154;174;159m";
20
+ const ALERT = "\x1b[38;2;255;106;106m";
19
21
  const ANSI_RE = /\x1b\[[0-9;]*m/g;
20
22
  const MASTHEAD_WORD = "OUROBOROS";
21
23
  const CLASSIC_WORDMARK_GLYPHS = {
@@ -71,6 +73,25 @@ function boardWidth(columns) {
71
73
  const requested = columns ?? 88;
72
74
  return Math.max(58, Math.min(requested, 96));
73
75
  }
76
+ function renderOverwriteFrame(lines, prevLineCount, isTTY) {
77
+ if (!isTTY)
78
+ return `${lines.join("\n")}\n`;
79
+ let output = "";
80
+ if (prevLineCount > 0) {
81
+ output += `\x1b[${prevLineCount}A`;
82
+ }
83
+ for (const line of lines) {
84
+ output += `\x1b[2K${line}\n`;
85
+ }
86
+ const extraLineCount = Math.max(0, prevLineCount - lines.length);
87
+ for (let i = 0; i < extraLineCount; i++) {
88
+ output += "\x1b[2K\n";
89
+ }
90
+ if (extraLineCount > 0) {
91
+ output += `\x1b[${extraLineCount}A`;
92
+ }
93
+ return output;
94
+ }
74
95
  function renderPanelTTY(title, lines, width) {
75
96
  const innerWidth = Math.max(8, width - 4);
76
97
  const topPrefix = `╭─ ${title} `;
@@ -94,15 +115,18 @@ function renderPanelPlain(title, lines) {
94
115
  ];
95
116
  }
96
117
  function mastheadArt(columns) {
97
- if ((columns ?? 88) >= 74) {
98
- const rows = Array.from({ length: 5 }, () => []);
99
- for (const letter of MASTHEAD_WORD.split("")) {
100
- const glyph = CLASSIC_WORDMARK_GLYPHS[letter];
101
- for (const [index, line] of glyph.entries()) {
102
- rows[index].push(line);
103
- }
118
+ const rows = Array.from({ length: 5 }, () => []);
119
+ for (const letter of MASTHEAD_WORD.split("")) {
120
+ const glyph = CLASSIC_WORDMARK_GLYPHS[letter];
121
+ for (const [index, line] of glyph.entries()) {
122
+ rows[index].push(line);
104
123
  }
105
- return rows.map((row) => row.join(" "));
124
+ }
125
+ const classicWordmark = rows.map((row) => row.join(" "));
126
+ const availableColumns = columns ?? 88;
127
+ const classicWidth = Math.max(...classicWordmark.map((line) => line.length));
128
+ if (availableColumns >= classicWidth) {
129
+ return classicWordmark;
106
130
  }
107
131
  return [MASTHEAD_WORD];
108
132
  }
@@ -182,7 +206,58 @@ function formatOperationStep(step) {
182
206
  const detail = step.detail ? ` — ${step.detail}` : "";
183
207
  return `${marker} ${step.label}${detail}`;
184
208
  }
209
+ function operationMarkerTone(status) {
210
+ switch (status) {
211
+ case "done":
212
+ return GLOW;
213
+ case "active":
214
+ return BONE;
215
+ case "failed":
216
+ return ALERT;
217
+ case "pending":
218
+ default:
219
+ return MIST;
220
+ }
221
+ }
222
+ function renderOperationStepTTY(step) {
223
+ const marker = step.status === "done"
224
+ ? "✓"
225
+ : step.status === "failed"
226
+ ? "✗"
227
+ : step.status === "active"
228
+ ? "→"
229
+ : "○";
230
+ const label = color(step.label, step.status === "pending" ? MIST : BONE, step.status !== "pending");
231
+ const detail = step.detail ? ` ${color(`— ${step.detail}`, MIST)}` : "";
232
+ return `${color(marker, operationMarkerTone(step.status), true)} ${label}${detail}`;
233
+ }
234
+ function renderOperationSectionTTY(title, lines, width) {
235
+ const rule = "─".repeat(Math.max(8, width - title.length - 3));
236
+ return [
237
+ `${color("─ ", CANOPY)}${color(title, BONE, true)} ${color(rule, CANOPY)}`,
238
+ ...lines.map((line) => ` ${line}`),
239
+ ];
240
+ }
241
+ function renderOperationSectionPlain(title, lines) {
242
+ return [
243
+ title,
244
+ ...lines.map((line) => ` ${plainLine(line)}`),
245
+ ];
246
+ }
185
247
  function renderTerminalOperation(options) {
248
+ if (!options.suppressEvent) {
249
+ (0, runtime_1.emitNervesEvent)({
250
+ component: "daemon",
251
+ event: "daemon.terminal_operation_rendered",
252
+ message: "rendered terminal operation surface",
253
+ meta: {
254
+ title: options.title,
255
+ steps: options.steps?.length ?? 0,
256
+ hasCurrentStep: !!options.currentStep,
257
+ tty: options.isTTY,
258
+ },
259
+ });
260
+ }
186
261
  const steps = options.steps ?? [];
187
262
  const currentLines = options.currentStep
188
263
  ? [
@@ -191,25 +266,34 @@ function renderTerminalOperation(options) {
191
266
  ]
192
267
  : ["Standing by."];
193
268
  const progressLines = steps.length > 0
194
- ? steps.map((step) => formatOperationStep(step))
269
+ ? options.isTTY
270
+ ? steps.map((step) => renderOperationStepTTY(step))
271
+ : steps.map((step) => formatOperationStep(step))
195
272
  : ["No active steps yet."];
196
- return renderTerminalBoard({
273
+ const width = boardWidth(options.columns);
274
+ const blocks = [];
275
+ blocks.push(renderOuroMasthead({
197
276
  isTTY: options.isTTY,
198
- columns: options.columns,
199
- masthead: options.masthead,
200
- title: options.title,
201
- summary: options.summary,
202
- sections: [
203
- {
204
- title: options.currentTitle ?? "Right now",
205
- lines: currentLines,
206
- },
207
- {
208
- title: options.stepsTitle ?? "Progress",
209
- lines: progressLines,
210
- },
211
- ],
212
- prompt: options.prompt,
213
- suppressEvent: options.suppressEvent,
214
- });
277
+ columns: width,
278
+ subtitle: options.masthead?.subtitle,
279
+ }).trimEnd());
280
+ const introLines = [
281
+ options.isTTY ? color(options.title, BONE, true) : options.title,
282
+ ...(options.summary
283
+ ? wrapPlain(options.summary, Math.max(20, width - 2)).map((line) => options.isTTY ? color(line, MIST) : line)
284
+ : []),
285
+ ];
286
+ blocks.push(introLines.join("\n"));
287
+ const renderedSteps = options.isTTY
288
+ ? renderOperationSectionTTY(options.stepsTitle ?? "Checklist", progressLines, width)
289
+ : renderOperationSectionPlain(options.stepsTitle ?? "Checklist", progressLines);
290
+ const renderedCurrent = options.isTTY
291
+ ? renderOperationSectionTTY(options.currentTitle ?? "Current work", currentLines.map((line, index) => index === 0 ? color(line, BONE, true) : color(line, MIST)), width)
292
+ : renderOperationSectionPlain(options.currentTitle ?? "Current work", currentLines);
293
+ blocks.push(renderedSteps.join("\n"));
294
+ blocks.push(renderedCurrent.join("\n"));
295
+ if (options.prompt) {
296
+ blocks.push(options.isTTY ? color(options.prompt, BONE, true) : options.prompt);
297
+ }
298
+ return `${blocks.join("\n\n")}\n`;
215
299
  }
@@ -219,19 +219,7 @@ class UpProgress {
219
219
  return "";
220
220
  }
221
221
  const lines = this.renderLines(now);
222
- let output = "";
223
- if (this.prevLineCount > 0) {
224
- output += `\x1b[${this.prevLineCount}A`;
225
- }
226
- for (const line of lines) {
227
- output += `\x1b[2K${line}\n`;
228
- }
229
- // Clear any leftover lines from previous render that are no longer needed
230
- if (lines.length < this.prevLineCount) {
231
- for (let i = 0; i < this.prevLineCount - lines.length; i++) {
232
- output += `\x1b[2K\n`;
233
- }
234
- }
222
+ const output = (0, terminal_ui_1.renderOverwriteFrame)(lines, this.prevLineCount, true);
235
223
  this.prevLineCount = lines.length;
236
224
  return output;
237
225
  }
@@ -301,14 +289,8 @@ class UpProgress {
301
289
  }
302
290
  renderUpScreen(now) {
303
291
  const seenLabels = new Set();
304
- const steps = this.completed.map((phase) => {
305
- seenLabels.add(phase.label);
306
- return {
307
- label: this.renderUpStepLabel(phase.label),
308
- status: phase.status === "failure" ? "failed" : "done",
309
- detail: phase.detail,
310
- };
311
- });
292
+ const completedByLabel = new Map(this.completed.map((phase) => [phase.label, phase]));
293
+ const steps = [];
312
294
  let currentStepLabel = this.completed.some((phase) => phase.status === "failure")
313
295
  ? "Boot paused."
314
296
  : this.completed.length > 0
@@ -322,19 +304,44 @@ class UpProgress {
322
304
  const spinner = SPINNER_FRAMES[frameIndex];
323
305
  currentStepLabel = `${spinner} ${this.renderUpStepLabel(this.currentPhase.label)} (${elapsedSec}s)`;
324
306
  currentStepDetails = splitDetailLines(this.currentPhase.detail);
325
- steps.push({
326
- label: this.renderUpStepLabel(this.currentPhase.label),
327
- status: "active",
328
- });
329
- seenLabels.add(this.currentPhase.label);
330
307
  }
331
308
  for (const label of this.upPhasePlan) {
332
- if (!seenLabels.has(label)) {
309
+ seenLabels.add(label);
310
+ const completedPhase = completedByLabel.get(label);
311
+ if (completedPhase) {
312
+ steps.push({
313
+ label: this.renderUpStepLabel(label),
314
+ status: completedPhase.status === "failure" ? "failed" : "done",
315
+ detail: completedPhase.detail,
316
+ });
317
+ continue;
318
+ }
319
+ if (this.currentPhase?.label === label) {
333
320
  steps.push({
334
321
  label: this.renderUpStepLabel(label),
335
- status: "pending",
322
+ status: "active",
336
323
  });
324
+ continue;
337
325
  }
326
+ steps.push({
327
+ label: this.renderUpStepLabel(label),
328
+ status: "pending",
329
+ });
330
+ }
331
+ for (const phase of this.completed) {
332
+ if (!seenLabels.has(phase.label)) {
333
+ steps.push({
334
+ label: this.renderUpStepLabel(phase.label),
335
+ status: phase.status === "failure" ? "failed" : "done",
336
+ detail: phase.detail,
337
+ });
338
+ }
339
+ }
340
+ if (this.currentPhase && !seenLabels.has(this.currentPhase.label)) {
341
+ steps.push({
342
+ label: this.renderUpStepLabel(this.currentPhase.label),
343
+ status: "active",
344
+ });
338
345
  }
339
346
  return (0, terminal_ui_1.renderTerminalOperation)({
340
347
  isTTY: true,
@@ -342,8 +349,8 @@ class UpProgress {
342
349
  masthead: {
343
350
  subtitle: "Booting the local agent runtime.",
344
351
  },
345
- title: "Ouro boot checklist",
346
- summary: "Ouro will check for updates, prepare this machine, verify the providers your agents use right now, start the background service, and make sure it stays up.",
352
+ title: "Starting Ouro",
353
+ summary: "Ouro is bringing the local agent runtime online and will stop here if anything needs attention.",
347
354
  currentStep: {
348
355
  label: currentStepLabel,
349
356
  detailLines: currentStepDetails,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.444",
3
+ "version": "0.1.0-alpha.445",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",