@ouro.bot/cli 0.1.0-alpha.315 → 0.1.0-alpha.316
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 +6 -0
- package/dist/heart/daemon/cli-exec.js +15 -8
- package/dist/heart/daemon/up-progress.js +126 -0
- package/dist/senses/cli/ouro-tui.js +10 -0
- package/package.json +10 -1
- package/dist/outlook-ui/assets/index-BiYn3Fwj.js +0 -61
- package/dist/outlook-ui/assets/index-LwChZTgL.css +0 -1
- package/dist/outlook-ui/index.html +0 -15
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.316",
|
|
6
|
+
"changes": [
|
|
7
|
+
"feat(daemon): unified progress TUI for ouro up lifecycle. New UpProgress accumulated-checklist renderer replaces disconnected writeStdout calls with a cohesive progress display showing completed phases with checkmarks and active phase with spinner. In-place ANSI overwrite in TTY mode, static lines in non-TTY (CI/pipes). Wired into daemon.up handler for all phases: update check, system setup, agent updates, bundle pruning, daemon start. Emits nerves events for each phase completion."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.315",
|
|
6
12
|
"changes": [
|
|
@@ -75,6 +75,7 @@ const interactive_repair_1 = require("./interactive-repair");
|
|
|
75
75
|
const agentic_repair_1 = require("./agentic-repair");
|
|
76
76
|
const startup_tui_1 = require("./startup-tui");
|
|
77
77
|
const stale_bundle_prune_1 = require("./stale-bundle-prune");
|
|
78
|
+
const up_progress_1 = require("./up-progress");
|
|
78
79
|
// ── ensureDaemonRunning ──
|
|
79
80
|
async function ensureDaemonRunning(deps) {
|
|
80
81
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
@@ -814,19 +815,19 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
814
815
|
}
|
|
815
816
|
}
|
|
816
817
|
const linkedVersionBeforeUp = deps.getCurrentCliVersion?.() ?? null;
|
|
818
|
+
const progress = new up_progress_1.UpProgress({ write: deps.writeStdout, isTTY: false });
|
|
817
819
|
// ── versioned CLI update check ──
|
|
818
820
|
if (deps.checkForCliUpdate) {
|
|
819
|
-
|
|
821
|
+
progress.startPhase("update check");
|
|
820
822
|
let pendingReExec = false;
|
|
821
823
|
try {
|
|
822
824
|
const updateResult = await deps.checkForCliUpdate();
|
|
823
825
|
if (updateResult.available && updateResult.latestVersion) {
|
|
824
|
-
deps.writeStdout(`installing ${updateResult.latestVersion}...`);
|
|
825
826
|
/* v8 ignore next -- fallback: getCurrentCliVersion always injected in tests @preserve */
|
|
826
827
|
const currentVersion = linkedVersionBeforeUp ?? "unknown";
|
|
827
828
|
await deps.installCliVersion(updateResult.latestVersion);
|
|
828
829
|
deps.activateCliVersion(updateResult.latestVersion);
|
|
829
|
-
|
|
830
|
+
progress.completePhase("update check", `installed ${updateResult.latestVersion}`);
|
|
830
831
|
const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(currentVersion, updateResult.latestVersion);
|
|
831
832
|
/* v8 ignore next -- buildChangelogCommand is non-null when an actual newer version is installed @preserve */
|
|
832
833
|
if (changelogCommand) {
|
|
@@ -847,13 +848,16 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
847
848
|
}
|
|
848
849
|
/* v8 ignore stop */
|
|
849
850
|
if (pendingReExec) {
|
|
851
|
+
progress.end();
|
|
850
852
|
deps.reExecFromNewVersion(args);
|
|
851
853
|
}
|
|
852
854
|
else {
|
|
853
|
-
|
|
855
|
+
progress.completePhase("update check", "up to date");
|
|
854
856
|
}
|
|
855
857
|
}
|
|
858
|
+
progress.startPhase("system setup");
|
|
856
859
|
await performSystemSetup(deps);
|
|
860
|
+
progress.completePhase("system setup");
|
|
857
861
|
// Track whether we've already printed the "ouro updated to" message
|
|
858
862
|
// this turn so the bundle-meta-fallback path below doesn't double-print.
|
|
859
863
|
// There are three independent paths that can detect "the binary just
|
|
@@ -919,15 +923,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
919
923
|
const to = updateSummary.updated[0].to;
|
|
920
924
|
const fromStr = from ? ` (was ${from})` : "";
|
|
921
925
|
const count = agents.length;
|
|
922
|
-
|
|
926
|
+
progress.startPhase("agent updates");
|
|
927
|
+
progress.completePhase("agent updates", `${count} agent${count === 1 ? "" : "s"} to runtime ${to}${fromStr}`);
|
|
923
928
|
}
|
|
924
929
|
// ── stale bundle pruning ──
|
|
925
930
|
const prunedBundles = (0, stale_bundle_prune_1.pruneStaleEphemeralBundles)({ bundlesRoot: deps.bundlesRoot });
|
|
926
|
-
|
|
927
|
-
|
|
931
|
+
if (prunedBundles.length > 0) {
|
|
932
|
+
progress.startPhase("bundle cleanup");
|
|
933
|
+
progress.completePhase("bundle cleanup", `pruned ${prunedBundles.length} stale bundle${prunedBundles.length === 1 ? "" : "s"}`);
|
|
928
934
|
}
|
|
929
|
-
|
|
935
|
+
progress.startPhase("starting daemon");
|
|
930
936
|
const daemonResult = await ensureDaemonRunning(deps);
|
|
937
|
+
progress.end();
|
|
931
938
|
deps.writeStdout(daemonResult.message);
|
|
932
939
|
// Interactive repair for degraded agents (Unit 5) — skipped by --no-repair (Unit 6)
|
|
933
940
|
if (daemonResult.stability?.degraded && daemonResult.stability.degraded.length > 0) {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* UpProgress — accumulated-checklist progress renderer for `ouro up`.
|
|
4
|
+
*
|
|
5
|
+
* Displays completed phases with checkmarks, the current phase with a
|
|
6
|
+
* spinner and elapsed time, and pending phases as plain text. Uses ANSI
|
|
7
|
+
* cursor control for in-place overwriting in TTY mode, and falls back to
|
|
8
|
+
* static line-per-phase output in non-TTY mode.
|
|
9
|
+
*
|
|
10
|
+
* The caller drives animation by calling `render(now)` on a setInterval.
|
|
11
|
+
* This module owns no timers.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.UpProgress = void 0;
|
|
15
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
16
|
+
// ── ANSI constants (shared with startup-tui.ts pattern) ──
|
|
17
|
+
const SPINNER_FRAMES = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
|
|
18
|
+
const RESET = "\x1b[0m";
|
|
19
|
+
const BOLD = "\x1b[1m";
|
|
20
|
+
const DIM = "\x1b[2m";
|
|
21
|
+
const GREEN = "\x1b[38;2;46;204;64m";
|
|
22
|
+
// ── UpProgress class ──
|
|
23
|
+
class UpProgress {
|
|
24
|
+
write;
|
|
25
|
+
isTTY;
|
|
26
|
+
completed = [];
|
|
27
|
+
currentPhase = null;
|
|
28
|
+
prevLineCount = 0;
|
|
29
|
+
ended = false;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
/* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
|
|
32
|
+
this.write = options?.write ?? ((text) => process.stdout.write(text));
|
|
33
|
+
/* v8 ignore next -- thin wrapper: real isTTY check injected for testability @preserve */
|
|
34
|
+
this.isTTY = options?.isTTY ?? (process.stdout.isTTY === true);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Begin a new phase with spinner. If a phase is already active, it is
|
|
38
|
+
* auto-completed (no detail text).
|
|
39
|
+
*/
|
|
40
|
+
startPhase(label) {
|
|
41
|
+
if (this.currentPhase) {
|
|
42
|
+
this.completePhase(this.currentPhase.label);
|
|
43
|
+
}
|
|
44
|
+
this.currentPhase = { label, startedAt: Date.now() };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Mark the current phase as done. In non-TTY mode, immediately writes
|
|
48
|
+
* a static line. Emits a nerves event for observability.
|
|
49
|
+
*/
|
|
50
|
+
completePhase(label, detail) {
|
|
51
|
+
if (!this.currentPhase) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const elapsedMs = Date.now() - this.currentPhase.startedAt;
|
|
55
|
+
this.completed.push({ label, detail });
|
|
56
|
+
this.currentPhase = null;
|
|
57
|
+
(0, runtime_1.emitNervesEvent)({
|
|
58
|
+
component: "daemon",
|
|
59
|
+
event: "daemon.up_phase_complete",
|
|
60
|
+
message: `phase complete: ${label}`,
|
|
61
|
+
meta: { phase: label, detail: detail ?? null, elapsedMs },
|
|
62
|
+
});
|
|
63
|
+
if (!this.isTTY) {
|
|
64
|
+
const detailStr = detail ? ` \u2014 ${detail}` : "";
|
|
65
|
+
this.write(` \u2713 ${label}${detailStr}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build an ANSI string for in-place terminal display. Returns empty
|
|
70
|
+
* string in non-TTY mode (output is written eagerly in completePhase).
|
|
71
|
+
*/
|
|
72
|
+
render(now) {
|
|
73
|
+
if (!this.isTTY) {
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
const lines = [];
|
|
77
|
+
// Completed phases
|
|
78
|
+
for (const phase of this.completed) {
|
|
79
|
+
const detailStr = phase.detail ? ` ${DIM}\u2014 ${phase.detail}${RESET}` : "";
|
|
80
|
+
lines.push(` ${GREEN}\u2713${RESET} ${phase.label}${detailStr}`);
|
|
81
|
+
}
|
|
82
|
+
// Current phase with spinner
|
|
83
|
+
if (this.currentPhase) {
|
|
84
|
+
const elapsed = now - this.currentPhase.startedAt;
|
|
85
|
+
const elapsedSec = (elapsed / 1000).toFixed(1);
|
|
86
|
+
const frameIndex = Math.floor(elapsed / 80) % SPINNER_FRAMES.length;
|
|
87
|
+
const spinner = SPINNER_FRAMES[frameIndex];
|
|
88
|
+
lines.push(` ${BOLD}${spinner}${RESET} ${this.currentPhase.label} ${DIM}(${elapsedSec}s)${RESET}`);
|
|
89
|
+
}
|
|
90
|
+
let output = "";
|
|
91
|
+
if (this.prevLineCount > 0) {
|
|
92
|
+
output += `\x1b[${this.prevLineCount}A`;
|
|
93
|
+
}
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
output += `\x1b[2K${line}\n`;
|
|
96
|
+
}
|
|
97
|
+
// Clear any leftover lines from previous render that are no longer needed
|
|
98
|
+
if (lines.length < this.prevLineCount) {
|
|
99
|
+
for (let i = 0; i < this.prevLineCount - lines.length; i++) {
|
|
100
|
+
output += `\x1b[2K\n`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
this.prevLineCount = lines.length;
|
|
104
|
+
return output;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Finalize the progress display. Clears the current phase (if any) and
|
|
108
|
+
* writes the final checklist state. Idempotent.
|
|
109
|
+
*/
|
|
110
|
+
end() {
|
|
111
|
+
if (this.ended) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this.ended = true;
|
|
115
|
+
if (this.currentPhase) {
|
|
116
|
+
this.currentPhase = null;
|
|
117
|
+
}
|
|
118
|
+
if (this.isTTY) {
|
|
119
|
+
const output = this.render(Date.now());
|
|
120
|
+
if (output) {
|
|
121
|
+
this.write(output);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.UpProgress = UpProgress;
|
|
@@ -253,6 +253,16 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
253
253
|
// PageUp/PageDown: suppress (no text insertion, no action)
|
|
254
254
|
if (key.pageUp || key.pageDown)
|
|
255
255
|
return;
|
|
256
|
+
// Alt+Enter (single data event): Ink checks `return: input === '\r'` before
|
|
257
|
+
// stripping the \x1b prefix, so key.return is false when the terminal sends
|
|
258
|
+
// \x1b\r as one chunk. Detect via the stripped inputChar instead.
|
|
259
|
+
if (inputChar === "\r" && key.meta) {
|
|
260
|
+
lastEscTime.current = 0;
|
|
261
|
+
const before = inputRef.current.slice(0, cursorRef.current);
|
|
262
|
+
const after = inputRef.current.slice(cursorRef.current);
|
|
263
|
+
updateInput(before + "\n" + after, cursorRef.current + 1);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
256
266
|
if (key.return) {
|
|
257
267
|
// Alt+Enter: detect via key.meta OR recent ESC (within 50ms — Ink splits \x1b\r)
|
|
258
268
|
const recentEsc = (Date.now() - lastEscTime.current) < 50;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.316",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"teams": "tsc && node dist/senses/teams-entry.js --agent ouroboros",
|
|
27
27
|
"bluebubbles": "tsc && node dist/senses/bluebubbles/entry.js --agent ouroboros",
|
|
28
28
|
"test": "vitest run",
|
|
29
|
+
"test:outlook-ui": "npm test --prefix packages/outlook-ui",
|
|
29
30
|
"test:coverage:vitest": "vitest run --coverage",
|
|
30
31
|
"test:coverage": "node scripts/run-coverage-gate.cjs",
|
|
31
32
|
"build": "tsc && (cd packages/outlook-ui && npm install --ignore-scripts 2>/dev/null && npm run build && cp -r dist ../../dist/outlook-ui) || echo 'outlook-ui build skipped'",
|
|
@@ -50,11 +51,19 @@
|
|
|
50
51
|
"url": "https://github.com/ouroborosbot/ouroboros"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
54
|
+
"@testing-library/react": "^16.3.2",
|
|
53
55
|
"@types/semver": "^7.7.1",
|
|
54
56
|
"@vitest/coverage-v8": "^4.0.18",
|
|
55
57
|
"eslint": "^10.0.2",
|
|
58
|
+
"jsdom": "^29.0.2",
|
|
56
59
|
"typescript": "^5.7.0",
|
|
57
60
|
"typescript-eslint": "^8.56.1",
|
|
58
61
|
"vitest": "^4.0.18"
|
|
62
|
+
},
|
|
63
|
+
"overrides": {
|
|
64
|
+
"@testing-library/react": {
|
|
65
|
+
"react": "$react",
|
|
66
|
+
"@types/react": "$@types/react"
|
|
67
|
+
}
|
|
59
68
|
}
|
|
60
69
|
}
|