@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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 {
|