@ouro.bot/cli 0.1.0-alpha.417 → 0.1.0-alpha.418

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,18 @@
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.418",
6
+ "changes": [
7
+ "`ouro up` now uses the live progress renderer in interactive terminals instead of forcing static non-TTY output, so long startup/check phases animate instead of leaving a blinking cursor.",
8
+ "Non-TTY and captured `ouro up` output now prints current phase starts and changed detail lines (`launching daemon process`, `waiting for daemon socket`, `daemon answered`) before completion, avoiding silent waits in logs and terminal sessions that cannot render spinners.",
9
+ "Daemon startup readiness is completed explicitly before provider checks begin; `ouro up` no longer marks `starting daemon` as done by accidentally auto-completing it when the next phase starts.",
10
+ "Daemon startup polling can now report progress into the parent `ouro up` checklist without rendering its own nested startup TUI, keeping startup output as one coherent surface.",
11
+ "Runtime drift restart messages now summarize drift categories such as `code path` or `managed agents` without printing raw worktree/package paths in normal CLI output.",
12
+ "Default CLI stdout now writes exactly one newline instead of using `console.log`, removing the extra blank lines caused when progress renderers already include newline-terminated output.",
13
+ "`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the `ouro up` progress polish release."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.417",
6
18
  "changes": [
@@ -89,9 +89,13 @@ function defaultStartDaemonProcess(socketPath) {
89
89
  return Promise.resolve({ pid: child.pid ?? null });
90
90
  }
91
91
  function defaultWriteStdout(text) {
92
- // eslint-disable-next-line no-console -- terminal UX: CLI command output
93
- console.log(text);
92
+ process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
94
93
  }
94
+ /* v8 ignore start -- thin terminal adapter around process stdout @preserve */
95
+ function defaultWriteRaw(text) {
96
+ process.stdout.write(text);
97
+ }
98
+ /* v8 ignore stop */
95
99
  /**
96
100
  * Read the runtimeVersion from the first .ouro bundle's bundle-meta.json.
97
101
  * Returns undefined if none found or unreadable.
@@ -486,6 +490,8 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
486
490
  sendCommand: socket_client_1.sendDaemonCommand,
487
491
  startDaemonProcess: defaultStartDaemonProcess,
488
492
  writeStdout: defaultWriteStdout,
493
+ writeRaw: defaultWriteRaw,
494
+ isTTY: process.stdout.isTTY === true,
489
495
  checkSocketAlive: socket_client_1.checkDaemonSocketAlive,
490
496
  cleanupStaleSocket: defaultCleanupStaleSocket,
491
497
  fallbackPendingMessage: defaultFallbackPendingMessage,
@@ -179,6 +179,15 @@ function providerRepairCountSummary(count) {
179
179
  return "ok";
180
180
  return `${count} ${count === 1 ? "needs" : "need"} attention`;
181
181
  }
182
+ function daemonProgressSummary(result) {
183
+ if (result.verifyStartupStatus === false)
184
+ return "not answering yet";
185
+ if (result.alreadyRunning)
186
+ return "already running";
187
+ if (result.message.includes("restarted"))
188
+ return "restarted and ready";
189
+ return "ready";
190
+ }
182
191
  async function reportPostRepairProviderHealth(deps, repairedAgents, onProgress) {
183
192
  const remainingDegraded = await checkAgentProviders(deps, repairedAgents, onProgress);
184
193
  (0, runtime_1.emitNervesEvent)({
@@ -314,17 +323,19 @@ async function ensureDaemonRunning(deps, options = {}) {
314
323
  sendCommand: deps.sendCommand,
315
324
  socketPath: deps.socketPath,
316
325
  daemonPid: runtimeResult.startedPid ?? null,
317
- /* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
318
- writeRaw: (text) => process.stdout.write(text),
319
- /* v8 ignore next -- thin wrapper: real stdout TTY detection injected for captured-output safety @preserve */
320
- isTTY: process.stdout.isTTY === true,
321
- /* v8 ignore next -- thin wrapper: real Date.now() injected for testability @preserve */
322
- now: () => Date.now(),
323
- /* v8 ignore next -- thin wrapper: real setTimeout injected for testability @preserve */
324
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
326
+ /* v8 ignore next -- thin wrapper: real stdout fallback injected by default deps @preserve */
327
+ writeRaw: deps.writeRaw ?? ((text) => process.stdout.write(text)),
328
+ /* v8 ignore next -- thin wrapper: real stdout TTY detection injected by default deps @preserve */
329
+ isTTY: deps.isTTY ?? process.stdout.isTTY === true,
330
+ /* v8 ignore next -- thin wrapper: real Date.now fallback injected by default deps @preserve */
331
+ now: deps.now ?? (() => Date.now()),
332
+ /* v8 ignore next -- thin wrapper: real setTimeout fallback injected by default deps @preserve */
333
+ sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
325
334
  /* v8 ignore start -- daemon log tail + pid check: reads real filesystem, tested via deployment @preserve */
326
335
  readLatestDaemonEvent: readLatestDaemonStartupEvent,
327
336
  /* v8 ignore stop */
337
+ onProgress: deps.reportDaemonStartupPhase,
338
+ render: !deps.reportDaemonStartupPhase,
328
339
  });
329
340
  return {
330
341
  alreadyRunning: runtimeResult.alreadyRunning,
@@ -339,8 +350,8 @@ async function ensureDaemonRunning(deps, options = {}) {
339
350
  };
340
351
  let lastPid = null;
341
352
  for (let attempt = 0; attempt <= retryLimit; attempt += 1) {
342
- deps.reportDaemonStartupPhase?.("starting daemon...");
343
- deps.reportDaemonStartupPhase?.("waiting for daemon socket...");
353
+ deps.reportDaemonStartupPhase?.("launching daemon process");
354
+ deps.reportDaemonStartupPhase?.("waiting for daemon socket");
344
355
  deps.cleanupStaleSocket(deps.socketPath);
345
356
  const bootStartedAtMs = (deps.now ?? Date.now)();
346
357
  const started = await deps.startDaemonProcess(deps.socketPath);
@@ -354,17 +365,19 @@ async function ensureDaemonRunning(deps, options = {}) {
354
365
  sendCommand: deps.sendCommand,
355
366
  socketPath: deps.socketPath,
356
367
  daemonPid: lastPid,
357
- /* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
358
- writeRaw: (text) => process.stdout.write(text),
359
- /* v8 ignore next -- thin wrapper: real stdout TTY detection injected for captured-output safety @preserve */
360
- isTTY: process.stdout.isTTY === true,
361
- /* v8 ignore next -- thin wrapper: real Date.now() injected for testability @preserve */
362
- now: () => Date.now(),
363
- /* v8 ignore next -- thin wrapper: real setTimeout injected for testability @preserve */
364
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
368
+ /* v8 ignore next -- thin wrapper: real stdout fallback injected by default deps @preserve */
369
+ writeRaw: deps.writeRaw ?? ((text) => process.stdout.write(text)),
370
+ /* v8 ignore next -- thin wrapper: real stdout TTY detection injected by default deps @preserve */
371
+ isTTY: deps.isTTY ?? process.stdout.isTTY === true,
372
+ /* v8 ignore next -- thin wrapper: real Date.now fallback injected by default deps @preserve */
373
+ now: deps.now ?? (() => Date.now()),
374
+ /* v8 ignore next -- thin wrapper: real setTimeout fallback injected by default deps @preserve */
375
+ sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
365
376
  /* v8 ignore start -- daemon log tail + pid check: reads real filesystem, tested via deployment @preserve */
366
377
  readLatestDaemonEvent: readLatestDaemonStartupEvent,
367
378
  /* v8 ignore stop */
379
+ onProgress: deps.reportDaemonStartupPhase,
380
+ render: !deps.reportDaemonStartupPhase,
368
381
  });
369
382
  return {
370
383
  alreadyRunning: false,
@@ -376,7 +389,7 @@ async function ensureDaemonRunning(deps, options = {}) {
376
389
  if (!startupFailure.retryable || attempt >= retryLimit) {
377
390
  break;
378
391
  }
379
- deps.reportDaemonStartupPhase?.("daemon startup lost stability; cleaning up and retrying once...");
392
+ deps.reportDaemonStartupPhase?.("daemon startup lost stability; retrying once");
380
393
  }
381
394
  return {
382
395
  alreadyRunning: false,
@@ -449,7 +462,7 @@ async function waitForDaemonStartup(deps, options) {
449
462
  if (!sawSocket) {
450
463
  sawSocket = true;
451
464
  stableSinceMs = now();
452
- deps.reportDaemonStartupPhase?.("verifying daemon health...");
465
+ deps.reportDaemonStartupPhase?.("verifying daemon health");
453
466
  }
454
467
  if (!hasFreshCurrentBootHealthSignal(deps, options.bootStartedAtMs, options.pid)) {
455
468
  continue;
@@ -2469,7 +2482,13 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2469
2482
  }
2470
2483
  }
2471
2484
  const linkedVersionBeforeUp = deps.getCurrentCliVersion?.() ?? null;
2472
- const progress = new up_progress_1.UpProgress({ write: deps.writeStdout, isTTY: false });
2485
+ const outputIsTTY = deps.isTTY ?? process.stdout.isTTY === true;
2486
+ const progress = new up_progress_1.UpProgress({
2487
+ write: deps.writeRaw ?? deps.writeStdout,
2488
+ isTTY: outputIsTTY,
2489
+ now: deps.now ?? (() => Date.now()),
2490
+ autoRender: true,
2491
+ });
2473
2492
  // ── versioned CLI update check ──
2474
2493
  if (deps.checkForCliUpdate) {
2475
2494
  progress.startPhase("update check");
@@ -2631,6 +2650,12 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2631
2650
  progress.announceStep?.(label);
2632
2651
  },
2633
2652
  }, { initialAlive: daemonAliveBeforeStart });
2653
+ progress.completePhase("starting daemon", daemonProgressSummary(daemonResult));
2654
+ if (daemonResult.verifyStartupStatus === false) {
2655
+ progress.end();
2656
+ deps.writeStdout(daemonResult.message);
2657
+ return daemonResult.message;
2658
+ }
2634
2659
  if (!providerChecksAlreadyRun || daemonResult.alreadyRunning) {
2635
2660
  progress.startPhase("provider checks");
2636
2661
  const providerDegraded = await checkAlreadyRunningAgentProviders(deps, (msg) => progress.updateDetail(msg));
@@ -2638,7 +2663,6 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2638
2663
  progress.completePhase("provider checks", providerRepairCountSummary(providerDegraded.length));
2639
2664
  }
2640
2665
  progress.end();
2641
- deps.writeStdout(daemonResult.message);
2642
2666
  // Interactive repair for degraded agents (Unit 5) — skipped by --no-repair (Unit 6)
2643
2667
  if (daemonResult.stability?.degraded && daemonResult.stability.degraded.length > 0) {
2644
2668
  if (command.noRepair) {
@@ -83,14 +83,8 @@ function collectRuntimeDriftReasons(local, running) {
83
83
  }
84
84
  return reasons;
85
85
  }
86
- function formatRuntimeValue(reason) {
87
- if (reason.key === "configFingerprint") {
88
- return `${reason.running.slice(0, 12)} -> ${reason.local.slice(0, 12)}`;
89
- }
90
- return `${reason.running} -> ${reason.local}`;
91
- }
92
- function formatRuntimeDriftSummary(reasons) {
93
- return reasons.map((reason) => `${reason.label} ${formatRuntimeValue(reason)}`).join("; ");
86
+ function formatRuntimeDriftPublicSummary(reasons) {
87
+ return reasons.map((reason) => reason.label).join(", ");
94
88
  }
95
89
  async function ensureCurrentDaemonRuntime(deps) {
96
90
  const localRuntime = normalizeRuntimeIdentity({
@@ -107,7 +101,7 @@ async function ensureCurrentDaemonRuntime(deps) {
107
101
  let result;
108
102
  if (driftReasons.length > 0) {
109
103
  const includesVersionDrift = driftReasons.some((entry) => entry.key === "version");
110
- const driftSummary = formatRuntimeDriftSummary(driftReasons);
104
+ const publicDriftSummary = formatRuntimeDriftPublicSummary(driftReasons);
111
105
  try {
112
106
  await deps.stopDaemon();
113
107
  }
@@ -117,7 +111,7 @@ async function ensureCurrentDaemonRuntime(deps) {
117
111
  alreadyRunning: true,
118
112
  message: includesVersionDrift
119
113
  ? `daemon already running (${deps.socketPath}; could not replace stale daemon ${runningVersion} -> ${deps.localVersion}: ${reason})`
120
- : `daemon already running (${deps.socketPath}; could not replace drifted daemon ${driftSummary}: ${reason})`,
114
+ : `daemon already running (${deps.socketPath}; could not replace runtime drift ${publicDriftSummary}: ${reason})`,
121
115
  };
122
116
  (0, runtime_1.emitNervesEvent)({
123
117
  level: "warn",
@@ -148,12 +142,12 @@ async function ensureCurrentDaemonRuntime(deps) {
148
142
  const pid = started.pid ?? "unknown";
149
143
  const verified = await verifyDaemonStarted(deps);
150
144
  /* v8 ignore next -- daemon liveness failure: requires real daemon crash timing @preserve */
151
- const suffix = verified ? "" : "\ndaemon did not answer yet, so Ouro is checking repair paths next.";
145
+ const suffix = verified ? "" : "\ndaemon restart has not answered yet; check logs with `ouro logs` or run `ouro doctor`.";
152
146
  result = {
153
147
  alreadyRunning: false,
154
148
  message: includesVersionDrift
155
149
  ? `restarted stale daemon ${runningVersion} -> ${deps.localVersion} (pid ${pid})${suffix}`
156
- : `restarted drifted daemon (${driftSummary}) (pid ${pid})${suffix}`,
150
+ : `restarted daemon after runtime drift: ${publicDriftSummary} (pid ${pid})${suffix}`,
157
151
  verifyStartupStatus: verified,
158
152
  startedPid: started.pid ?? null,
159
153
  };
@@ -134,6 +134,14 @@ async function pollDaemonStartup(deps) {
134
134
  let prevLineCount = 0;
135
135
  const isTTY = deps.isTTY ?? true;
136
136
  const isAlive = deps.isProcessAlive ?? defaultIsProcessAlive;
137
+ const shouldRender = deps.render ?? true;
138
+ let lastProgress = null;
139
+ const reportProgress = (message) => {
140
+ if (!deps.onProgress || message === lastProgress)
141
+ return;
142
+ lastProgress = message;
143
+ deps.onProgress(message);
144
+ };
137
145
  (0, runtime_1.emitNervesEvent)({
138
146
  component: "daemon",
139
147
  event: "daemon.startup_poll_start",
@@ -174,22 +182,30 @@ async function pollDaemonStartup(deps) {
174
182
  }
175
183
  // Show what the daemon is doing from its log
176
184
  const latestEvent = deps.readLatestDaemonEvent?.() ?? null;
177
- const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount, { isTTY });
178
- deps.writeRaw(output);
179
- prevLineCount = latestEvent ? 2 : 1;
185
+ reportProgress(latestEvent ?? "waiting for daemon");
186
+ if (shouldRender) {
187
+ const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount, { isTTY });
188
+ deps.writeRaw(output);
189
+ prevLineCount = latestEvent ? 2 : 1;
190
+ }
180
191
  }
181
192
  if (payload) {
182
- const output = renderStartupProgress(payload, elapsed, prevLineCount, { isTTY });
183
- deps.writeRaw(output);
184
- prevLineCount = payload.workers.length + 1;
193
+ reportProgress(formatStartupProgressDetail(payload));
194
+ if (shouldRender) {
195
+ const output = renderStartupProgress(payload, elapsed, prevLineCount, { isTTY });
196
+ deps.writeRaw(output);
197
+ prevLineCount = payload.workers.length + 1;
198
+ }
185
199
  const assessment = assessStability(payload, now);
186
200
  if (assessment.resolved) {
187
201
  const result = {
188
202
  stable: assessment.stable,
189
203
  degraded: assessment.degraded,
190
204
  };
191
- const summary = renderFinalSummary(result, isTTY);
192
- deps.writeRaw(summary);
205
+ if (shouldRender) {
206
+ const summary = renderFinalSummary(result, isTTY);
207
+ deps.writeRaw(summary);
208
+ }
193
209
  (0, runtime_1.emitNervesEvent)({
194
210
  component: "daemon",
195
211
  event: "daemon.startup_poll_end",
@@ -206,6 +222,12 @@ async function pollDaemonStartup(deps) {
206
222
  await deps.sleep(POLL_INTERVAL_MS);
207
223
  }
208
224
  }
225
+ function formatStartupProgressDetail(payload) {
226
+ if (payload.workers.length === 0)
227
+ return "daemon answered";
228
+ const workers = payload.workers.map((worker) => `${worker.agent}/${worker.worker} ${worker.status}`).join(", ");
229
+ return `waiting for agents: ${workers}`;
230
+ }
209
231
  function colorStatus(status) {
210
232
  const statusColor = status === "running" ? GREEN
211
233
  : status === "crashed" ? RED
@@ -7,8 +7,9 @@
7
7
  * cursor control for in-place overwriting in TTY mode, and falls back to
8
8
  * static line-per-phase output in non-TTY mode.
9
9
  *
10
- * The caller drives animation by calling `render(now)` on a setInterval.
11
- * This module owns no timers.
10
+ * The caller can drive animation by calling `render(now)`. In production CLI
11
+ * use, `autoRender` starts a short-lived timer while a TTY phase is active so
12
+ * long operations never leave a dead-looking cursor.
12
13
  */
13
14
  Object.defineProperty(exports, "__esModule", { value: true });
14
15
  exports.UpProgress = void 0;
@@ -23,15 +24,30 @@ const GREEN = "\x1b[38;2;46;204;64m";
23
24
  class UpProgress {
24
25
  write;
25
26
  isTTY;
27
+ now;
28
+ autoRender;
29
+ renderIntervalMs;
30
+ setTimer;
31
+ clearTimer;
26
32
  completed = [];
27
33
  currentPhase = null;
34
+ currentDetail = null;
28
35
  prevLineCount = 0;
29
36
  ended = false;
37
+ renderTimer = null;
30
38
  constructor(options) {
31
39
  /* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
32
40
  this.write = options?.write ?? ((text) => process.stdout.write(text));
33
41
  /* v8 ignore next -- thin wrapper: real isTTY check injected for testability @preserve */
34
42
  this.isTTY = options?.isTTY ?? (process.stdout.isTTY === true);
43
+ /* v8 ignore next -- thin wrapper: real Date.now injected for testability @preserve */
44
+ this.now = options?.now ?? (() => Date.now());
45
+ this.autoRender = options?.autoRender ?? false;
46
+ this.renderIntervalMs = options?.renderIntervalMs ?? 80;
47
+ /* v8 ignore start -- real timers are injected in tests when needed @preserve */
48
+ this.setTimer = options?.setInterval ?? ((callback, ms) => setInterval(callback, ms));
49
+ this.clearTimer = options?.clearInterval ?? ((handle) => clearInterval(handle));
50
+ /* v8 ignore stop */
35
51
  }
36
52
  /**
37
53
  * Begin a new phase with spinner. If a phase is already active, it is
@@ -41,26 +57,44 @@ class UpProgress {
41
57
  if (this.currentPhase) {
42
58
  this.completePhase(this.currentPhase.label);
43
59
  }
44
- this.currentPhase = { label, startedAt: Date.now() };
60
+ this.currentPhase = { label, startedAt: this.now() };
61
+ this.currentDetail = null;
62
+ if (this.isTTY) {
63
+ this.ensureAutoRender();
64
+ this.flushRender();
65
+ }
66
+ else {
67
+ this.write(` ... ${label}\n`);
68
+ }
45
69
  }
46
70
  /**
47
71
  * Emit a one-line status breadcrumb in non-TTY mode without affecting the
48
72
  * accumulated checklist state. Used for daemon startup sub-steps.
49
73
  */
50
74
  announceStep(label) {
75
+ if (this.currentPhase) {
76
+ this.updateDetail(label);
77
+ return;
78
+ }
51
79
  if (this.isTTY)
52
80
  return;
53
- this.write(label);
81
+ this.write(` ${label}\n`);
54
82
  }
55
83
  /**
56
84
  * Update the sub-step detail on the current spinner phase. Rendered as
57
- * "label (Xs) -- detail" in TTY mode. No-op in non-TTY mode or when
58
- * no phase is active.
85
+ * "label (Xs) -- detail" in TTY mode. In non-TTY mode, writes changed
86
+ * detail lines so long operations remain visible in logs and captured output.
59
87
  */
60
88
  updateDetail(detail) {
61
- if (!this.isTTY || !this.currentPhase)
89
+ if (!this.currentPhase || detail === this.currentDetail)
62
90
  return;
91
+ this.currentDetail = detail;
63
92
  this.currentPhase.detail = detail;
93
+ if (this.isTTY) {
94
+ this.flushRender();
95
+ return;
96
+ }
97
+ this.write(` ${detail}\n`);
64
98
  }
65
99
  /**
66
100
  * Mark the current phase as done. In non-TTY mode, immediately writes
@@ -70,16 +104,21 @@ class UpProgress {
70
104
  if (!this.currentPhase) {
71
105
  return;
72
106
  }
73
- const elapsedMs = Date.now() - this.currentPhase.startedAt;
107
+ const elapsedMs = this.now() - this.currentPhase.startedAt;
74
108
  this.completed.push({ label, detail });
75
109
  this.currentPhase = null;
110
+ this.currentDetail = null;
111
+ this.stopAutoRender();
76
112
  (0, runtime_1.emitNervesEvent)({
77
113
  component: "daemon",
78
114
  event: "daemon.up_phase_complete",
79
115
  message: `phase complete: ${label}`,
80
116
  meta: { phase: label, detail: detail ?? null, elapsedMs },
81
117
  });
82
- if (!this.isTTY) {
118
+ if (this.isTTY) {
119
+ this.flushRender();
120
+ }
121
+ else {
83
122
  const detailStr = detail ? ` \u2014 ${detail}` : "";
84
123
  this.write(` \u2713 ${label}${detailStr}\n`);
85
124
  }
@@ -134,12 +173,30 @@ class UpProgress {
134
173
  this.ended = true;
135
174
  if (this.currentPhase) {
136
175
  this.currentPhase = null;
176
+ this.currentDetail = null;
137
177
  }
178
+ this.stopAutoRender();
138
179
  if (this.isTTY) {
139
- const output = this.render(Date.now());
140
- if (output) {
141
- this.write(output);
142
- }
180
+ this.flushRender();
181
+ }
182
+ }
183
+ ensureAutoRender() {
184
+ if (!this.autoRender || !this.isTTY || this.renderTimer !== null) {
185
+ return;
186
+ }
187
+ this.renderTimer = this.setTimer(() => this.flushRender(), this.renderIntervalMs);
188
+ }
189
+ stopAutoRender() {
190
+ if (this.renderTimer === null) {
191
+ return;
192
+ }
193
+ this.clearTimer(this.renderTimer);
194
+ this.renderTimer = null;
195
+ }
196
+ flushRender() {
197
+ const output = this.render(this.now());
198
+ if (output) {
199
+ this.write(output);
143
200
  }
144
201
  }
145
202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.417",
3
+ "version": "0.1.0-alpha.418",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",