@nonbot/cli 0.5.15 → 0.7.0

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.
@@ -287,6 +287,33 @@ export function pollTick(args) {
287
287
  const statusSeg = c.muted(statusText, stream);
288
288
  return `${ts} ${pollCount} ${sep} ${sleepSeg} ${sep} ${statusSeg}`;
289
289
  }
290
+ export function formatElapsed(ms) {
291
+ if (!Number.isFinite(ms) || ms < 0)
292
+ return '0s';
293
+ const totalSec = Math.floor(ms / 1000);
294
+ if (totalSec < 60)
295
+ return `${totalSec}s`;
296
+ const totalMin = Math.floor(totalSec / 60);
297
+ if (totalMin < 60)
298
+ return `${totalMin}m ${totalSec % 60}s`;
299
+ const hours = Math.floor(totalMin / 60);
300
+ return `${hours}h ${totalMin % 60}m`;
301
+ }
302
+ function summaryAnchor(stream) {
303
+ const glyph = asciiOnly() ? '*' : '◆';
304
+ return c.cyan(glyph, stream);
305
+ }
306
+ export function runSummary(args) {
307
+ const stream = args.stream ?? process.stdout;
308
+ const failed = args.failed ?? 0;
309
+ const sep = c.muted('·', stream);
310
+ const segs = [];
311
+ segs.push(c.green(`${args.running} running`, stream));
312
+ segs.push(c.muted(`${args.done} done`, stream));
313
+ if (failed > 0)
314
+ segs.push(c.red(`${failed} failed`, stream));
315
+ return `${summaryAnchor(stream)} ` + segs.join(` ${sep} `);
316
+ }
290
317
  export function fixBlock(args) {
291
318
  const stream = args.stream ?? process.stdout;
292
319
  const title = c.amber(`| ${args.title.toUpperCase()} |`, stream);
@@ -0,0 +1,51 @@
1
+ import { spawnSync as nodeSpawnSync } from 'node:child_process';
2
+ const STATE_GLYPH = {
3
+ running: '▶',
4
+ completed: '✓',
5
+ failed: '✗',
6
+ stopping: '■',
7
+ };
8
+ const STATE_GLYPH_ASCII = {
9
+ running: '>',
10
+ completed: 'OK',
11
+ failed: 'XX',
12
+ stopping: '#',
13
+ };
14
+ function asciiOnly(env = process.env) {
15
+ return env.NONBOT_ASCII_ONLY === '1' || env.NONBOT_ASCII_ONLY === 'true';
16
+ }
17
+ export function paneStateGlyph(state, env = process.env) {
18
+ return asciiOnly(env) ? STATE_GLYPH_ASCII[state] : STATE_GLYPH[state];
19
+ }
20
+ export const PANE_TITLE_MAX = 44;
21
+ export function formatPaneTitle(state, story, env = process.env) {
22
+ const glyph = paneStateGlyph(state, env);
23
+ const cleaned = (story ?? '')
24
+ .replace(/[\x00-\x1f\x7f]/g, ' ')
25
+ .replace(/ {2,}/g, ' ')
26
+ .trim();
27
+ const label = cleaned.length > 0 ? cleaned : 'nonbot run';
28
+ const title = `${glyph} ${label}`;
29
+ return title.length > PANE_TITLE_MAX ? title.slice(0, PANE_TITLE_MAX - 1) + '…' : title;
30
+ }
31
+ export function buildPaneTitleCommand(paneId, state, story, env = process.env) {
32
+ if (!/^%\d+$/.test(paneId))
33
+ return null;
34
+ const title = formatPaneTitle(state, story, env);
35
+ return { cmd: 'tmux', args: ['select-pane', '-t', paneId, '-T', title] };
36
+ }
37
+ export function applyPaneTitle(paneId, state, story, spawnImpl = nodeSpawnSync, env = process.env) {
38
+ const command = buildPaneTitleCommand(paneId, state, story, env);
39
+ if (!command)
40
+ return false;
41
+ try {
42
+ spawnImpl(command.cmd, command.args, {
43
+ encoding: 'utf-8',
44
+ timeout: 1000,
45
+ windowsHide: true,
46
+ });
47
+ }
48
+ catch {
49
+ }
50
+ return true;
51
+ }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.5.15';
1
+ export const VERSION = '0.7.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonbot/cli",
3
- "version": "0.5.15",
3
+ "version": "0.7.0",
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",