@pugi/cli 0.1.0-beta.43 → 0.1.0-beta.45
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/bash-classifier.js +132 -3
- package/dist/runtime/cli.js +54 -0
- package/dist/runtime/version.js +1 -1
- package/dist/tui/input-box.js +70 -1
- package/package.json +2 -2
|
@@ -119,6 +119,63 @@ const DESTRUCTIVE_PATTERNS = [
|
|
|
119
119
|
// History destruction
|
|
120
120
|
{ pattern: 'history -c' },
|
|
121
121
|
{ pattern: ' >/dev/null 2>&1; rm' },
|
|
122
|
+
// ---------------------------------------------------------------
|
|
123
|
+
// Patterns ported from KeiSeiKit `destructive-guard.sh` (Apache-2.0)
|
|
124
|
+
// and `safety-guard.sh` BLOCKED_PATTERNS array. Upstream source:
|
|
125
|
+
// /tmp/KeiSeiKit/hooks/destructive-guard.sh (lines 7-13)
|
|
126
|
+
// /tmp/KeiSeiKit/hooks/safety-guard.sh (lines 14-50)
|
|
127
|
+
// Attribution: licenses/keiseikit-LICENSE-NOTICE.md.
|
|
128
|
+
//
|
|
129
|
+
// The patterns below need word-boundary matching because their
|
|
130
|
+
// tokens (kill, halt, reboot, ...) appear as substrings of common
|
|
131
|
+
// unrelated words (skills, default, chrooted-rebooter, etc.).
|
|
132
|
+
// Substring `.includes` cannot express that — `regex` is required.
|
|
133
|
+
// ---------------------------------------------------------------
|
|
134
|
+
// Process termination — `kill`, `pkill`, `killall` at command head
|
|
135
|
+
// or after `sudo`. Matches `kill 1234`, `kill -9 $$`, `sudo killall
|
|
136
|
+
// node`, but NOT `skill issue` (no leading boundary) or
|
|
137
|
+
// `git commit -m "skill kill story"` (the kill is inside a quoted
|
|
138
|
+
// string — quote-aware split handled upstream; here we still need
|
|
139
|
+
// the boundary). Anchored to start-of-component or `sudo ` prefix.
|
|
140
|
+
{
|
|
141
|
+
pattern: 'kill',
|
|
142
|
+
regex: /^(?:sudo\s+)?(?:pkill|killall|kill)\b/,
|
|
143
|
+
},
|
|
144
|
+
// System power state — reboot / shutdown / halt / poweroff / init 0
|
|
145
|
+
// / init 6. KeiSei matches these anywhere in the command; we
|
|
146
|
+
// tighten to start-of-component or `sudo ` prefix to avoid FPs on
|
|
147
|
+
// file paths or variable names containing the substring.
|
|
148
|
+
{
|
|
149
|
+
pattern: 'reboot',
|
|
150
|
+
regex: /^(?:sudo\s+)?reboot\b/,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
pattern: 'shutdown',
|
|
154
|
+
regex: /^(?:sudo\s+)?shutdown\b/,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
pattern: 'halt',
|
|
158
|
+
regex: /^(?:sudo\s+)?halt\b/,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
pattern: 'poweroff',
|
|
162
|
+
regex: /^(?:sudo\s+)?poweroff\b/,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
pattern: 'init 0',
|
|
166
|
+
regex: /^(?:sudo\s+)?init\s+0\b/,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
pattern: 'init 6',
|
|
170
|
+
regex: /^(?:sudo\s+)?init\s+6\b/,
|
|
171
|
+
},
|
|
172
|
+
// `git clean -f` (without -dx) — KeiSei lists this as destructive
|
|
173
|
+
// because it still deletes untracked files. Pugi previously only
|
|
174
|
+
// gated `git clean -fdx`; broaden to any `-f` variant.
|
|
175
|
+
{
|
|
176
|
+
pattern: 'git clean -f',
|
|
177
|
+
regex: /\bgit\s+clean\s+-[A-Za-z]*f/,
|
|
178
|
+
},
|
|
122
179
|
];
|
|
123
180
|
/**
|
|
124
181
|
* Compound separators. We split on `&&`, `||`, `;`, `|` to classify
|
|
@@ -622,7 +679,15 @@ function classifyComponent(cmd, ctx) {
|
|
|
622
679
|
}
|
|
623
680
|
function findDestructiveMatch(cmd) {
|
|
624
681
|
const upper = cmd.toUpperCase();
|
|
625
|
-
for (const { pattern, caseInsensitive } of DESTRUCTIVE_PATTERNS) {
|
|
682
|
+
for (const { pattern, caseInsensitive, regex } of DESTRUCTIVE_PATTERNS) {
|
|
683
|
+
if (regex) {
|
|
684
|
+
// Word-boundary regex form (KeiSei-derived patterns). Match
|
|
685
|
+
// against the trimmed component so `^` anchors to command head,
|
|
686
|
+
// not surrounding whitespace from the compound split.
|
|
687
|
+
if (regex.test(cmd.trim()))
|
|
688
|
+
return pattern;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
626
691
|
if (caseInsensitive) {
|
|
627
692
|
if (upper.includes(pattern))
|
|
628
693
|
return pattern;
|
|
@@ -697,8 +762,17 @@ function detectProtectedWrite(cmd, ctx) {
|
|
|
697
762
|
// Captures `sort -o`, `uniq <in> <out>`, `sed -i` files, `awk '... > "file"'`,
|
|
698
763
|
// and `>` / `>>` redirections without surrounding whitespace.
|
|
699
764
|
const writeTargets = extractWriteTargets(cmd);
|
|
765
|
+
// Strip heredoc bodies before substring scan. Heredoc payloads are
|
|
766
|
+
// DATA (file contents the script writes), not commands the shell
|
|
767
|
+
// executes — a `package.json` body containing `/usr/local/bin/...`
|
|
768
|
+
// would FP as "Write into protected path: /usr/" under the broad
|
|
769
|
+
// includes() scan below. The per-target check at the bottom of this
|
|
770
|
+
// function still catches real `cat > /usr/file << EOF` attempts
|
|
771
|
+
// because extractWriteTargets reads the redirection target, not the
|
|
772
|
+
// heredoc body. CEO dogfood 2026-05-28 (#28 follow-up).
|
|
773
|
+
const cmdForScan = stripHeredocBodies(cmd);
|
|
700
774
|
for (const needle of PROTECTED_PATH_SUBSTRINGS) {
|
|
701
|
-
if (!
|
|
775
|
+
if (!cmdForScan.includes(needle))
|
|
702
776
|
continue;
|
|
703
777
|
// Reading from a protected path is allowed at the classifier
|
|
704
778
|
// layer (the permission engine still gates `read`); writing is
|
|
@@ -760,6 +834,56 @@ function detectProtectedWrite(cmd, ctx) {
|
|
|
760
834
|
* Conservative — we do not try to resolve shell vars or globs; the
|
|
761
835
|
* caller still gates absolute paths via `looksAbsoluteOutsideWorkspace`.
|
|
762
836
|
*/
|
|
837
|
+
/**
|
|
838
|
+
* Strip heredoc bodies so substring scans (e.g. `cmd.includes('/usr/')`)
|
|
839
|
+
* do not false-positive on file content the script is *writing*. A
|
|
840
|
+
* heredoc starts с `<< 'WORD'` (or `<< WORD` / `<<-WORD`) and ends на a
|
|
841
|
+
* line containing only WORD. The body between is DATA, not commands.
|
|
842
|
+
*
|
|
843
|
+
* Best-effort: handles single-heredoc-per-command (the common case)
|
|
844
|
+
* AND multiple sequential heredocs. Nested heredocs (heredoc-inside-
|
|
845
|
+
* heredoc) are rare and out of scope — the substring scan still gates
|
|
846
|
+
* the outer command, just без stripping the nested body. Per-target
|
|
847
|
+
* detection at detectProtectedWrite's tail loop catches real
|
|
848
|
+
* `cat > /usr/file << EOF` attacks regardless of body content.
|
|
849
|
+
*
|
|
850
|
+
* CEO dogfood 2026-05-28 (#28): `cat > package.json << 'EOF'\n{"bin":
|
|
851
|
+
* "/usr/local/bin/foo"}\nEOF` was rejected as "Write into protected
|
|
852
|
+
* path: /usr/" because the broad substring scan saw `/usr/` in the
|
|
853
|
+
* JSON body. With heredoc-body stripping, the scan now sees only
|
|
854
|
+
* `cat > package.json << 'EOF' EOF` which contains no protected path.
|
|
855
|
+
*/
|
|
856
|
+
function stripHeredocBodies(cmd) {
|
|
857
|
+
// Match `<< [-]'WORD'` or `<< [-]"WORD"` or `<< [-]WORD` (quoted form
|
|
858
|
+
// disables variable expansion in real bash; we treat all three the
|
|
859
|
+
// same for stripping). Capture the WORD so we can find the close
|
|
860
|
+
// marker.
|
|
861
|
+
const heredocStart = /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1/g;
|
|
862
|
+
let out = cmd;
|
|
863
|
+
let safetyLoops = 0;
|
|
864
|
+
let match;
|
|
865
|
+
while ((match = heredocStart.exec(out)) !== null) {
|
|
866
|
+
if (++safetyLoops > 16)
|
|
867
|
+
break;
|
|
868
|
+
const word = match[2];
|
|
869
|
+
if (!word)
|
|
870
|
+
continue;
|
|
871
|
+
const headEnd = match.index + match[0].length;
|
|
872
|
+
// Find the close-marker line: `\n<optional indent>WORD<\n|$>`.
|
|
873
|
+
const closeRegex = new RegExp(`\\n\\s*${word}(?:\\n|$)`);
|
|
874
|
+
closeRegex.lastIndex = headEnd;
|
|
875
|
+
const closeMatch = closeRegex.exec(out.slice(headEnd));
|
|
876
|
+
if (!closeMatch)
|
|
877
|
+
break;
|
|
878
|
+
const closeStart = headEnd + closeMatch.index;
|
|
879
|
+
const closeEnd = closeStart + closeMatch[0].length;
|
|
880
|
+
// Replace heredoc body + close marker с single space so the regex
|
|
881
|
+
// iterator's lastIndex stays meaningful.
|
|
882
|
+
out = out.slice(0, headEnd) + ' ' + out.slice(closeEnd);
|
|
883
|
+
heredocStart.lastIndex = headEnd + 1;
|
|
884
|
+
}
|
|
885
|
+
return out;
|
|
886
|
+
}
|
|
763
887
|
function extractWriteTargets(cmd) {
|
|
764
888
|
const targets = [];
|
|
765
889
|
// Shell redirection (`>`, `>>`) with optional whitespace. Skip
|
|
@@ -831,8 +955,13 @@ function detectProtectedRead(cmd) {
|
|
|
831
955
|
firstToken === 'find';
|
|
832
956
|
if (!isReadTool)
|
|
833
957
|
return null;
|
|
958
|
+
// Strip heredoc bodies so `cat > config << 'EOF'\n... /etc/... \nEOF`
|
|
959
|
+
// does not FP as "Read from protected path" when first-token=`cat` +
|
|
960
|
+
// redirection writes к workspace-local file. Heredoc payload is data.
|
|
961
|
+
// CEO dogfood 2026-05-28 (#28).
|
|
962
|
+
const cmdForScan = stripHeredocBodies(cmd);
|
|
834
963
|
for (const needle of PROTECTED_PATH_SUBSTRINGS) {
|
|
835
|
-
if (
|
|
964
|
+
if (cmdForScan.includes(needle)) {
|
|
836
965
|
return {
|
|
837
966
|
reason: `Read from protected path: ${needle}`,
|
|
838
967
|
matched: needle,
|
package/dist/runtime/cli.js
CHANGED
|
@@ -1168,6 +1168,33 @@ export async function runCli(argv) {
|
|
|
1168
1168
|
// Propagating via env keeps the session module transport-free.
|
|
1169
1169
|
if (flags.allowFetch)
|
|
1170
1170
|
process.env.PUGI_ALLOW_FETCH = '1';
|
|
1171
|
+
// CEO P0 (2026-05-28): auto-init pre-flight on the bare REPL
|
|
1172
|
+
// boot path. PR #628 wired `runAutoInitPreflight` into every
|
|
1173
|
+
// engine command (`pugi code/fix/build/...`) but the bare
|
|
1174
|
+
// `pugi` REPL entry boots straight into the workspace label
|
|
1175
|
+
// resolver — which surfaces the "(not bound — run /init OR cd
|
|
1176
|
+
// into project)" banner without ever asking the operator
|
|
1177
|
+
// whether к initialise the workspace. CEO escalation: closed
|
|
1178
|
+
// multiple times wrong; the operator hits the banner and walks
|
|
1179
|
+
// away thinking Pugi is broken. We use the REPL-tuned variant
|
|
1180
|
+
// that NEVER throws — `n`, `--bare`, `--no-init`, non-TTY all
|
|
1181
|
+
// fall through к the legacy "not bound" banner so the existing
|
|
1182
|
+
// contract (booting REPL still works without `.pugi/`) holds.
|
|
1183
|
+
// The prompt fires ONLY on the happy path: interactive TTY,
|
|
1184
|
+
// no `.pugi/`, no opt-out flag. Wrapped in a defensive try/
|
|
1185
|
+
// catch — the scaffold may legitimately fail (read-only fs,
|
|
1186
|
+
// permission denied) but the REPL still needs to boot so the
|
|
1187
|
+
// operator gets a usable surface к diagnose the failure.
|
|
1188
|
+
try {
|
|
1189
|
+
await runReplAutoInitPreflight(process.cwd(), flags);
|
|
1190
|
+
}
|
|
1191
|
+
catch (error) {
|
|
1192
|
+
// Surface the scaffold error on stderr but proceed to mount
|
|
1193
|
+
// the REPL. Crashing here would regress the splash-fallback
|
|
1194
|
+
// path the dispatcher used to take when `.pugi/` was missing.
|
|
1195
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1196
|
+
process.stderr.write(`Auto-init failed: ${message}\n`);
|
|
1197
|
+
}
|
|
1171
1198
|
// α6.2: peek the npm registry for a newer @pugi/cli before
|
|
1172
1199
|
// mounting Ink. Wrapped in a try/catch belt-and-braces even
|
|
1173
1200
|
// though `checkForUpdate` already swallows every failure mode —
|
|
@@ -1187,6 +1214,12 @@ export async function runCli(argv) {
|
|
|
1187
1214
|
await renderRepl({
|
|
1188
1215
|
apiUrl: runtimeConfig.apiUrl,
|
|
1189
1216
|
apiKey: runtimeConfig.apiKey,
|
|
1217
|
+
// Re-resolve AFTER the auto-init pre-flight so a freshly-
|
|
1218
|
+
// scaffolded `.pugi/PUGI.md` flips the splash label from
|
|
1219
|
+
// "(not bound — run /init OR cd into project)" к the project
|
|
1220
|
+
// basename in the same boot cycle. `workspaceLabel` is a
|
|
1221
|
+
// pure function over `process.cwd()`; calling it twice is
|
|
1222
|
+
// cheap.
|
|
1190
1223
|
workspaceLabel: workspaceLabel(process.cwd()),
|
|
1191
1224
|
cliVersion: PUGI_CLI_VERSION,
|
|
1192
1225
|
updateBanner,
|
|
@@ -6466,6 +6499,27 @@ async function runAutoInitPreflight(root, flags) {
|
|
|
6466
6499
|
throw new Error('Run pugi init first');
|
|
6467
6500
|
}
|
|
6468
6501
|
}
|
|
6502
|
+
export async function runReplAutoInitPreflight(root, flags, overrides = {}) {
|
|
6503
|
+
const interactive = overrides.interactive ?? isInteractive(flags);
|
|
6504
|
+
return ensureInitializedHelper({
|
|
6505
|
+
cwd: root,
|
|
6506
|
+
interactive,
|
|
6507
|
+
// Leak L22 (2026-05-27): `--bare` short-circuits BEFORE the prompt.
|
|
6508
|
+
// Bare mode is the operator's explicit "disable project auto-
|
|
6509
|
+
// discovery" signal — scaffolding `.pugi/` would directly violate
|
|
6510
|
+
// that contract. Treat `--bare` the same as `--no-init` for the
|
|
6511
|
+
// pre-flight gate.
|
|
6512
|
+
skip: flags.noInit
|
|
6513
|
+
|| flags.bare
|
|
6514
|
+
|| process.env.PUGI_NO_AUTO_INIT === '1'
|
|
6515
|
+
|| process.env.PUGI_BARE === '1',
|
|
6516
|
+
prompt: overrides.prompt ?? (async (question) => readSingleChoice(question)),
|
|
6517
|
+
scaffold: overrides.scaffold
|
|
6518
|
+
?? (async (input) => {
|
|
6519
|
+
await scaffoldPugiWorkspace({ cwd: input.cwd, noDefaults: flags.noDefaults });
|
|
6520
|
+
}),
|
|
6521
|
+
});
|
|
6522
|
+
}
|
|
6469
6523
|
/**
|
|
6470
6524
|
* Wave 6 UX (2026-05-27): async pre-flight wrapper around the
|
|
6471
6525
|
* `ensureAuthenticatedHelper` from `core/auth/ensure-authenticated.ts`.
|
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.45');
|
|
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.
|
package/dist/tui/input-box.js
CHANGED
|
@@ -238,7 +238,9 @@ export function InputBox(props) {
|
|
|
238
238
|
setCursor(draftBeforeSearch.length);
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
|
-
|
|
241
|
+
// Bare LF accepts the focused match, same as CR (`key.return`).
|
|
242
|
+
// See the post-search block below for the rationale.
|
|
243
|
+
if (key.return || (input === '\n' && !key.meta && !key.ctrl && !key.shift)) {
|
|
242
244
|
const picked = currentBrief(search);
|
|
243
245
|
setSearch(undefined);
|
|
244
246
|
if (picked !== null) {
|
|
@@ -257,6 +259,11 @@ export function InputBox(props) {
|
|
|
257
259
|
return;
|
|
258
260
|
}
|
|
259
261
|
if (input && !key.meta && !key.ctrl) {
|
|
262
|
+
// Drop a bare LF from the search query — the Enter-accept
|
|
263
|
+
// branch above already handled it; falling through here would
|
|
264
|
+
// splice a newline into the search string.
|
|
265
|
+
if (input === '\n')
|
|
266
|
+
return;
|
|
260
267
|
const nextQuery = search.query + input;
|
|
261
268
|
setSearch(applyQuery(search, nextQuery, history));
|
|
262
269
|
return;
|
|
@@ -279,6 +286,68 @@ export function InputBox(props) {
|
|
|
279
286
|
setSearch(initialSearchState(history));
|
|
280
287
|
return;
|
|
281
288
|
}
|
|
289
|
+
// P0 fix (CEO 2026-05-29 dogfood): bare LF (`\n`) MUST submit the
|
|
290
|
+
// brief, same as bare CR (`\r`). Ink's parseKeypress maps `\r` to
|
|
291
|
+
// `key.return` and `\n` to `key.name === 'enter'` WITHOUT setting
|
|
292
|
+
// `key.return`. Most real terminals deliver CR for Enter (ICRNL on
|
|
293
|
+
// by default), so the `key.return` branch below catches them. But
|
|
294
|
+
// when stdin is a PTY whose parent writes raw `\n` (Python's
|
|
295
|
+
// `pty.fork` + `os.write(fd, b"\n")`, automation harnesses,
|
|
296
|
+
// certain SSH multiplexers), the LF arrives as a printable char
|
|
297
|
+
// and lands in the buffer as a literal newline. The result: the
|
|
298
|
+
// input box shows a multi-line composer, the brief never
|
|
299
|
+
// dispatches, status stays "idle". CEO PTY repro 2026-05-29.
|
|
300
|
+
//
|
|
301
|
+
// Detection contract: the chunk is a SINGLE bare `\n` with no
|
|
302
|
+
// modifiers and not inside a bracketed-paste burst. Multi-char
|
|
303
|
+
// chunks containing `\n` (multi-line pastes, terminal-pasted
|
|
304
|
+
// markdown blocks) preserve interior newlines so the operator
|
|
305
|
+
// sees the full text in the buffer — those are handled by the
|
|
306
|
+
// printable-char branch below.
|
|
307
|
+
if (input === '\n' && !key.meta && !key.ctrl && !key.shift) {
|
|
308
|
+
// Synthesise the same payload-shape the `key.return` branch
|
|
309
|
+
// below uses so palette completion + history dedup + onSubmit
|
|
310
|
+
// dispatch all run identically. We re-enter the handler with a
|
|
311
|
+
// synthetic key.return = true would require a refactor; the
|
|
312
|
+
// cheap fix is to inline the submit logic here in a way that
|
|
313
|
+
// mirrors the canonical branch exactly. Kept tight + obvious so
|
|
314
|
+
// future edits to one path get mirrored to the other.
|
|
315
|
+
const paletteHere = !paletteSuppressed
|
|
316
|
+
? filterPalette(line)
|
|
317
|
+
: { rows: [], totalBeforeLimit: 0 };
|
|
318
|
+
const paletteOpenHere = paletteHere.rows.length > 0;
|
|
319
|
+
const paletteFocusedIndexHere = paletteHere.rows.length === 0
|
|
320
|
+
? 0
|
|
321
|
+
: Math.min(paletteIndex, paletteHere.rows.length - 1);
|
|
322
|
+
let payload = line;
|
|
323
|
+
if (paletteOpenHere) {
|
|
324
|
+
const completed = completePalette(line, paletteHere.rows, paletteFocusedIndexHere);
|
|
325
|
+
if (completed !== null)
|
|
326
|
+
payload = completed;
|
|
327
|
+
}
|
|
328
|
+
const trimmed = payload.trim();
|
|
329
|
+
if (trimmed.length > 0) {
|
|
330
|
+
setHistory((prev) => {
|
|
331
|
+
if (prev[prev.length - 1] === trimmed)
|
|
332
|
+
return prev;
|
|
333
|
+
return [...prev, trimmed];
|
|
334
|
+
});
|
|
335
|
+
setHistoryIndex(-1);
|
|
336
|
+
if (props.workspaceSlug) {
|
|
337
|
+
appendHistory({
|
|
338
|
+
home: props.historyHome,
|
|
339
|
+
workspaceSlug: props.workspaceSlug,
|
|
340
|
+
brief: trimmed,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
props.onSubmit(trimmed);
|
|
344
|
+
}
|
|
345
|
+
setLine('');
|
|
346
|
+
setCursor(0);
|
|
347
|
+
setPaletteSuppressed(false);
|
|
348
|
+
setPaletteIndex(0);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
282
351
|
// Readline-style kill ring shortcuts. All four kills push the
|
|
283
352
|
// removed slice onto the ring; Ctrl+Y yanks the most recent.
|
|
284
353
|
if (key.ctrl && input === 'u') {
|
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.45",
|
|
4
4
|
"description": "Pugi CLI - terminal-native software execution system",
|
|
5
5
|
"homepage": "https://pugi.io",
|
|
6
6
|
"repository": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"undici": "^8.3.0",
|
|
56
56
|
"zod": "^3.23.0",
|
|
57
57
|
"@pugi/personas": "0.1.2",
|
|
58
|
-
"@pugi/sdk": "0.1.0-beta.
|
|
58
|
+
"@pugi/sdk": "0.1.0-beta.45"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "^22.0.0",
|