@pugi/cli 0.1.0-beta.18 → 0.1.0-beta.19

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,178 @@
1
+ /**
2
+ * `pugi status` — concise session-state probe (Leak L34, 2026-05-27).
3
+ *
4
+ * Different from `pugi doctor` (environment health). The status
5
+ * command answers "what is this Pugi session doing right now?" —
6
+ * session id + age, cwd, permission mode, CLI version, token
7
+ * usage, dispatch counts, last command, compact boundaries, auth
8
+ * identity.
9
+ *
10
+ * # Module contract
11
+ *
12
+ * - This file owns the WIRING from CLI flags + ambient process
13
+ * state к the snapshot collector. The data collector itself
14
+ * lives in `core/diagnostics/probes/status-snapshot.ts` and
15
+ * has zero coupling к the CLI dispatch surface.
16
+ *
17
+ * - `runStatusCommand` is the single entry point. Both the
18
+ * top-level `pugi status` handler in `runtime/cli.ts` AND the
19
+ * in-REPL `/status` slash command call it. The function
20
+ * returns the `StatusSnapshot` so the REPL slash handler can
21
+ * mount the Ink renderer without re-collecting fields.
22
+ *
23
+ * - The credential resolver is captured behind a function so the
24
+ * spec can stub it without monkey-patching `core/credentials.ts`.
25
+ *
26
+ * - The permission state module is dynamic-imported with a
27
+ * try/catch so this command lands cleanly before the L6
28
+ * permission-mode work — when the module is absent the field
29
+ * degrades к "unknown" instead of crashing the snapshot.
30
+ *
31
+ * - Exit code is 0 unless the snapshot itself throws. The
32
+ * command is intentionally informational — even a fully
33
+ * "unavailable" snapshot (everything degraded к sentinels)
34
+ * exits 0 so monitoring scripts can rely on a stable contract.
35
+ */
36
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
37
+ import { homedir } from 'node:os';
38
+ import { resolveActiveCredential } from '../../core/credentials.js';
39
+ import { PUGI_CLI_VERSION } from '../version.js';
40
+ import { collectStatusSnapshot, } from '../../core/diagnostics/probes/status-snapshot.js';
41
+ /**
42
+ * Default production filesystem stubs. Wraps node:fs so the
43
+ * snapshot collector can stay sync + structurally testable.
44
+ */
45
+ const DEFAULT_FS = {
46
+ existsSync,
47
+ statSync,
48
+ readdirSync: (p) => readdirSync(p),
49
+ readFileSync: (p, encoding) => readFileSync(p, encoding),
50
+ };
51
+ /**
52
+ * Default credential resolver. Wraps `resolveActiveCredential` so
53
+ * the snapshot only sees the trimmed `ResolvedCredentialSummary`
54
+ * shape and не the full store record.
55
+ */
56
+ function defaultResolveCredential(env, home) {
57
+ const cred = resolveActiveCredential(env, home);
58
+ if (!cred)
59
+ return null;
60
+ // Tier is не yet recorded в the credentials file — set к null
61
+ // until a future sprint surfaces `tier` on the stored record.
62
+ // The renderer hides the parenthetical when tier is null.
63
+ return {
64
+ apiUrl: cred.apiUrl,
65
+ label: cred.label ?? null,
66
+ tier: null,
67
+ identity: cred.label ?? null,
68
+ };
69
+ }
70
+ /**
71
+ * Default permission-mode loader. Dynamic-imports
72
+ * `core/permissions/state.ts` (L6 in the leak-parity roadmap);
73
+ * returns null when the module is absent OR malformed, which the
74
+ * snapshot field degrades к the "unknown" sentinel.
75
+ */
76
+ async function defaultResolvePermissionMode() {
77
+ try {
78
+ const mod = (await import('../../core/permissions/state.js').catch(() => null));
79
+ if (!mod || typeof mod.getCurrentPermissionMode !== 'function') {
80
+ return null;
81
+ }
82
+ const mode = mod.getCurrentPermissionMode();
83
+ return typeof mode === 'string' && mode.length > 0 ? mode : null;
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * Collect the snapshot + emit the output via the supplied
91
+ * writeOutput sink. Returns the snapshot so REPL callers can
92
+ * route it к the Ink renderer instead of the plain-text fallback.
93
+ */
94
+ export async function runStatusCommand(ctx) {
95
+ // Resolve the permission mode upfront — the snapshot is sync but
96
+ // the L6 loader is async (dynamic import). We materialise the
97
+ // value here and hand the snapshot a sync getter.
98
+ const permissionMode = ctx.resolvePermissionMode === undefined
99
+ ? await defaultResolvePermissionMode()
100
+ : null;
101
+ const deps = {
102
+ cwd: ctx.cwd,
103
+ home: ctx.home,
104
+ cliVersion: PUGI_CLI_VERSION,
105
+ now: ctx.now ?? Date.now,
106
+ liveSessionId: ctx.liveSessionId ?? null,
107
+ sessionStartedAtEpochMs: ctx.sessionStartedAtEpochMs ?? null,
108
+ liveTokensUsed: ctx.liveTokensUsed ?? null,
109
+ lastCommand: ctx.lastCommand ?? null,
110
+ lastCommandAtEpochMs: ctx.lastCommandAtEpochMs ?? null,
111
+ fs: ctx.fs ?? DEFAULT_FS,
112
+ resolveCredential: ctx.resolveCredential ?? (() => defaultResolveCredential(ctx.env, ctx.home)),
113
+ resolvePermissionMode: ctx.resolvePermissionMode ?? (() => permissionMode),
114
+ };
115
+ let snapshot;
116
+ try {
117
+ snapshot = collectStatusSnapshot(deps);
118
+ }
119
+ catch (error) {
120
+ // Defensive: the snapshot collector is fail-soft per field, but
121
+ // a structural crash (e.g. a hostile env that throws on
122
+ // `process.version` access) must не bring down `pugi status`.
123
+ // We synthesise a minimal envelope and surface the error в the
124
+ // text output so the operator can file a bug.
125
+ const message = error instanceof Error ? error.message : String(error);
126
+ snapshot = {
127
+ command: 'status',
128
+ fields: [
129
+ {
130
+ key: 'session',
131
+ label: 'Session',
132
+ value: 'unknown',
133
+ available: false,
134
+ note: `snapshot collector crashed: ${message}`,
135
+ },
136
+ ],
137
+ meta: {
138
+ cliVersion: PUGI_CLI_VERSION,
139
+ nodeVersion: process.version,
140
+ cwd: ctx.cwd,
141
+ capturedAt: new Date((ctx.now ?? Date.now)()).toISOString(),
142
+ },
143
+ };
144
+ }
145
+ const text = renderStatusTable(snapshot);
146
+ ctx.writeOutput(snapshot, text);
147
+ // Always exit 0 — `pugi status` is informational, never a gate.
148
+ process.exitCode = 0;
149
+ return snapshot;
150
+ }
151
+ /**
152
+ * Plain-text table renderer. Lays out the snapshot as a two-column
153
+ * (LABEL / VALUE) table with the brand-voice header `Pugi status`.
154
+ * Matches the column-light pattern used by `renderDoctorTable` so
155
+ * narrow terminals stay legible without a layout library.
156
+ */
157
+ export function renderStatusTable(snapshot) {
158
+ const labelWidth = Math.max('Label'.length, ...snapshot.fields.map((f) => f.label.length));
159
+ const lines = [];
160
+ lines.push('Pugi status');
161
+ lines.push('═'.repeat(50));
162
+ for (const field of snapshot.fields) {
163
+ const labelPart = field.label.padEnd(labelWidth, ' ');
164
+ lines.push(`${labelPart} ${field.value}`);
165
+ }
166
+ lines.push('');
167
+ lines.push(`CLI ${snapshot.meta.cliVersion} Node ${snapshot.meta.nodeVersion} cwd ${snapshot.meta.cwd}`);
168
+ return lines.join('\n');
169
+ }
170
+ /**
171
+ * Default home dir resolver. Centralised so the CLI handler can
172
+ * call `runStatusCommand` without re-importing `os.homedir`
173
+ * everywhere.
174
+ */
175
+ export function defaultStatusHome() {
176
+ return homedir();
177
+ }
178
+ //# sourceMappingURL=status.js.map
@@ -44,7 +44,7 @@ export function sanitizeSemver(raw) {
44
44
  * during import). When bumping the CLI version BOTH literals must be
45
45
  * updated; the release smoke-test (`pack:smoke`) verifies they agree.
46
46
  */
47
- export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.18');
47
+ export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.19');
48
48
  /**
49
49
  * Outbound: the CLI's installed semver. Read at request time by
50
50
  * `version-interceptor.ts` and injected on every `fetch` call.
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Visible boundary marker for the conversation pane.
4
+ *
5
+ * Rendered inline in the transcript when the replay walker hits a
6
+ * `compaction` event. Conveys three facts at a glance:
7
+ *
8
+ * 1. A compaction happened (separator line).
9
+ * 2. How many turns were folded into the summary.
10
+ * 3. Whether it was manual or auto-triggered.
11
+ *
12
+ * The separator line uses U+2500 (BOX DRAWINGS LIGHT HORIZONTAL) so
13
+ * the visual weight matches the rest of the Ink chrome. Dim ink-color
14
+ * `gray` keeps the marker subdued — it is a navigation aid, not the
15
+ * conversation itself.
16
+ *
17
+ * Width-aware: when stdout columns are known we render the dashes to
18
+ * fill the line minus the centred label; on unknown width we fall
19
+ * back to a fixed 64-dash pad. The fallback is wide enough for any
20
+ * realistic terminal and narrow enough to not wrap on small ones.
21
+ */
22
+ import { Box, Text } from 'ink';
23
+ const FALLBACK_COLUMNS = 80;
24
+ /**
25
+ * Render the boundary line. The wrapping `<Box>` keeps the marker on
26
+ * its own row even when the surrounding flex container packs siblings.
27
+ */
28
+ export function CompactBanner(props) {
29
+ const columns = props.columns && props.columns > 20 ? props.columns : FALLBACK_COLUMNS;
30
+ const label = buildLabel(props);
31
+ const dashesEach = Math.max(3, Math.floor((columns - label.length - 2) / 2));
32
+ const dashes = '─'.repeat(dashesEach);
33
+ return (_jsx(Box, { children: _jsx(Text, { color: "gray", children: `${dashes} ${label} ${dashes}` }) }));
34
+ }
35
+ /**
36
+ * Build the centred label. Examples:
37
+ * "context compacted (12 turns → 1 summary, auto)"
38
+ * "context compacted (4 turns → 1 summary, manual · ~3.2k tokens)"
39
+ */
40
+ export function buildLabel(props) {
41
+ const trigger = props.trigger === 'auto' ? 'auto' : 'manual';
42
+ const turns = `${props.turnsBefore} ${props.turnsBefore === 1 ? 'turn' : 'turns'}`;
43
+ const tokens = props.summaryTokenCount && props.summaryTokenCount > 0
44
+ ? ` · ~${formatTokens(props.summaryTokenCount)} tokens`
45
+ : '';
46
+ return `context compacted (${turns} → 1 summary, ${trigger}${tokens})`;
47
+ }
48
+ /** Format token counts like 1234 → 1.2k, 950 → 950. */
49
+ function formatTokens(n) {
50
+ if (n < 1000)
51
+ return `${n}`;
52
+ return `${(n / 1000).toFixed(1)}k`;
53
+ }
54
+ //# sourceMappingURL=compact-banner.js.map
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function StatusTable({ snapshot }) {
4
+ const labelWidth = Math.max('Label'.length, ...snapshot.fields.map((f) => f.label.length));
5
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Pugi status" }) }), snapshot.fields.map((field) => (_jsxs(Box, { children: [_jsxs(Text, { children: [field.label.padEnd(labelWidth, ' '), " "] }), field.available ? (_jsx(Text, { children: field.value })) : (_jsx(Text, { dimColor: true, children: field.value }))] }, field.key))), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["CLI ", snapshot.meta.cliVersion, " Node ", snapshot.meta.nodeVersion, " cwd ", snapshot.meta.cwd] }) })] }));
6
+ }
7
+ //# sourceMappingURL=status-table.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pugi/cli",
3
- "version": "0.1.0-beta.18",
3
+ "version": "0.1.0-beta.19",
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.18"
57
+ "@pugi/sdk": "0.1.0-beta.19"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^22.0.0",