@pugi/cli 0.1.0-beta.15 → 0.1.0-beta.17

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.
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ /** Width of the progress bar in display cells. Tuned to fit comfortably
4
+ * inside an 80-col terminal alongside the percent label. */
5
+ export const PROGRESS_BAR_WIDTH = 24;
6
+ /** Max milestone rows the card renders before collapsing to the footer
7
+ * summary. Matches the CC `/compact` cutoff. */
8
+ export const MAX_VISIBLE_MILESTONES = 5;
9
+ const STATUS_GLYPH = {
10
+ done: '◼',
11
+ active: '▸',
12
+ pending: '◻',
13
+ };
14
+ const STATUS_COLOR = {
15
+ done: 'green',
16
+ active: 'yellow',
17
+ pending: 'gray',
18
+ };
19
+ const HEADER_DOT_COLOR = {
20
+ running: 'cyan',
21
+ completed: 'green',
22
+ failed: 'red',
23
+ };
24
+ /**
25
+ * Build the unicode progress bar. Exported для тесты — guarantees the
26
+ * filled/empty counts match the percent under all rounding edges.
27
+ */
28
+ export function renderProgressBarCells(percent, width = PROGRESS_BAR_WIDTH) {
29
+ const safePercent = Math.max(0, Math.min(100, percent));
30
+ const cells = Math.round((safePercent / 100) * width);
31
+ const clamped = Math.max(0, Math.min(width, cells));
32
+ return {
33
+ filled: '▰'.repeat(clamped),
34
+ empty: '▱'.repeat(width - clamped),
35
+ cells: clamped,
36
+ };
37
+ }
38
+ /**
39
+ * Format milliseconds as the CC-style `Hh Mm Ss` / `Mm Ss` / `Ss` label.
40
+ * Mirrors the rule used by status-bar elapsed slot.
41
+ */
42
+ export function formatElapsed(ms) {
43
+ const total = Math.max(0, Math.floor(ms / 1000));
44
+ const h = Math.floor(total / 3600);
45
+ const m = Math.floor((total % 3600) / 60);
46
+ const s = total % 60;
47
+ if (h > 0)
48
+ return `${h}h ${m}m ${s}s`;
49
+ if (m > 0)
50
+ return `${m}m ${s}s`;
51
+ return `${s}s`;
52
+ }
53
+ /**
54
+ * Format a raw token count as `21.7k` / `3.4M` / `812`. Mirrors the
55
+ * formatter in `core/repl/model-pricing.ts` so both surfaces stay
56
+ * visually consistent without coupling.
57
+ */
58
+ export function formatTokenCount(n) {
59
+ if (n === undefined)
60
+ return undefined;
61
+ if (n < 1_000)
62
+ return `${n}`;
63
+ if (n < 1_000_000) {
64
+ const k = n / 1_000;
65
+ return `${k >= 10 ? k.toFixed(1).replace(/\.0$/, '') : k.toFixed(1)}k`;
66
+ }
67
+ const m = n / 1_000_000;
68
+ return `${m >= 10 ? m.toFixed(1).replace(/\.0$/, '') : m.toFixed(1)}M`;
69
+ }
70
+ /**
71
+ * Compute the "… +N pending, M completed" footer counts. When the
72
+ * agent supplied rollups they win; otherwise we derive from the
73
+ * milestone array.
74
+ */
75
+ export function computeFooterCounts(milestones, visibleCount, rollup) {
76
+ const pending = rollup.pendingCount
77
+ ?? milestones.filter((m) => m.status === 'pending').length;
78
+ const completed = rollup.completedCount
79
+ ?? milestones.filter((m) => m.status === 'done').length;
80
+ const hidden = Math.max(0, milestones.length - visibleCount);
81
+ return { pending, completed, hidden };
82
+ }
83
+ function MilestoneRow({ milestone }) {
84
+ const glyph = STATUS_GLYPH[milestone.status];
85
+ const color = STATUS_COLOR[milestone.status];
86
+ // Truncate to 64 chars so a verbose label can't wrap and break the
87
+ // grid layout in the watcher.
88
+ const label = milestone.label.length > 64
89
+ ? `${milestone.label.slice(0, 63)}…`
90
+ : milestone.label;
91
+ return (_jsxs(Box, { children: [_jsx(Text, { children: ' ' }), _jsx(Text, { color: color, children: glyph }), _jsx(Text, { children: " " }), _jsx(Text, { color: color === 'gray' ? 'gray' : undefined, dimColor: milestone.status === 'pending', children: label })] }));
92
+ }
93
+ export function AgentProgressCard({ progress, nowEpochMs, }) {
94
+ // Re-derive elapsed from the wall clock when the parent supplied it;
95
+ // this is what makes the card tick once a second without the writer
96
+ // re-emitting JSON every tick.
97
+ const elapsed = nowEpochMs !== undefined
98
+ ? Math.max(progress.elapsedMs, nowEpochMs - Date.parse(progress.startedAt))
99
+ : progress.elapsedMs;
100
+ const bar = renderProgressBarCells(progress.percentComplete);
101
+ const percentLabel = `${Math.round(Math.max(0, Math.min(100, progress.percentComplete)))}%`;
102
+ const tokensLabel = formatTokenCount(progress.tokensUsed);
103
+ const dotColor = HEADER_DOT_COLOR[progress.status];
104
+ const visibleMilestones = progress.milestones.slice(0, MAX_VISIBLE_MILESTONES);
105
+ const footer = computeFooterCounts(progress.milestones, visibleMilestones.length, { pendingCount: progress.pendingCount, completedCount: progress.completedCount });
106
+ // CC compact pattern: header has a leading `· ` glyph + the task label.
107
+ // We append `…` only while running (matches CC's "Compacting…" verb form).
108
+ const headerVerb = progress.status === 'running' ? '…' : '';
109
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 0, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: '· ' }), _jsx(Text, { bold: true, children: progress.agentType }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [progress.task, headerVerb] }), _jsxs(Text, { dimColor: true, children: [' (', formatElapsed(elapsed), tokensLabel ? ` · ↑ ${tokensLabel} tokens` : '', ')'] })] }), _jsxs(Box, { children: [_jsx(Text, { children: ' ' }), _jsx(Text, { color: "cyan", children: bar.filled }), _jsx(Text, { dimColor: true, children: bar.empty }), _jsxs(Text, { children: [' ', percentLabel] })] }), progress.stepDescription ? (_jsxs(Box, { children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { dimColor: true, children: ["step ", progress.currentStep, "/", progress.totalSteps, ": ", progress.stepDescription] })] })) : null, visibleMilestones.length > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { children: ' ' }), _jsx(Text, { dimColor: true, children: "\u23BF" })] }), visibleMilestones.map((m, i) => (_jsx(MilestoneRow, { milestone: m }, `${m.label}-${i}`))), footer.hidden > 0 ? (_jsxs(Box, { children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { dimColor: true, children: ["\u2026 +", footer.pending, " pending, ", footer.completed, " completed"] })] })) : null] })) : null] }));
110
+ }
111
+ //# sourceMappingURL=agent-progress-card.js.map
@@ -16,6 +16,8 @@
16
16
  * a tiny `event:`/`data:`/`id:` parser. This keeps the dependency
17
17
  * graph at zero new packages.
18
18
  */
19
+ import { existsSync } from 'node:fs';
20
+ import { resolve } from 'node:path';
19
21
  import React from 'react';
20
22
  import { render } from 'ink';
21
23
  import { Repl } from './repl.js';
@@ -314,11 +316,9 @@ export function drainBufferedStdin(stdin = process.stdin) {
314
316
  * future unit spec can lock the contract.
315
317
  */
316
318
  export function isProjectRoot(cwd) {
317
- // Local import keeps the bootstrap free of top-of-file `fs` calls
318
- // that would run at module-load time Ink + the SSE transport are
319
- // happier when this file's side-effect surface stays small.
320
- const { existsSync } = require('node:fs');
321
- const { resolve } = require('node:path');
319
+ // ESM static imports `require()` is not defined in a `"type": "module"`
320
+ // bundle and would throw `ReferenceError: require is not defined` the
321
+ // moment the REPL bootstrap calls this gate. Beta.16 P0 fix 2026-05-27.
322
322
  return (existsSync(resolve(cwd, 'package.json')) ||
323
323
  existsSync(resolve(cwd, '.git')) ||
324
324
  existsSync(resolve(cwd, '.pugi')) ||
@@ -64,6 +64,13 @@ function toolDisplayName(tool) {
64
64
  switch (tool) {
65
65
  case 'read':
66
66
  return 'Read';
67
+ case 'write':
68
+ // 2026-05-27 — Write is the most operator-visible tool for the
69
+ // codegen-dispatch surface (Hiroshi writing index.html / style.css
70
+ // / script.js for a tic-tac-toe brief). Add the display name so
71
+ // the tool stream pane renders ✓ Write(index.html) instead of an
72
+ // unlabeled placeholder. Mirrors the Claude Code Write rendering.
73
+ return 'Write';
67
74
  case 'edit':
68
75
  return 'Edit';
69
76
  case 'bash':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pugi/cli",
3
- "version": "0.1.0-beta.15",
3
+ "version": "0.1.0-beta.17",
4
4
  "description": "Pugi CLI - terminal-native software execution system",
5
5
  "homepage": "https://pugi.io",
6
6
  "repository": {
@@ -54,7 +54,7 @@
54
54
  "undici": "^8.3.0",
55
55
  "zod": "^3.23.0",
56
56
  "@pugi/personas": "0.1.2",
57
- "@pugi/sdk": "0.1.0-beta.15"
57
+ "@pugi/sdk": "0.1.0-beta.17"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^22.0.0",