@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.
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +196 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +442 -0
- package/dist/core/engine/native-pugi.js +20 -0
- package/dist/core/engine/tool-bridge.js +153 -14
- package/dist/core/permissions/gate.js +187 -0
- package/dist/core/permissions/index.js +18 -0
- package/dist/core/permissions/mode.js +102 -0
- package/dist/core/permissions/state.js +160 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/repl/session.js +227 -9
- package/dist/core/repl/slash-commands.js +58 -4
- package/dist/runtime/cli.js +129 -0
- package/dist/runtime/commands/compact.js +296 -0
- package/dist/runtime/commands/doctor.js +12 -0
- package/dist/runtime/commands/permissions.js +87 -0
- package/dist/runtime/commands/status.js +178 -0
- package/dist/runtime/version.js +1 -1
- package/dist/tui/compact-banner.js +54 -0
- package/dist/tui/status-table.js +7 -0
- package/package.json +2 -2
|
@@ -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
|
package/dist/runtime/version.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
57
|
+
"@pugi/sdk": "0.1.0-beta.19"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^22.0.0",
|