@nonbot/cli 0.9.6 → 0.9.7

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.
@@ -13,7 +13,7 @@ import { applyPaneTitle, applyPaneState } from '../lib/pane-title.js';
13
13
  import { installService, uninstallService } from '../lib/service.js';
14
14
  import { detectTmuxSession, inTmuxSession, nonbotTmuxOptOut, resolveTerminal, } from '../lib/terminal.js';
15
15
  import { VERSION } from '../version.js';
16
- import { errorBlock, statusRow, daemonOpener, daemonCloser, pollTick, activationCard, runSummary, formatElapsed, liveFooter, needsYouBanner, resumedLine, buildPaneBorderFormat, buildTmuxStatusLeft, buildTmuxStatusRight, c, } from '../lib/output.js';
16
+ import { errorBlock, statusRow, daemonOpener, daemonCloser, activationCard, runSummary, formatElapsed, liveFooter, needsYouBanner, resumedLine, buildPaneBorderFormat, buildTmuxStatusLeft, buildTmuxStatusRight, WORDMARK_WIDTH, c, } from '../lib/output.js';
17
17
  export const POLL_FAST_MS = 2000;
18
18
  export const POLL_MAX_MS = 30000;
19
19
  export const POLL_INTERVAL_MS = POLL_FAST_MS;
@@ -89,12 +89,12 @@ export function applyNonbotTmuxConfig(deps = {}) {
89
89
  tmux('set-option', '-g', 'pane-border-lines', 'heavy');
90
90
  tmux('set-option', '-g', 'status', 'on');
91
91
  tmux('set-option', '-g', 'status-interval', '5');
92
- tmux('set-option', '-g', 'status-style', 'bg=#0b0b12,fg=#8b949e');
92
+ tmux('set-option', '-g', 'status-style', 'bg=#0a0d17,fg=#8a93a6');
93
93
  tmux('set-option', '-g', 'status-left-length', '60');
94
94
  tmux('set-option', '-g', 'status-right-length', '120');
95
95
  tmux('set-option', '-g', 'status-left', buildTmuxStatusLeft({ ascii }));
96
96
  tmux('set-option', '-g', 'status-right', buildTmuxStatusRight({ ascii }));
97
- tmux('set-option', '-g', 'window-status-current-style', 'fg=#0b0b12,bg=#4fc3f7,bold');
97
+ tmux('set-option', '-g', 'window-status-current-style', 'fg=#0a0d17,bg=#6c8cff,bold');
98
98
  tmux('refresh-client', '-S');
99
99
  }
100
100
  if (!stdoutIsTTY)
@@ -289,13 +289,11 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
289
289
  catch {
290
290
  }
291
291
  };
292
- const stdoutIsTTY = deps.stdoutIsTTY ?? (process.stdout?.isTTY === true && !process.env.VITEST);
293
- const footerEnabled = stdoutIsTTY && !headless;
294
- let footerActive = false;
292
+ const log = (s) => rawLog(s);
295
293
  let lastPollOk = true;
296
294
  const noBell = process.env.NONBOT_NO_BELL === '1' || process.env.NONBOT_NO_BELL === 'true';
297
- const ERASE = '\r\x1b[K';
298
- const footerHealth = () => {
295
+ const stdoutIsTTY = deps.stdoutIsTTY ?? (process.stdout?.isTTY === true && !process.env.VITEST);
296
+ const pollHealth = () => {
299
297
  if (openPrompts.size > 0)
300
298
  return 'waiting';
301
299
  if (!lastPollOk)
@@ -304,7 +302,7 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
304
302
  return 'backoff';
305
303
  return 'live';
306
304
  };
307
- const renderFooter = () => liveFooter({
305
+ const heartbeatLine = () => liveFooter({
308
306
  running: trackedPanes.size,
309
307
  waiting: openPrompts.size,
310
308
  done: doneCount,
@@ -312,33 +310,8 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
312
310
  nextPollMs: fixedInterval ?? currentInterval,
313
311
  version: `v${VERSION}`,
314
312
  machine: machineName,
315
- health: footerHealth(),
313
+ health: pollHealth(),
316
314
  });
317
- const refreshFooter = () => {
318
- if (footerEnabled && footerActive)
319
- rawLog(ERASE + renderFooter());
320
- };
321
- const startFooter = () => {
322
- if (!footerEnabled)
323
- return;
324
- footerActive = true;
325
- rawLog(ERASE + renderFooter());
326
- };
327
- const log = (s) => {
328
- if (!footerEnabled || !footerActive) {
329
- rawLog(s);
330
- return;
331
- }
332
- rawLog(ERASE);
333
- rawLog(s);
334
- rawLog(ERASE + renderFooter());
335
- };
336
- const dropFooter = () => {
337
- if (footerActive) {
338
- footerActive = false;
339
- rawLog(ERASE);
340
- }
341
- };
342
315
  const emitSummary = () => {
343
316
  log(runSummary({ running: trackedPanes.size, done: doneCount, failed: failedCount }) + '\n');
344
317
  pushTmuxStatus();
@@ -372,7 +345,7 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
372
345
  }) +
373
346
  '\n');
374
347
  retitlePane('waiting', paneId, meta?.story ?? '');
375
- if (!noBell && footerEnabled)
348
+ if (!noBell && stdoutIsTTY)
376
349
  rawLog('\x07');
377
350
  changed = true;
378
351
  }
@@ -391,7 +364,6 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
391
364
  }
392
365
  if (changed) {
393
366
  pushTmuxStatus();
394
- refreshFooter();
395
367
  }
396
368
  };
397
369
  let running = true;
@@ -401,7 +373,6 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
401
373
  running = false;
402
374
  for (const id of [...runHeartbeats.keys()])
403
375
  stopHeartbeat(id);
404
- dropFooter();
405
376
  pushTmuxStatus(0, true);
406
377
  rawLog('\n' + statusRow('✓', 'daemon stopped', 'Ctrl-C received') + '\n');
407
378
  process.exit(0);
@@ -428,11 +399,16 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
428
399
  ];
429
400
  if (tmuxSessionName)
430
401
  badgeSublines.push(`tmux host: ${tmuxSessionName}`);
402
+ const stdoutCols = deps.stdoutColumns ?? process.stdout?.columns ?? 0;
403
+ const stdoutRows = deps.stdoutRows ?? process.stdout?.rows ?? 0;
404
+ const compactMast = stdoutIsTTY &&
405
+ ((stdoutCols > 0 && stdoutCols < WORDMARK_WIDTH + 4) || (stdoutRows > 0 && stdoutRows < 16));
431
406
  log(daemonOpener(`v${VERSION}`, {
432
407
  state: 'IDLE',
433
408
  badgeColor: 'green',
434
409
  badgeLabel: 'CONNECTED',
435
410
  badgeSublines,
411
+ compact: compactMast,
436
412
  }) + '\n\n');
437
413
  const headerMarker = c.green('[ + ]');
438
414
  const headerLabel = c.white('LISTENING · READY FOR ACTIVATIONS');
@@ -458,7 +434,6 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
458
434
  }) + '\n');
459
435
  log('\n');
460
436
  pushTmuxStatus();
461
- startFooter();
462
437
  while (running) {
463
438
  let firedThisPoll = false;
464
439
  try {
@@ -719,12 +694,8 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
719
694
  emitSummary();
720
695
  }
721
696
  }
722
- if (!firedThisPoll && pendingKills.length === 0 && !footerEnabled) {
723
- log(pollTick({
724
- pending: 0,
725
- sleepMs: fixedInterval ?? currentInterval,
726
- backingOff: fixedInterval === undefined && currentInterval > POLL_FAST_MS,
727
- }) + '\n');
697
+ if (!firedThisPoll && pendingKills.length === 0 && !tmuxChromeEnabled) {
698
+ log(heartbeatLine() + '\n');
728
699
  }
729
700
  evictOldestToCap(seen, 500);
730
701
  }
@@ -736,13 +707,11 @@ export async function runDaemonCommand(args = [], options = {}, deps = {}) {
736
707
  if (options.oneShot)
737
708
  break;
738
709
  if (fixedInterval !== undefined) {
739
- refreshFooter();
740
710
  await sleep(fixedInterval);
741
711
  }
742
712
  else {
743
713
  if (firedThisPoll)
744
714
  currentInterval = POLL_FAST_MS;
745
- refreshFooter();
746
715
  await sleep(currentInterval);
747
716
  if (!firedThisPoll) {
748
717
  currentInterval = Math.min(currentInterval * 2, maxIntervalMs);
@@ -1,12 +1,15 @@
1
1
  import { ANSI, renderBannerHeader } from './banner.js';
2
2
  export { ANSI };
3
3
  export const BRAND = {
4
- white: '\x1b[38;2;255;255;255m',
5
- cyan: '\x1b[38;2;79;195;247m',
6
- green: '\x1b[38;2;40;199;111m',
7
- amber: '\x1b[38;2;255;159;67m',
8
- red: '\x1b[38;2;255;76;81m',
9
- muted: '\x1b[38;2;139;148;158m',
4
+ white: '\x1b[38;2;240;235;226m',
5
+ cream: '\x1b[38;2;240;235;226m',
6
+ blue: '\x1b[38;2;108;140;255m',
7
+ indigo: '\x1b[38;2;155;108;240m',
8
+ cyan: '\x1b[38;2;88;199;230m',
9
+ green: '\x1b[38;2;69;209;127m',
10
+ amber: '\x1b[38;2;255;180;84m',
11
+ red: '\x1b[38;2;255;92;108m',
12
+ muted: '\x1b[38;2;138;147;166m',
10
13
  bold: '\x1b[1m',
11
14
  reset: '\x1b[0m',
12
15
  underline: '\x1b[4m',
@@ -31,6 +34,9 @@ function paint(s, code, stream = process.stdout) {
31
34
  }
32
35
  export const c = {
33
36
  white: (s, stream) => paintRaw(s, BRAND.white, stream),
37
+ cream: (s, stream) => paintRaw(s, BRAND.cream, stream),
38
+ blue: (s, stream) => paintRaw(s, BRAND.blue, stream),
39
+ indigo: (s, stream) => paintRaw(s, BRAND.indigo, stream),
34
40
  cyan: (s, stream) => paintRaw(s, BRAND.cyan, stream),
35
41
  green: (s, stream) => paintRaw(s, BRAND.green, stream),
36
42
  amber: (s, stream) => paintRaw(s, BRAND.amber, stream),
@@ -137,17 +143,7 @@ const WORDMARK_LINES = [
137
143
  ];
138
144
  export const WORDMARK_WIDTH = 63;
139
145
  export const WORDMARK_HEIGHT = 6;
140
- export const WORDMARK_GRADIENT = [
141
- '#13b9e2', '#1abadc', '#20bad7', '#27bbd1', '#2ebbcb', '#35bcc5', '#3bbdc0',
142
- '#42bdba', '#49beb4', '#50bfae', '#50bfae', '#5dc0a3', '#64c09d', '#6bc197',
143
- '#71c292', '#78c28c', '#7fc386', '#86c380', '#8cc47b', '#93c575', '#93c575',
144
- '#a1c669', '#a7c664', '#aec75e', '#b5c858', '#bcc852', '#c2c94d', '#c9ca47',
145
- '#d0ca41', '#d7cb3b', '#ddcb36', '#e4cc30', '#e0c536', '#dcbf3b', '#d8b841',
146
- '#d4b246', '#d0ab4c', '#cca551', '#c89e57', '#c4975d', '#c09162', '#bc8a68',
147
- '#bc8a68', '#bc8a68', '#b07679', '#ac707e', '#a86984', '#a36389', '#9f5c8f',
148
- '#9b5694', '#974f9a', '#9348a0', '#8f42a5', '#8f42a5', '#8735b0', '#832eb6',
149
- '#7f27bc', '#7b21c1', '#771ac7', '#7314cc', '#6f0dd2', '#6b07d7', '#6700dd',
150
- ];
146
+ export const WORDMARK_DOT_COLS = [31, 32, 33];
151
147
  function hexFg(hex) {
152
148
  const n = parseInt(hex.slice(1), 16);
153
149
  return `\x1b[38;2;${(n >> 16) & 255};${(n >> 8) & 255};${n & 255}m`;
@@ -163,8 +159,8 @@ function paintWordmarkLine(line, stream = process.stdout) {
163
159
  out += ' ';
164
160
  continue;
165
161
  }
166
- const hex = WORDMARK_GRADIENT[i] ?? WORDMARK_GRADIENT[WORDMARK_GRADIENT.length - 1];
167
- out += BRAND.bold + hexFg(hex) + ch + BRAND.reset;
162
+ const colour = WORDMARK_DOT_COLS.includes(i) ? BRAND.blue : BRAND.cream;
163
+ out += BRAND.bold + colour + ch + BRAND.reset;
168
164
  }
169
165
  return out;
170
166
  }
@@ -333,6 +329,43 @@ export function fixBlock(args) {
333
329
  });
334
330
  return lines.join('\n');
335
331
  }
332
+ export function promptLine(opts = {}) {
333
+ const stream = opts.stream ?? process.stdout;
334
+ const cursor = asciiOnly() ? '_' : '▌';
335
+ return (`${c.cyan('~', stream)} ${c.green('$', stream)} ` +
336
+ `${c.bold(c.cream('nonbot', stream), stream)} ${c.blue('run', stream)}` +
337
+ c.cream(cursor, stream));
338
+ }
339
+ export function gradientRule(width, opts = {}) {
340
+ const stream = opts.stream ?? process.stdout;
341
+ const n = Math.max(0, Math.floor(width));
342
+ const glyph = asciiOnly() ? '-' : '━';
343
+ if (!isTTY(stream) || n === 0)
344
+ return glyph.repeat(n);
345
+ const from = [0x6c, 0x8c, 0xff];
346
+ const to = [0x9b, 0x6c, 0xf0];
347
+ let out = '';
348
+ for (let i = 0; i < n; i++) {
349
+ const t = n === 1 ? 0 : i / (n - 1);
350
+ const r = Math.round(from[0] + (to[0] - from[0]) * t);
351
+ const g = Math.round(from[1] + (to[1] - from[1]) * t);
352
+ const b = Math.round(from[2] + (to[2] - from[2]) * t);
353
+ out += `\x1b[38;2;${r};${g};${b}m${glyph}`;
354
+ }
355
+ return out + BRAND.reset;
356
+ }
357
+ export function compactMasthead(version, opts = {}) {
358
+ const stream = opts.stream ?? process.stdout;
359
+ const state = opts.state ?? 'IDLE';
360
+ const badgeColor = opts.badgeColor ?? 'green';
361
+ const badgeLabel = opts.badgeLabel ?? 'CONNECTED';
362
+ const dotGlyph = asciiOnly() ? '*' : '●';
363
+ const brand = `${c.bold(c.cream('non.bot', stream), stream)}${c.blue('.', stream)}`;
364
+ const badge = colorize(`${dotGlyph} ${badgeLabel}`, badgeColor, stream);
365
+ const sep = c.muted('·', stream);
366
+ const tail = [c.muted(`daemon ${version}`, stream), sep, c.muted(state, stream)].join(' ');
367
+ return `${brand} ${badge} ${tail}`;
368
+ }
336
369
  export const DAEMON_TAGLINES = [
337
370
  "click ▶ on the canvas. terminal opens here. that's it.",
338
371
  ];
@@ -348,10 +381,19 @@ export function daemonOpener(version, opts = {}) {
348
381
  const seed = opts.seed ?? Date.now();
349
382
  const tagline = pickDaemonTagline(seed, opts.pool);
350
383
  const state = opts.state ?? 'IDLE';
351
- const breadcrumb = opts.breadcrumb ?? 'SYS://nonbot/daemon';
352
- const breadcrumbLine = breadcrumb
353
- ? ` ${c.cyan(breadcrumb, stream)}\n\n`
354
- : '';
384
+ if (opts.compact) {
385
+ return compactMasthead(version, {
386
+ state,
387
+ badgeColor: opts.badgeColor ?? 'green',
388
+ badgeLabel: opts.badgeLabel ?? 'CONNECTED',
389
+ stream,
390
+ });
391
+ }
392
+ const topLine = opts.breadcrumb === ''
393
+ ? ''
394
+ : opts.breadcrumb
395
+ ? ` ${c.cyan(opts.breadcrumb, stream)}\n\n`
396
+ : ` ${promptLine({ stream })}\n\n`;
355
397
  const stamp = renderWordmarkWithBadge({
356
398
  badgeColor: opts.badgeColor ?? 'green',
357
399
  badgeLabel: opts.badgeLabel ?? 'CONNECTED',
@@ -369,8 +411,10 @@ export function daemonOpener(version, opts = {}) {
369
411
  subParts.push(c.muted(trackedCaps(state), stream));
370
412
  }
371
413
  const subtitleLine = ` ${subParts.join(' ')}`;
414
+ const ruleWidth = Math.min(46, Math.max(10, terminalWidth(opts.width) - 2));
415
+ const ruleLine = ` ${gradientRule(ruleWidth, { stream })}`;
372
416
  const taglineLine = tagline ? `\n\n ${c.muted(tagline, stream)}` : '';
373
- return `${breadcrumbLine}${stamp}\n\n${subtitleLine}${taglineLine}`;
417
+ return `${topLine}${stamp}\n\n${ruleLine}\n\n${subtitleLine}${taglineLine}`;
374
418
  }
375
419
  export function daemonCloser(opts = {}) {
376
420
  const stream = opts.stream ?? process.stdout;
@@ -475,27 +519,27 @@ export function resumedLine(activationId, stream = process.stdout) {
475
519
  }
476
520
  export function buildPaneBorderFormat(opts = {}) {
477
521
  const warn = opts.ascii ? '!! ' : '⚠ ';
478
- return (`#{?#{==:#{@nb_state},waiting},#[fg=colour203 bold]${warn},}` +
522
+ return (`#{?#{==:#{@nb_state},waiting},#[fg=#ffb454 bold]${warn},}` +
479
523
  '#[fg=' +
480
- '#{?#{==:#{@nb_state},waiting},colour203,' +
481
- '#{?#{==:#{@nb_state},failed},colour203,' +
482
- '#{?#{==:#{@nb_state},completed},colour78,' +
483
- '#{?#{==:#{@nb_state},stopping},colour214,colour45}}}}' +
524
+ '#{?#{==:#{@nb_state},waiting},#ffb454,' +
525
+ '#{?#{==:#{@nb_state},failed},#ff5c6c,' +
526
+ '#{?#{==:#{@nb_state},completed},#45d17f,' +
527
+ '#{?#{==:#{@nb_state},stopping},#ffb454,#58c7e6}}}}' +
484
528
  ']' +
485
529
  ' #{pane_title} ');
486
530
  }
487
531
  export function buildTmuxStatusLeft(opts = {}) {
488
532
  const dot = opts.ascii ? '* ' : '● ';
489
- return (' #[fg=#ffffff,bold]non.bot#[default] ' +
490
- '#{?#{==:#{@nb_halted},1},#[fg=#ff4c51]HALTED,' +
491
- `#[fg=#28c76f]${dot}CONNECTED}#[default] `);
533
+ return (' #[fg=#f0ebe2,bold]non#[fg=#6c8cff].#[fg=#f0ebe2]bot#[default] ' +
534
+ '#{?#{==:#{@nb_halted},1},#[fg=#ff5c6c]HALTED,' +
535
+ `#[fg=#45d17f]${dot}CONNECTED}#[default] `);
492
536
  }
493
537
  export function buildTmuxStatusRight(opts = {}) {
494
538
  const warn = opts.ascii ? '!!' : '⚠';
495
539
  const dot = '·';
496
- return ('#[fg=#28c76f]#{@nb_running} run#[default] ' +
497
- `#[fg=#8b949e]${dot} #{@nb_done} done` +
498
- `#{?#{@nb_failed}, #[fg=#ff4c51]${dot} #{@nb_failed} fail,}#[default] ` +
499
- `#{?#{@nb_needsyou},#[fg=#ff9f43,bold]${dot} ${warn} #{@nb_needsyou} NEEDS YOU ,}` +
500
- `#[fg=#4fc3f7]${dot} #{host_short} ${dot} poll #{@nb_poll}s `);
540
+ return ('#[fg=#45d17f]#{@nb_running} run#[default] ' +
541
+ `#[fg=#8a93a6]${dot} #{@nb_done} done` +
542
+ `#{?#{@nb_failed}, #[fg=#ff5c6c]${dot} #{@nb_failed} fail,}#[default] ` +
543
+ `#{?#{@nb_needsyou},#[fg=#ffb454,bold]${dot} ${warn} #{@nb_needsyou} NEEDS YOU ,}` +
544
+ `#[fg=#58c7e6]${dot} #{host_short} ${dot} poll #{@nb_poll}s `);
501
545
  }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.9.6';
1
+ export const VERSION = '0.9.7';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonbot/cli",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "type": "module",
5
5
  "description": "The local host for non.bot ▶ Run — opens a terminal on your machine and starts the work in your linked repo.",
6
6
  "license": "UNLICENSED",