@pugi/cli 0.1.0-beta.92 → 0.1.0-beta.94
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/commands/retro.js +210 -0
- package/dist/core/diagnostics/probes/sandbox.js +65 -33
- package/dist/core/engine/native-pugi.js +185 -11
- package/dist/core/engine/prompts.js +1 -1
- package/dist/core/engine/tool-bridge.js +35 -0
- package/dist/core/engine/verification-patterns.js +195 -0
- package/dist/core/mcp/orchestrator-config.js +192 -0
- package/dist/core/mcp/orchestrator-tools.js +147 -3
- package/dist/core/pugi-gitignore.js +52 -0
- package/dist/core/repl/engine-bridge.js +199 -0
- package/dist/core/repl/session.js +395 -6
- package/dist/core/repl/tool-route.js +382 -0
- package/dist/core/retro/git-collector.js +251 -0
- package/dist/core/retro/health-card.js +25 -0
- package/dist/core/retro/metrics.js +342 -0
- package/dist/core/retro/narrative.js +249 -0
- package/dist/core/retro/plane-collector.js +274 -0
- package/dist/core/retro/pr-issue-link.js +65 -0
- package/dist/core/retro/types.js +16 -0
- package/dist/core/sandboxing/adapter.js +29 -0
- package/dist/core/sandboxing/index.js +49 -0
- package/dist/core/sandboxing/none.js +19 -0
- package/dist/core/sandboxing/seatbelt.js +183 -0
- package/dist/core/session.js +27 -0
- package/dist/core/settings.js +22 -0
- package/dist/runtime/cli.js +167 -33
- package/dist/runtime/commands/compact.js +1 -1
- package/dist/runtime/commands/config.js +1 -1
- package/dist/runtime/commands/mcp.js +64 -8
- package/dist/runtime/commands/memory.js +1 -1
- package/dist/runtime/deprecation-warning.js +69 -0
- package/dist/runtime/headless.js +8 -3
- package/dist/runtime/stream-renderer.js +195 -0
- package/dist/runtime/version.js +1 -1
- package/dist/skills/bundled/remember.js +2 -2
- package/dist/tui/agent-tree.js +11 -0
- package/dist/tui/ask-user-question-chips.js +1 -1
- package/dist/tui/multi-file-diff-approval.js +3 -3
- package/dist/tui/repl-render.js +42 -0
- package/package.json +2 -2
- package/test/scenarios/identity.scenario.txt +0 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming event renderer (Trust Sprint item 5).
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to the engine adapter's `streamEmitter` and writes
|
|
5
|
+
* human-readable progress lines to stderr while the dispatch is in
|
|
6
|
+
* flight. Solves Codex dogfood 2026-06-04 finding: `pugi
|
|
7
|
+
* code/fix/build/plan` were silent until the full dispatch
|
|
8
|
+
* completed, which produced an empty terminal for 20-30s and made
|
|
9
|
+
* customers think the CLI had hung.
|
|
10
|
+
*
|
|
11
|
+
* Design constraints:
|
|
12
|
+
*
|
|
13
|
+
* 1. STDERR ONLY. The CLI's machine-readable contract is "JSON on
|
|
14
|
+
* stdout, prose on stderr". Streaming on stdout would break the
|
|
15
|
+
* JSON envelope for `pugi code --json`.
|
|
16
|
+
*
|
|
17
|
+
* 2. No engine modifications. We attach a listener to the existing
|
|
18
|
+
* `streamEmitter` that the engine already emits to (used by the
|
|
19
|
+
* headless mode). The engine adapter file is owned by the other
|
|
20
|
+
* agent on PUGI-VERIFY-GATE and intentionally NOT touched here.
|
|
21
|
+
*
|
|
22
|
+
* 3. TTY-only by default. CI / pipes / JSON consumers get a clean
|
|
23
|
+
* stream. Explicit `--stream` overrides to force progress lines
|
|
24
|
+
* on; `--no-stream` overrides off.
|
|
25
|
+
*
|
|
26
|
+
* 4. Compact. One line per tool call (start) and one line per
|
|
27
|
+
* finish. Text deltas are aggregated to a single "thinking..."
|
|
28
|
+
* pulse so we don't paint the terminal with token-level noise.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Decide whether to enable the streaming renderer.
|
|
32
|
+
*
|
|
33
|
+
* - explicit=true → ON regardless of TTY.
|
|
34
|
+
* - explicit=false → OFF regardless of TTY.
|
|
35
|
+
* - explicit=null → ON when stderr is a TTY, OFF otherwise.
|
|
36
|
+
*
|
|
37
|
+
* Centralised so the CLI dispatcher and tests share one rule.
|
|
38
|
+
*/
|
|
39
|
+
export function shouldStream(opts) {
|
|
40
|
+
if (opts.explicit === true)
|
|
41
|
+
return true;
|
|
42
|
+
if (opts.explicit === false)
|
|
43
|
+
return false;
|
|
44
|
+
return opts.isTty;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Attach a stderr-based event renderer to the engine emitter.
|
|
48
|
+
*
|
|
49
|
+
* Returns a handle whose `enabled` field is true when the renderer
|
|
50
|
+
* actually attached. When disabled (non-TTY or explicit-off) the
|
|
51
|
+
* returned handle is a no-op so callers don't need to branch.
|
|
52
|
+
*/
|
|
53
|
+
export function attachStreamRenderer(emitter, opts) {
|
|
54
|
+
const enabled = shouldStream({ isTty: opts.isTty, explicit: opts.explicit });
|
|
55
|
+
if (!enabled) {
|
|
56
|
+
return { enabled: false, detach: () => { } };
|
|
57
|
+
}
|
|
58
|
+
const write = opts.write ?? ((line) => process.stderr.write(line));
|
|
59
|
+
// Aggregate text deltas so we don't paint a line per token. We
|
|
60
|
+
// print one "thinking..." pulse every ~500ms while text is
|
|
61
|
+
// streaming so the operator sees the loop is alive without a wall
|
|
62
|
+
// of partial sentences.
|
|
63
|
+
let pendingTextChars = 0;
|
|
64
|
+
let lastTextPulseAt = 0;
|
|
65
|
+
// Aggregate thinking deltas the same way. Separate budget so a
|
|
66
|
+
// chatty reasoning trace doesn't blow out the visible-text counter.
|
|
67
|
+
let pendingThinkingChars = 0;
|
|
68
|
+
let lastThinkingPulseAt = 0;
|
|
69
|
+
// Cache the running tool calls so the "end" line can report a
|
|
70
|
+
// duration without forcing the producer to round-trip the start
|
|
71
|
+
// timestamp through the discriminated union.
|
|
72
|
+
const toolStartedAt = new Map();
|
|
73
|
+
const handler = (event) => {
|
|
74
|
+
switch (event.type) {
|
|
75
|
+
case 'status': {
|
|
76
|
+
// Only surface the high-signal status frames. Turn-boundary
|
|
77
|
+
// frames are noise for a live operator; the tool-level lines
|
|
78
|
+
// already tell them what's happening. Dispatch start /
|
|
79
|
+
// budget / abort frames are worth one line each.
|
|
80
|
+
const msg = event.message;
|
|
81
|
+
if (msg.startsWith('dispatch_start') ||
|
|
82
|
+
msg.startsWith('budget') ||
|
|
83
|
+
msg.startsWith('aborted') ||
|
|
84
|
+
msg.startsWith('cancelled')) {
|
|
85
|
+
write(`pugi: ${msg}\n`);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
case 'tool.start': {
|
|
90
|
+
toolStartedAt.set(event.callId, Date.now());
|
|
91
|
+
const argsBrief = briefArgs(event.arguments);
|
|
92
|
+
write(`pugi: tool ${event.name}(${argsBrief}) running\n`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
case 'tool.end': {
|
|
96
|
+
const startedAt = toolStartedAt.get(event.callId);
|
|
97
|
+
toolStartedAt.delete(event.callId);
|
|
98
|
+
const ms = startedAt ? Date.now() - startedAt : null;
|
|
99
|
+
const glyph = event.ok ? 'ok' : 'fail';
|
|
100
|
+
const summary = (event.summary ?? '').slice(0, 80).replace(/\s+/g, ' ').trim();
|
|
101
|
+
const tail = ms !== null ? ` (${ms}ms)` : '';
|
|
102
|
+
write(`pugi: tool ${glyph}${tail}${summary ? `: ${summary}` : ''}\n`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
case 'tool.delta': {
|
|
106
|
+
// Most bash tools emit a single delta with the entire stdout
|
|
107
|
+
// tail. We surface the first non-empty chunk only, capped to
|
|
108
|
+
// a single line, so the operator gets a peek without
|
|
109
|
+
// flooding.
|
|
110
|
+
const chunk = (event.chunk ?? '').replace(/\s+/g, ' ').trim();
|
|
111
|
+
if (chunk.length === 0)
|
|
112
|
+
return;
|
|
113
|
+
const oneLine = chunk.slice(0, 80);
|
|
114
|
+
write(`pugi: stdout: ${oneLine}\n`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
case 'text.delta': {
|
|
118
|
+
pendingTextChars += (event.chunk ?? '').length;
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
if (now - lastTextPulseAt >= 500 && pendingTextChars >= 40) {
|
|
121
|
+
lastTextPulseAt = now;
|
|
122
|
+
pendingTextChars = 0;
|
|
123
|
+
write(`pugi: writing reply...\n`);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
case 'thinking.start':
|
|
128
|
+
write(`pugi: thinking...\n`);
|
|
129
|
+
lastThinkingPulseAt = Date.now();
|
|
130
|
+
return;
|
|
131
|
+
case 'thinking.delta': {
|
|
132
|
+
pendingThinkingChars += (event.chunk ?? '').length;
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
if (now - lastThinkingPulseAt >= 1500 && pendingThinkingChars >= 80) {
|
|
135
|
+
lastThinkingPulseAt = now;
|
|
136
|
+
pendingThinkingChars = 0;
|
|
137
|
+
write(`pugi: still thinking...\n`);
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
case 'thinking.end':
|
|
142
|
+
// Quiet — text.delta will pick up the visible answer next.
|
|
143
|
+
return;
|
|
144
|
+
default:
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
emitter.on('event', handler);
|
|
149
|
+
return {
|
|
150
|
+
enabled: true,
|
|
151
|
+
detach: () => {
|
|
152
|
+
emitter.off('event', handler);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Render a tool's JSON-serialised arguments to a single-line brief.
|
|
158
|
+
* Keep this very cheap — runs on every tool.start event.
|
|
159
|
+
*/
|
|
160
|
+
function briefArgs(serialised) {
|
|
161
|
+
if (!serialised || serialised === '{}')
|
|
162
|
+
return '';
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(serialised);
|
|
165
|
+
// Prioritise the most signal-bearing field name for known tools.
|
|
166
|
+
const path = pickStringField(parsed, ['path', 'file', 'file_path', 'target', 'cwd']);
|
|
167
|
+
if (path)
|
|
168
|
+
return path.slice(0, 60);
|
|
169
|
+
const command = pickStringField(parsed, ['command', 'cmd', 'query', 'pattern']);
|
|
170
|
+
if (command)
|
|
171
|
+
return command.slice(0, 60);
|
|
172
|
+
// Fallback — surface the first scalar field so we surface
|
|
173
|
+
// something rather than an empty string.
|
|
174
|
+
for (const [, value] of Object.entries(parsed)) {
|
|
175
|
+
if (typeof value === 'string')
|
|
176
|
+
return value.slice(0, 60);
|
|
177
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
178
|
+
return String(value);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return serialised.slice(0, 60);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function pickStringField(obj, keys) {
|
|
188
|
+
for (const key of keys) {
|
|
189
|
+
const value = obj[key];
|
|
190
|
+
if (typeof value === 'string' && value.length > 0)
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=stream-renderer.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.94');
|
|
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.
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
*/
|
|
50
50
|
import { readFileSync } from 'node:fs';
|
|
51
51
|
import { PERSONA_MEMORY_KINDS, enqueueMemoryOp, } from '../../core/memory-sync/queue.js';
|
|
52
|
-
const DEFAULT_PERSONA = '
|
|
52
|
+
const DEFAULT_PERSONA = 'pugi';
|
|
53
53
|
function parseFlags(args) {
|
|
54
54
|
const flags = {
|
|
55
55
|
json: false,
|
|
@@ -372,7 +372,7 @@ const REMEMBER_USAGE = [
|
|
|
372
372
|
'',
|
|
373
373
|
'Flags:',
|
|
374
374
|
' --json Emit a JSON envelope instead of human text.',
|
|
375
|
-
' --persona <slug> Persona slug to attribute the memory to (default:
|
|
375
|
+
' --persona <slug> Persona slug to attribute the memory to (default: pugi).',
|
|
376
376
|
' --input <path> Read newline-separated candidates from a file.',
|
|
377
377
|
'',
|
|
378
378
|
'Every proposal is shown to the operator BEFORE persisting; nothing is',
|
package/dist/tui/agent-tree.js
CHANGED
|
@@ -33,6 +33,12 @@ function statusGlyph(status) {
|
|
|
33
33
|
// anti-pattern (memory feedback_no_fake_dispatch_promises).
|
|
34
34
|
case 'replied':
|
|
35
35
|
return '→';
|
|
36
|
+
// `unverified` (PUGI-538c-FU-OUTCOME) — files landed on disk but
|
|
37
|
+
// the verify-gate did not certify the run (no test command in
|
|
38
|
+
// the workspace). Tilde glyph reads as "approximately done"
|
|
39
|
+
// without claiming the verified-shipped guarantee.
|
|
40
|
+
case 'unverified':
|
|
41
|
+
return '~';
|
|
36
42
|
case 'blocked':
|
|
37
43
|
return '✗';
|
|
38
44
|
case 'failed':
|
|
@@ -50,6 +56,11 @@ function statusColor(status) {
|
|
|
50
56
|
// Dim/grey-ish — succeeded technically but no work was shipped.
|
|
51
57
|
case 'replied':
|
|
52
58
|
return 'gray';
|
|
59
|
+
// Yellow — work landed but the verify-gate could not certify it.
|
|
60
|
+
// Same hue as `blocked` because both are "advisory, not green";
|
|
61
|
+
// the glyph distinguishes them (`~` vs `✗`).
|
|
62
|
+
case 'unverified':
|
|
63
|
+
return 'yellow';
|
|
53
64
|
case 'blocked':
|
|
54
65
|
return 'yellow';
|
|
55
66
|
case 'failed':
|
|
@@ -76,7 +76,7 @@ export const ASK_CHIPS_PREVIEW_CHAR_CAP = 5000;
|
|
|
76
76
|
* `preview` field. The renderer uses this gate to switch к side-by-side
|
|
77
77
|
* layout (vertical option list + preview pane). When false, the legacy
|
|
78
78
|
* horizontal chip layout is preserved (zero regression for callers
|
|
79
|
-
* shipping previewless payloads —
|
|
79
|
+
* shipping previewless payloads — contract intact).
|
|
80
80
|
*/
|
|
81
81
|
export function hasAnyPreview(questions) {
|
|
82
82
|
return questions.some((q) => q.options.some((opt) => typeof opt.preview === 'string' && opt.preview.length > 0));
|
|
@@ -192,7 +192,7 @@ function buildResult(entries, verdicts, cancelled) {
|
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
194
|
export function MultiFileDiffApproval(props) {
|
|
195
|
-
// FIX (
|
|
195
|
+
// FIX ( triple-review): the previous `useMemo(() => props.entries,
|
|
196
196
|
// [props.entries])` was a no-op — it returned the same reference it
|
|
197
197
|
// depended on, so the memo never produced a stable identity gain. We
|
|
198
198
|
// reference `props.entries` directly now; downstream useEffect /
|
|
@@ -202,7 +202,7 @@ export function MultiFileDiffApproval(props) {
|
|
|
202
202
|
const [cursor, setCursor] = useState(0);
|
|
203
203
|
const [verdicts, setVerdicts] = useState(() => entries.map(() => 'pending'));
|
|
204
204
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
205
|
-
// FIX (
|
|
205
|
+
// FIX ( triple-review, P1): the lazy useState initialiser
|
|
206
206
|
// captures `entries.length` ONCE at mount. If the caller swaps in
|
|
207
207
|
// a different entries array (length mismatch), the verdicts vector
|
|
208
208
|
// would drift — `setVerdictAt` could index out of range or
|
|
@@ -222,7 +222,7 @@ export function MultiFileDiffApproval(props) {
|
|
|
222
222
|
return Math.min(c, entries.length - 1);
|
|
223
223
|
});
|
|
224
224
|
}, [entries.length, entries]);
|
|
225
|
-
// FIX (
|
|
225
|
+
// FIX ( triple-review, P1): `commit()` is called from inside
|
|
226
226
|
// the useInput closure, which captures the `verdicts` value at the
|
|
227
227
|
// render time the closure was created. With rapid keypresses (e.g.
|
|
228
228
|
// `a` then Enter on the same React tick), React has scheduled the
|
package/dist/tui/repl-render.js
CHANGED
|
@@ -27,9 +27,11 @@ import { ThemeProvider } from '../core/theme/context.js';
|
|
|
27
27
|
import { resolveTheme } from '../core/theme/state.js';
|
|
28
28
|
import { ReplSession, } from '../core/repl/session.js';
|
|
29
29
|
import { resolveWorkspaceContext } from '../core/repl/workspace-context.js';
|
|
30
|
+
import { createEngineBridge } from '../core/repl/engine-bridge.js';
|
|
30
31
|
import { SqliteSessionStore } from '../core/repl/store/index.js';
|
|
31
32
|
import { slugForCwd } from '../core/repl/history.js';
|
|
32
33
|
import { loadSettings } from '../core/settings.js';
|
|
34
|
+
import { buildRuntimeConfig } from '@pugi/sdk';
|
|
33
35
|
import { WorkingSet, buildRepoSkeleton, loadPugiIgnore, PugiWatcher, } from '../core/context/index.js';
|
|
34
36
|
/**
|
|
35
37
|
* Mount the REPL and resolve when the user exits via Ctrl+C × 2 or
|
|
@@ -136,12 +138,52 @@ export async function renderRepl(options) {
|
|
|
136
138
|
cwd: process.cwd(),
|
|
137
139
|
env: process.env,
|
|
138
140
|
});
|
|
141
|
+
// PUGI-538c () -- wire the production engine bridge so
|
|
142
|
+
// `<pugi-tool-route>` envelopes emitted by Pugi's coordinator
|
|
143
|
+
// actually drive a local `NativePugiEngineAdapter` run instead of
|
|
144
|
+
// surfacing "Engine bridge not configured" on the system line. The
|
|
145
|
+
// factory closure resolves `cwd` per invocation so an operator who
|
|
146
|
+
// `cd`-ed mid-session writes files into the new directory (matches
|
|
147
|
+
// `pugi code` direct-path behaviour).
|
|
148
|
+
//
|
|
149
|
+
// The bridge runtime config reuses the REPL transport credentials so
|
|
150
|
+
// a single token authenticates both the coordinator brief (via
|
|
151
|
+
// admin-api /api/pugi/sessions) AND the bridged engine call (via
|
|
152
|
+
// /api/pugi/engine). No new credential surface, no double login.
|
|
153
|
+
//
|
|
154
|
+
// Fail-safe construction: `buildRuntimeConfig` validates apiUrl via
|
|
155
|
+
// `z.string().url()` and throws on a malformed value. We must not
|
|
156
|
+
// crash REPL launch on that path -- every other bootstrap step in
|
|
157
|
+
// this file (auto-init, openLocalStore, bootstrapContext, watcher)
|
|
158
|
+
// is fail-safe. On construction failure we surface a one-line stderr
|
|
159
|
+
// diagnostic under PUGI_DEBUG=1 and pass `engineBridge: undefined`
|
|
160
|
+
// so the REPL launches; the operator sees the legacy "Engine bridge
|
|
161
|
+
// not configured" system line on the first routed brief, which is
|
|
162
|
+
// the same UX as a pre-PUGI-538c CLI build.
|
|
163
|
+
let engineBridge;
|
|
164
|
+
try {
|
|
165
|
+
engineBridge = createEngineBridge({
|
|
166
|
+
config: buildRuntimeConfig({
|
|
167
|
+
apiUrl: options.apiUrl,
|
|
168
|
+
apiKey: options.apiKey,
|
|
169
|
+
}),
|
|
170
|
+
cwd: () => process.cwd(),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (process.env.PUGI_DEBUG === '1') {
|
|
175
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
176
|
+
process.stderr.write(`[pugi-debug] engine bridge bootstrap failed: ${msg}\n`);
|
|
177
|
+
}
|
|
178
|
+
engineBridge = undefined;
|
|
179
|
+
}
|
|
139
180
|
const session = new ReplSession({
|
|
140
181
|
apiUrl: options.apiUrl,
|
|
141
182
|
apiKey: options.apiKey,
|
|
142
183
|
workspaceLabel: options.workspaceLabel,
|
|
143
184
|
cliVersion: options.cliVersion,
|
|
144
185
|
transport,
|
|
186
|
+
...(engineBridge !== undefined ? { engineBridge } : {}),
|
|
145
187
|
workspace,
|
|
146
188
|
cyberZoo,
|
|
147
189
|
store,
|
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.94",
|
|
4
4
|
"description": "Pugi CLI - terminal-native software execution system",
|
|
5
5
|
"homepage": "https://pugi.io",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"which": "^6.0.0",
|
|
64
64
|
"zod": "^3.23.0",
|
|
65
65
|
"@pugi/personas": "0.1.2",
|
|
66
|
-
"@pugi/sdk": "0.1.0-beta.
|
|
66
|
+
"@pugi/sdk": "0.1.0-beta.94"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node": "^22.0.0",
|