@czottmann/pi-automode 1.12.0 → 1.14.0
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/CHANGELOG.md +27 -0
- package/README.md +6 -4
- package/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +2 -0
- package/docs/adr/ADR-002-global-config-in-extension-data-directory.md +60 -0
- package/docs/adr/INDEX.md +1 -0
- package/docs/automode-classifier-flow.md +4 -4
- package/docs/configuration.md +20 -4
- package/docs/observability-logging.md +1 -1
- package/extensions/auto-mode/bash.ts +692 -0
- package/extensions/auto-mode/classifier.ts +55 -4
- package/extensions/auto-mode/config.ts +188 -6
- package/extensions/auto-mode/constants.ts +6 -1
- package/extensions/auto-mode/extension.ts +143 -36
- package/extensions/auto-mode/hard-deny.ts +225 -159
- package/extensions/auto-mode/paths.ts +32 -6
- package/extensions/auto-mode/permissions.ts +344 -22
- package/extensions/auto-mode.ts +1 -0
- package/package.json +4 -1
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeBash,
|
|
3
|
+
type BashAnalysis,
|
|
4
|
+
type BashCommandAnalysis,
|
|
5
|
+
type BashRedirectAnalysis,
|
|
6
|
+
} from "./bash.ts";
|
|
1
7
|
import type { ToolPattern } from "./types.ts";
|
|
2
8
|
import {
|
|
3
9
|
expandHomePattern,
|
|
4
10
|
normalizePathForMatch,
|
|
11
|
+
resolveInputPath,
|
|
5
12
|
resolvePathForPolicy,
|
|
6
13
|
resolveToolInputPath,
|
|
7
14
|
} from "./paths.ts";
|
|
@@ -9,6 +16,8 @@ import {
|
|
|
9
16
|
export const MAX_WILDCARD_PATTERN_LENGTH = 4096;
|
|
10
17
|
export const MAX_WILDCARD_INPUT_LENGTH = 1024 * 1024;
|
|
11
18
|
|
|
19
|
+
const bashPatternAnalyses = new WeakMap<ToolPattern, BashAnalysis>();
|
|
20
|
+
|
|
12
21
|
/** Preserve the previous non-Unicode RegExp `/i` case-equivalence rules. */
|
|
13
22
|
function canonicalizeCase(value: string): string {
|
|
14
23
|
let canonical = "";
|
|
@@ -55,11 +64,14 @@ export function parseToolPattern(value: unknown): ToolPattern | undefined {
|
|
|
55
64
|
|
|
56
65
|
const match = raw.match(/^@?([A-Za-z0-9_-]+)(?:\((.*)\))?$/s);
|
|
57
66
|
if (!match) return { raw };
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
argumentPattern
|
|
62
|
-
|
|
67
|
+
const toolName = normalizeToolName(match[1] ?? "");
|
|
68
|
+
const argumentPattern = match[2];
|
|
69
|
+
const bashPatternAnalysis = toolName === "bash" && argumentPattern
|
|
70
|
+
? analyzeBash(argumentPattern)
|
|
71
|
+
: undefined;
|
|
72
|
+
const pattern: ToolPattern = { raw, toolName, argumentPattern };
|
|
73
|
+
if (bashPatternAnalysis) bashPatternAnalyses.set(pattern, bashPatternAnalysis);
|
|
74
|
+
return pattern;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
function literalPrefixTable(value: string): number[] {
|
|
@@ -166,36 +178,110 @@ export function matchesWildcardPattern(
|
|
|
166
178
|
return true;
|
|
167
179
|
}
|
|
168
180
|
|
|
169
|
-
function
|
|
181
|
+
export function normalizePermissionPathForMatch(
|
|
182
|
+
path: string,
|
|
183
|
+
platform: NodeJS.Platform = process.platform,
|
|
184
|
+
): string {
|
|
185
|
+
return platform === "win32" ? path.replace(/\\/g, "/") : path;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function pathArgumentsForMatch(
|
|
189
|
+
toolName: string,
|
|
190
|
+
cwd: string,
|
|
191
|
+
value: string,
|
|
192
|
+
overflowPolicy: WildcardOverflowPolicy,
|
|
193
|
+
): string[] {
|
|
194
|
+
const resolved = resolveToolInputPath(toolName, cwd, value) ?? value;
|
|
195
|
+
const canonical = resolvePathForPolicy(resolved);
|
|
196
|
+
const candidates = canonical
|
|
197
|
+
? [canonical, normalizePathForMatch(canonical, cwd)]
|
|
198
|
+
: [];
|
|
199
|
+
if (overflowPolicy === "match") {
|
|
200
|
+
candidates.push(resolved, normalizePathForMatch(resolved, cwd));
|
|
201
|
+
}
|
|
202
|
+
return [...new Set(
|
|
203
|
+
candidates.map((candidate) => normalizePermissionPathForMatch(candidate)),
|
|
204
|
+
)];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getPrimaryArguments(
|
|
170
208
|
toolName: string,
|
|
171
209
|
input: Record<string, unknown>,
|
|
172
210
|
cwd: string,
|
|
173
|
-
|
|
211
|
+
overflowPolicy: WildcardOverflowPolicy,
|
|
212
|
+
): string[] {
|
|
174
213
|
if (toolName === "bash" && typeof input.command === "string") {
|
|
175
|
-
return input.command;
|
|
214
|
+
return [input.command];
|
|
176
215
|
}
|
|
177
216
|
if (
|
|
178
217
|
(toolName === "read" || toolName === "write" || toolName === "edit") &&
|
|
179
218
|
typeof input.path === "string"
|
|
180
219
|
) {
|
|
181
|
-
return
|
|
182
|
-
resolveToolInputPath(toolName, cwd, input.path) ?? input.path,
|
|
183
|
-
cwd,
|
|
184
|
-
);
|
|
220
|
+
return pathArgumentsForMatch(toolName, cwd, input.path, overflowPolicy);
|
|
185
221
|
}
|
|
186
222
|
if (toolName === "grep" && typeof input.pattern === "string") {
|
|
187
|
-
return input.pattern;
|
|
223
|
+
return [input.pattern];
|
|
188
224
|
}
|
|
189
225
|
if (
|
|
190
226
|
(toolName === "find" || toolName === "ls") &&
|
|
191
227
|
typeof input.path === "string"
|
|
192
228
|
) {
|
|
193
|
-
return
|
|
194
|
-
resolveToolInputPath(toolName, cwd, input.path) ?? input.path,
|
|
195
|
-
cwd,
|
|
196
|
-
);
|
|
229
|
+
return pathArgumentsForMatch(toolName, cwd, input.path, overflowPolicy);
|
|
197
230
|
}
|
|
198
|
-
return JSON.stringify(input);
|
|
231
|
+
return [JSON.stringify(input)];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function isPermissionPathTool(toolName: string): boolean {
|
|
235
|
+
return toolName === "read" ||
|
|
236
|
+
toolName === "write" ||
|
|
237
|
+
toolName === "edit" ||
|
|
238
|
+
toolName === "find" ||
|
|
239
|
+
toolName === "ls";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function appendPermissionPathPatternSuffix(
|
|
243
|
+
scope: string,
|
|
244
|
+
suffix: string,
|
|
245
|
+
): string {
|
|
246
|
+
const normalizedScope = withoutTrailingSlash(
|
|
247
|
+
normalizePermissionPathForMatch(scope),
|
|
248
|
+
);
|
|
249
|
+
return normalizedScope.endsWith("/")
|
|
250
|
+
? `${normalizedScope}${suffix}`
|
|
251
|
+
: `${normalizedScope}/${suffix}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function permissionPathPatternVariants(
|
|
255
|
+
pattern: string,
|
|
256
|
+
cwd: string,
|
|
257
|
+
): string[] {
|
|
258
|
+
const expanded = normalizePermissionPathForMatch(expandHomePattern(pattern));
|
|
259
|
+
const wildcardIndex = expanded.indexOf("*");
|
|
260
|
+
if (wildcardIndex === -1) {
|
|
261
|
+
const resolved = resolveInputPath(cwd, expanded);
|
|
262
|
+
const canonical = resolved ? resolvePathForPolicy(resolved) : undefined;
|
|
263
|
+
return [...new Set(
|
|
264
|
+
[expanded, resolved, canonical]
|
|
265
|
+
.filter((value): value is string => !!value)
|
|
266
|
+
.map((value) => normalizePermissionPathForMatch(value)),
|
|
267
|
+
)];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const fixedPrefix = expanded.slice(0, wildcardIndex);
|
|
271
|
+
const lastSlash = fixedPrefix.lastIndexOf("/");
|
|
272
|
+
if (lastSlash < 0) return [expanded];
|
|
273
|
+
const fixedScope = fixedPrefix.slice(0, lastSlash) || "/";
|
|
274
|
+
const resolvedScope = resolveInputPath(cwd, fixedScope);
|
|
275
|
+
if (!resolvedScope) return [expanded];
|
|
276
|
+
const canonicalScope = resolvePathForPolicy(resolvedScope);
|
|
277
|
+
const suffix = expanded.slice(lastSlash).replace(/^\/+/, "");
|
|
278
|
+
return [...new Set([
|
|
279
|
+
expanded,
|
|
280
|
+
appendPermissionPathPatternSuffix(resolvedScope, suffix),
|
|
281
|
+
...(canonicalScope
|
|
282
|
+
? [appendPermissionPathPatternSuffix(canonicalScope, suffix)]
|
|
283
|
+
: []),
|
|
284
|
+
])];
|
|
199
285
|
}
|
|
200
286
|
|
|
201
287
|
/**
|
|
@@ -285,6 +371,37 @@ export function recursiveSearchMayReachDeniedPath(
|
|
|
285
371
|
});
|
|
286
372
|
}
|
|
287
373
|
|
|
374
|
+
function normalizedBashArgumentPattern(pattern: ToolPattern): string {
|
|
375
|
+
const patternAnalysis = bashPatternAnalyses.get(pattern);
|
|
376
|
+
if (
|
|
377
|
+
patternAnalysis &&
|
|
378
|
+
patternAnalysis.errors.length === 0 &&
|
|
379
|
+
patternAnalysis.redirects.length === 0 &&
|
|
380
|
+
isStructurallyPlainSingleCommand(patternAnalysis)
|
|
381
|
+
) {
|
|
382
|
+
return patternAnalysis.commands[0]?.text ?? pattern.argumentPattern ?? "";
|
|
383
|
+
}
|
|
384
|
+
return pattern.argumentPattern ?? "";
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function matchesBashArgumentPattern(
|
|
388
|
+
argumentPattern: string,
|
|
389
|
+
candidate: string,
|
|
390
|
+
overflowPolicy: WildcardOverflowPolicy,
|
|
391
|
+
): boolean {
|
|
392
|
+
if (matchesWildcardPattern(argumentPattern, candidate, overflowPolicy)) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
if (overflowPolicy !== "match" || !argumentPattern.endsWith(" *")) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
return matchesWildcardPattern(
|
|
399
|
+
argumentPattern.slice(0, -2),
|
|
400
|
+
candidate,
|
|
401
|
+
overflowPolicy,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
288
405
|
/** Match a scoped permission rule against a concrete tool call. */
|
|
289
406
|
export function matchesToolPattern(
|
|
290
407
|
pattern: ToolPattern,
|
|
@@ -292,10 +409,215 @@ export function matchesToolPattern(
|
|
|
292
409
|
input: Record<string, unknown>,
|
|
293
410
|
cwd: string,
|
|
294
411
|
overflowPolicy: WildcardOverflowPolicy = "match",
|
|
412
|
+
bashAnalysis?: BashAnalysis,
|
|
295
413
|
): boolean {
|
|
296
|
-
if (!pattern.toolName) return
|
|
414
|
+
if (!pattern.toolName) return overflowPolicy === "match";
|
|
297
415
|
if (pattern.toolName !== normalizeToolName(toolName)) return false;
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
416
|
+
if (pattern.argumentPattern === undefined) return true;
|
|
417
|
+
if (pattern.argumentPattern.trim() === "") {
|
|
418
|
+
return overflowPolicy === "match";
|
|
419
|
+
}
|
|
420
|
+
if (
|
|
421
|
+
toolName === "bash" &&
|
|
422
|
+
(bashPatternAnalyses.get(pattern)?.errors.length ?? 0) > 0
|
|
423
|
+
) {
|
|
424
|
+
return overflowPolicy === "match";
|
|
425
|
+
}
|
|
426
|
+
if (toolName === "bash" && bashAnalysis) {
|
|
427
|
+
if (bashAnalysis.errors.length > 0) return overflowPolicy === "match";
|
|
428
|
+
const candidates = overflowPolicy === "match"
|
|
429
|
+
? [bashAnalysis.source, ...bashAnalysis.commands.map((command) => command.text)]
|
|
430
|
+
: [bashAnalysis.source];
|
|
431
|
+
const argumentPattern = normalizedBashArgumentPattern(pattern);
|
|
432
|
+
return candidates.some((candidate) =>
|
|
433
|
+
matchesBashArgumentPattern(argumentPattern, candidate, overflowPolicy)
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
const argumentPatterns = isPermissionPathTool(toolName)
|
|
437
|
+
? permissionPathPatternVariants(pattern.argumentPattern, cwd)
|
|
438
|
+
: [pattern.argumentPattern];
|
|
439
|
+
const primaryArguments = getPrimaryArguments(
|
|
440
|
+
toolName,
|
|
441
|
+
input,
|
|
442
|
+
cwd,
|
|
443
|
+
overflowPolicy,
|
|
444
|
+
);
|
|
445
|
+
return argumentPatterns.some((argumentPattern) =>
|
|
446
|
+
primaryArguments.some((primary) =>
|
|
447
|
+
matchesWildcardPattern(argumentPattern, primary, overflowPolicy)
|
|
448
|
+
)
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Return the normalized Bash command that matched a scoped permission rule. */
|
|
453
|
+
export function matchingBashCommandText(
|
|
454
|
+
pattern: ToolPattern,
|
|
455
|
+
bashAnalysis: BashAnalysis | undefined,
|
|
456
|
+
overflowPolicy: WildcardOverflowPolicy = "match",
|
|
457
|
+
): string | undefined {
|
|
458
|
+
if (!bashAnalysis || pattern.toolName !== "bash") return undefined;
|
|
459
|
+
if (bashAnalysis.errors.length > 0) return undefined;
|
|
460
|
+
if (!pattern.argumentPattern) return undefined;
|
|
461
|
+
const argumentPattern = normalizedBashArgumentPattern(pattern);
|
|
462
|
+
const command = bashAnalysis.commands.find((candidate) =>
|
|
463
|
+
matchesBashArgumentPattern(argumentPattern, candidate.text, overflowPolicy)
|
|
464
|
+
);
|
|
465
|
+
if (command) return command.text;
|
|
466
|
+
return matchesBashArgumentPattern(argumentPattern, bashAnalysis.source, overflowPolicy)
|
|
467
|
+
? bashAnalysis.source
|
|
468
|
+
: undefined;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function redirectListsMatch(
|
|
472
|
+
patternRedirects: BashRedirectAnalysis[],
|
|
473
|
+
inputRedirects: BashRedirectAnalysis[],
|
|
474
|
+
): boolean {
|
|
475
|
+
if (patternRedirects.length !== inputRedirects.length) return false;
|
|
476
|
+
return patternRedirects.every((pattern, index) => {
|
|
477
|
+
const input = inputRedirects[index];
|
|
478
|
+
if (!input || pattern.heredoc || input.heredoc || input.targetDynamic) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
if (
|
|
482
|
+
pattern.operator !== input.operator ||
|
|
483
|
+
pattern.fileDescriptor !== input.fileDescriptor ||
|
|
484
|
+
pattern.variableName !== input.variableName
|
|
485
|
+
) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
if (pattern.target === undefined) return input.target === undefined;
|
|
489
|
+
if (input.target === undefined) return false;
|
|
490
|
+
return matchesWildcardPattern(pattern.target, input.target, "no-match");
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function commandMatchesAllowPattern(
|
|
495
|
+
patternCommand: BashCommandAnalysis,
|
|
496
|
+
inputCommand: BashCommandAnalysis,
|
|
497
|
+
): boolean {
|
|
498
|
+
return matchesWildcardPattern(
|
|
499
|
+
patternCommand.text,
|
|
500
|
+
inputCommand.text,
|
|
501
|
+
"no-match",
|
|
502
|
+
) && redirectListsMatch(patternCommand.redirects, inputCommand.redirects);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function structuresMatch(pattern: BashAnalysis, input: BashAnalysis): boolean {
|
|
506
|
+
return pattern.structure.length === input.structure.length &&
|
|
507
|
+
pattern.structure.every((token, index) => token === input.structure[index]);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function allRedirectsAreCommandRedirects(analysis: BashAnalysis): boolean {
|
|
511
|
+
return analysis.redirects.length === analysis.commands.reduce(
|
|
512
|
+
(count, command) => count + command.redirects.length,
|
|
513
|
+
0,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function isStructurallyPlainSingleCommand(analysis: BashAnalysis): boolean {
|
|
518
|
+
if (analysis.commands.length !== 1) return false;
|
|
519
|
+
if (analysis.structure.length !== 3 + analysis.redirects.length) return false;
|
|
520
|
+
if (analysis.structure[0] !== "script:1") return false;
|
|
521
|
+
if (analysis.structure[1] !== "node:Statement:foreground:0") return false;
|
|
522
|
+
if (!/^node:Command:\d+:\d+$/.test(analysis.structure[2] ?? "")) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
return analysis.structure.slice(3).every((token) =>
|
|
526
|
+
token.startsWith("redirect:")
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function supportsPerCommandAllowPatterns(analysis: BashAnalysis): boolean {
|
|
531
|
+
return analysis.structure.every((token) =>
|
|
532
|
+
token.startsWith("script:") ||
|
|
533
|
+
token.startsWith("node:Statement:foreground:") ||
|
|
534
|
+
token.startsWith("node:Command:") ||
|
|
535
|
+
token.startsWith("node:AndOr:") ||
|
|
536
|
+
token.startsWith("node:Pipeline:plain:plain:") ||
|
|
537
|
+
token.startsWith("redirect:")
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Whether permission allow rules cover the complete tool call. */
|
|
542
|
+
export function matchesAllowedToolPatterns(
|
|
543
|
+
patterns: ToolPattern[],
|
|
544
|
+
toolName: string,
|
|
545
|
+
input: Record<string, unknown>,
|
|
546
|
+
cwd: string,
|
|
547
|
+
bashAnalysis?: BashAnalysis,
|
|
548
|
+
): boolean {
|
|
549
|
+
if (toolName !== "bash" || !bashAnalysis) {
|
|
550
|
+
return patterns.some((pattern) =>
|
|
551
|
+
matchesToolPattern(pattern, toolName, input, cwd, "no-match")
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
if (
|
|
555
|
+
bashAnalysis.errors.length > 0 ||
|
|
556
|
+
bashAnalysis.commands.length === 0 ||
|
|
557
|
+
!bashAnalysis.allowStructureSafe
|
|
558
|
+
) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
if (
|
|
562
|
+
bashAnalysis.commands.some((command) =>
|
|
563
|
+
command.dynamicName || command.dynamicShellScript
|
|
564
|
+
)
|
|
565
|
+
) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
for (const pattern of patterns) {
|
|
570
|
+
if (pattern.toolName !== "bash") continue;
|
|
571
|
+
const patternAnalysis = bashPatternAnalyses.get(pattern);
|
|
572
|
+
if (
|
|
573
|
+
!patternAnalysis ||
|
|
574
|
+
patternAnalysis.errors.length > 0 ||
|
|
575
|
+
!patternAnalysis.allowStructureSafe ||
|
|
576
|
+
isStructurallyPlainSingleCommand(patternAnalysis) ||
|
|
577
|
+
patternAnalysis.commands.length !== bashAnalysis.commands.length ||
|
|
578
|
+
!structuresMatch(patternAnalysis, bashAnalysis) ||
|
|
579
|
+
!redirectListsMatch(patternAnalysis.redirects, bashAnalysis.redirects)
|
|
580
|
+
) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (
|
|
584
|
+
patternAnalysis.commands.every((patternCommand, index) => {
|
|
585
|
+
const inputCommand = bashAnalysis.commands[index];
|
|
586
|
+
return !!inputCommand &&
|
|
587
|
+
commandMatchesAllowPattern(patternCommand, inputCommand);
|
|
588
|
+
})
|
|
589
|
+
) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const hasBareBashPattern = patterns.some((pattern) =>
|
|
595
|
+
pattern.toolName === "bash" && pattern.argumentPattern === undefined
|
|
596
|
+
);
|
|
597
|
+
if (
|
|
598
|
+
!hasBareBashPattern &&
|
|
599
|
+
!supportsPerCommandAllowPatterns(bashAnalysis)
|
|
600
|
+
) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
if (!allRedirectsAreCommandRedirects(bashAnalysis)) return false;
|
|
604
|
+
return bashAnalysis.commands.every((command) =>
|
|
605
|
+
patterns.some((pattern) => {
|
|
606
|
+
if (pattern.toolName !== "bash") return false;
|
|
607
|
+
if (pattern.argumentPattern === undefined) {
|
|
608
|
+
return command.redirects.length === 0;
|
|
609
|
+
}
|
|
610
|
+
const patternAnalysis = bashPatternAnalyses.get(pattern);
|
|
611
|
+
if (
|
|
612
|
+
!patternAnalysis ||
|
|
613
|
+
patternAnalysis.errors.length > 0 ||
|
|
614
|
+
!isStructurallyPlainSingleCommand(patternAnalysis)
|
|
615
|
+
) {
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
const patternCommand = patternAnalysis.commands[0];
|
|
619
|
+
return !!patternCommand &&
|
|
620
|
+
commandMatchesAllowPattern(patternCommand, command);
|
|
621
|
+
})
|
|
622
|
+
);
|
|
301
623
|
}
|
package/extensions/auto-mode.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@czottmann/pi-automode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Claude Code-style auto mode guardrail for pi.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/czottmann/pi-automode"
|
|
@@ -54,5 +54,8 @@
|
|
|
54
54
|
"tsx": "^4.22.4",
|
|
55
55
|
"typebox": "^1.3.10",
|
|
56
56
|
"typescript": "^5.8.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"unbash": "4.0.10"
|
|
57
60
|
}
|
|
58
61
|
}
|