@ouro.bot/cli 0.1.0-alpha.326 → 0.1.0-alpha.327

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,12 @@
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.327",
6
+ "changes": [
7
+ "fix(daemon): make startup stability polling respect stdout TTY capability so captured `ouro up` output is plain append-only text without raw ANSI cursor-control or color escapes, while interactive terminals keep in-place progress rendering."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.326",
6
12
  "changes": [
@@ -167,6 +167,8 @@ async function ensureDaemonRunning(deps) {
167
167
  daemonPid: runtimeResult.startedPid ?? null,
168
168
  /* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
169
169
  writeRaw: (text) => process.stdout.write(text),
170
+ /* v8 ignore next -- thin wrapper: real stdout TTY detection injected for captured-output safety @preserve */
171
+ isTTY: process.stdout.isTTY === true,
170
172
  /* v8 ignore next -- thin wrapper: real Date.now() injected for testability @preserve */
171
173
  now: () => Date.now(),
172
174
  /* v8 ignore next -- thin wrapper: real setTimeout injected for testability @preserve */
@@ -205,6 +207,8 @@ async function ensureDaemonRunning(deps) {
205
207
  daemonPid: lastPid,
206
208
  /* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
207
209
  writeRaw: (text) => process.stdout.write(text),
210
+ /* v8 ignore next -- thin wrapper: real stdout TTY detection injected for captured-output safety @preserve */
211
+ isTTY: process.stdout.isTTY === true,
208
212
  /* v8 ignore next -- thin wrapper: real Date.now() injected for testability @preserve */
209
213
  now: () => Date.now(),
210
214
  /* v8 ignore next -- thin wrapper: real setTimeout injected for testability @preserve */
@@ -68,66 +68,57 @@ function assessStability(payload, now) {
68
68
  * Build an ANSI string for in-place terminal display during polling.
69
69
  * Uses cursor-up and line-clear escapes to overwrite previous output.
70
70
  */
71
- function renderStartupProgress(payload, elapsed, prevLineCount = 0) {
71
+ function renderStartupProgress(payload, elapsed, prevLineCount = 0, options = {}) {
72
+ const isTTY = options.isTTY ?? true;
72
73
  const frameIndex = Math.floor(elapsed / 100) % SPINNER_FRAMES.length;
73
74
  const spinner = SPINNER_FRAMES[frameIndex];
74
75
  const lines = [];
75
76
  const elapsedSec = (elapsed / 1000).toFixed(1);
76
- lines.push(`${spinner} ${BOLD}waiting for agents${RESET} ${DIM}(${elapsedSec}s)${RESET}`);
77
+ lines.push(isTTY
78
+ ? `${spinner} ${BOLD}waiting for agents${RESET} ${DIM}(${elapsedSec}s)${RESET}`
79
+ : `${spinner} waiting for agents (${elapsedSec}s)`);
77
80
  for (const worker of payload.workers) {
78
- const statusColor = worker.status === "running" ? GREEN
79
- : worker.status === "crashed" ? RED
80
- : YELLOW;
81
- const statusText = `${statusColor}${worker.status}${RESET}`;
81
+ const statusText = isTTY ? colorStatus(worker.status) : worker.status;
82
82
  lines.push(` ${worker.agent}/${worker.worker}: ${statusText}`);
83
83
  }
84
- let output = "";
85
- if (prevLineCount > 0) {
86
- output += `\x1b[${prevLineCount}A`;
87
- }
88
- for (const line of lines) {
89
- output += `\x1b[2K${line}\n`;
90
- }
91
- return output;
84
+ return renderStartupLines(lines, prevLineCount, isTTY);
92
85
  }
93
86
  /**
94
87
  * Render a pre-socket status line showing what the daemon is doing.
95
88
  */
96
- function renderWaitingForDaemon(elapsed, latestEvent, prevLineCount = 0) {
89
+ function renderWaitingForDaemon(elapsed, latestEvent, prevLineCount = 0, options = {}) {
90
+ const isTTY = options.isTTY ?? true;
97
91
  const elapsedSec = (elapsed / 1000).toFixed(1);
98
92
  const frameIndex = Math.floor(elapsed / 100) % SPINNER_FRAMES.length;
99
93
  const spinner = SPINNER_FRAMES[frameIndex];
100
94
  const lines = [];
101
- lines.push(`${spinner} ${BOLD}waiting for daemon${RESET} ${DIM}(${elapsedSec}s)${RESET}`);
95
+ lines.push(isTTY
96
+ ? `${spinner} ${BOLD}waiting for daemon${RESET} ${DIM}(${elapsedSec}s)${RESET}`
97
+ : `${spinner} waiting for daemon (${elapsedSec}s)`);
102
98
  if (latestEvent) {
103
- lines.push(` ${DIM}${latestEvent}${RESET}`);
104
- }
105
- let output = "";
106
- if (prevLineCount > 0) {
107
- output += `\x1b[${prevLineCount}A`;
108
- }
109
- for (const line of lines) {
110
- output += `\x1b[2K${line}\n`;
99
+ lines.push(isTTY ? ` ${DIM}${latestEvent}${RESET}` : ` ${latestEvent}`);
111
100
  }
112
- return output;
101
+ return renderStartupLines(lines, prevLineCount, isTTY);
113
102
  }
114
103
  /**
115
104
  * Render the final summary after all agents have resolved.
116
105
  */
117
- function renderFinalSummary(result) {
106
+ function renderFinalSummary(result, isTTY) {
118
107
  const lines = [];
119
108
  for (const agent of result.stable) {
120
- lines.push(` ${GREEN}\u2713${RESET} ${agent}: ${GREEN}stable${RESET}`);
109
+ lines.push(isTTY ? ` ${GREEN}\u2713${RESET} ${agent}: ${GREEN}stable${RESET}` : ` \u2713 ${agent}: stable`);
121
110
  }
122
111
  for (const d of result.degraded) {
123
- lines.push(` ${RED}\u2717${RESET} ${d.agent}: ${RED}degraded${RESET}`);
112
+ lines.push(isTTY ? ` ${RED}\u2717${RESET} ${d.agent}: ${RED}degraded${RESET}` : ` \u2717 ${d.agent}: degraded`);
124
113
  if (d.errorReason !== "unknown error") {
125
- lines.push(` ${DIM}error: ${d.errorReason}${RESET}`);
114
+ lines.push(isTTY ? ` ${DIM}error: ${d.errorReason}${RESET}` : ` error: ${d.errorReason}`);
126
115
  }
127
116
  if (d.fixHint !== "check daemon logs") {
128
- lines.push(` ${DIM}fix: ${d.fixHint}${RESET}`);
117
+ lines.push(isTTY ? ` ${DIM}fix: ${d.fixHint}${RESET}` : ` fix: ${d.fixHint}`);
129
118
  }
130
119
  }
120
+ if (!isTTY)
121
+ return lines.join("\n") + "\n";
131
122
  return lines.map((line) => `\x1b[2K${line}`).join("\n") + "\n";
132
123
  }
133
124
  // ── Polling loop ──
@@ -141,6 +132,7 @@ function renderFinalSummary(result) {
141
132
  async function pollDaemonStartup(deps) {
142
133
  const startTime = deps.now();
143
134
  let prevLineCount = 0;
135
+ const isTTY = deps.isTTY ?? true;
144
136
  const isAlive = deps.isProcessAlive ?? defaultIsProcessAlive;
145
137
  (0, runtime_1.emitNervesEvent)({
146
138
  component: "daemon",
@@ -169,7 +161,7 @@ async function pollDaemonStartup(deps) {
169
161
  meta: { pid: deps.daemonPid, lastEvent: latestEvent },
170
162
  });
171
163
  // Clear the waiting line
172
- if (prevLineCount > 0) {
164
+ if (isTTY && prevLineCount > 0) {
173
165
  let clear = `\x1b[${prevLineCount}A`;
174
166
  for (let i = 0; i < prevLineCount; i++)
175
167
  clear += `\x1b[2K\n`;
@@ -182,12 +174,12 @@ async function pollDaemonStartup(deps) {
182
174
  }
183
175
  // Show what the daemon is doing from its log
184
176
  const latestEvent = deps.readLatestDaemonEvent?.() ?? null;
185
- const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount);
177
+ const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount, { isTTY });
186
178
  deps.writeRaw(output);
187
179
  prevLineCount = latestEvent ? 2 : 1;
188
180
  }
189
181
  if (payload) {
190
- const output = renderStartupProgress(payload, elapsed, prevLineCount);
182
+ const output = renderStartupProgress(payload, elapsed, prevLineCount, { isTTY });
191
183
  deps.writeRaw(output);
192
184
  prevLineCount = payload.workers.length + 1;
193
185
  const assessment = assessStability(payload, now);
@@ -196,7 +188,7 @@ async function pollDaemonStartup(deps) {
196
188
  stable: assessment.stable,
197
189
  degraded: assessment.degraded,
198
190
  };
199
- const summary = renderFinalSummary(result);
191
+ const summary = renderFinalSummary(result, isTTY);
200
192
  deps.writeRaw(summary);
201
193
  (0, runtime_1.emitNervesEvent)({
202
194
  component: "daemon",
@@ -214,6 +206,24 @@ async function pollDaemonStartup(deps) {
214
206
  await deps.sleep(POLL_INTERVAL_MS);
215
207
  }
216
208
  }
209
+ function colorStatus(status) {
210
+ const statusColor = status === "running" ? GREEN
211
+ : status === "crashed" ? RED
212
+ : YELLOW;
213
+ return `${statusColor}${status}${RESET}`;
214
+ }
215
+ function renderStartupLines(lines, prevLineCount, isTTY) {
216
+ if (!isTTY)
217
+ return lines.join("\n") + "\n";
218
+ let output = "";
219
+ if (prevLineCount > 0) {
220
+ output += `\x1b[${prevLineCount}A`;
221
+ }
222
+ for (const line of lines) {
223
+ output += `\x1b[2K${line}\n`;
224
+ }
225
+ return output;
226
+ }
217
227
  /* v8 ignore start -- process liveness check: uses real process.kill(0), tested via deployment @preserve */
218
228
  function defaultIsProcessAlive(pid) {
219
229
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.326",
3
+ "version": "0.1.0-alpha.327",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",