@trim21/personal-pi-extensions 0.0.325 → 0.0.329
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/package.json +1 -1
- package/src/aft/tools.ts +49 -1
- package/src/bwrap/approval-rules.ts +15 -13
- package/src/lib/pendant.ts +8 -0
package/package.json
CHANGED
package/src/aft/tools.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
9
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
type AftProjectTransport,
|
|
@@ -32,6 +32,19 @@ export function resolvePathArg(cwd: string, input: string): string {
|
|
|
32
32
|
return isAbsolute(input) ? input : resolve(cwd, input);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** 人类可读的显示路径:cwd 内用 `./…`,home 内用 `~/…`,否则原样绝对路径。 */
|
|
36
|
+
export function formatDisplayPath(cwd: string, filePath: string): string {
|
|
37
|
+
const relToCwd = relative(cwd, filePath);
|
|
38
|
+
if (relToCwd !== "" && !relToCwd.startsWith("..") && !isAbsolute(relToCwd)) {
|
|
39
|
+
return `./${relToCwd}`;
|
|
40
|
+
}
|
|
41
|
+
const relToHome = relative(homedir(), filePath);
|
|
42
|
+
if (relToHome !== "" && !relToHome.startsWith("..") && !isAbsolute(relToHome)) {
|
|
43
|
+
return `~/${relToHome}`;
|
|
44
|
+
}
|
|
45
|
+
return filePath;
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
export interface AftToolContext {
|
|
36
49
|
cwd: string;
|
|
37
50
|
pool: AftTransportPool;
|
|
@@ -222,6 +235,8 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
|
|
|
222
235
|
if (contextLines !== undefined) rawArgs.contextLines = contextLines;
|
|
223
236
|
if (coerceBoolean(params.callgraph)) rawArgs.callgraph = true;
|
|
224
237
|
|
|
238
|
+
const subtitle = buildZoomSubtitle(extCtx.cwd, params);
|
|
239
|
+
|
|
225
240
|
const { text, response } = await callAftTool(bridgeFor(ctx), "zoom", rawArgs, extCtx);
|
|
226
241
|
const truncated = response.truncated === true;
|
|
227
242
|
return {
|
|
@@ -229,6 +244,8 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
|
|
|
229
244
|
details: {
|
|
230
245
|
truncated,
|
|
231
246
|
pendant: {
|
|
247
|
+
title: "aft_zoom",
|
|
248
|
+
subtitle,
|
|
232
249
|
markdown: buildPendantMarkdown({
|
|
233
250
|
title: "aft_zoom",
|
|
234
251
|
input: params,
|
|
@@ -243,6 +260,37 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
|
|
|
243
260
|
});
|
|
244
261
|
}
|
|
245
262
|
|
|
263
|
+
/** 构建 aft_zoom pendant 的 subtitle:`path="…" symbol="…"`,url 模式为 `url="…"`。 */
|
|
264
|
+
export function buildZoomSubtitle(
|
|
265
|
+
cwd: string,
|
|
266
|
+
params: Type.Static<typeof ZoomParams>,
|
|
267
|
+
): string | undefined {
|
|
268
|
+
const isEmpty = (v: unknown): boolean =>
|
|
269
|
+
v === undefined || v === null || v === "" || (Array.isArray(v) && v.length === 0);
|
|
270
|
+
|
|
271
|
+
const parts: string[] = [];
|
|
272
|
+
const targets = params.targets;
|
|
273
|
+
if (Array.isArray(targets)) {
|
|
274
|
+
for (const t of targets) {
|
|
275
|
+
parts.push(
|
|
276
|
+
`path="${formatDisplayPath(cwd, resolvePathArg(cwd, t.path))}" symbol="${t.symbol}"`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
} else if (targets) {
|
|
280
|
+
parts.push(
|
|
281
|
+
`path="${formatDisplayPath(cwd, resolvePathArg(cwd, targets.path))}" symbol="${targets.symbol}"`,
|
|
282
|
+
);
|
|
283
|
+
} else if (!isEmpty(params.url)) {
|
|
284
|
+
parts.push(`url="${params.url}"`);
|
|
285
|
+
} else if (params.path) {
|
|
286
|
+
const symbols = params.symbols;
|
|
287
|
+
const symbolStr = Array.isArray(symbols) ? symbols.join(", ") : symbols;
|
|
288
|
+
const pathPart = `path="${formatDisplayPath(cwd, resolvePathArg(cwd, params.path))}"`;
|
|
289
|
+
parts.push(symbolStr ? `${pathPart} symbol="${symbolStr}"` : pathPart);
|
|
290
|
+
}
|
|
291
|
+
return parts.length > 0 ? parts.join(" ") : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
246
294
|
const CALLGRAPH_OPS = [
|
|
247
295
|
"call_tree",
|
|
248
296
|
"callers",
|
|
@@ -209,21 +209,23 @@ export async function commandPatternsFor(command: string): Promise<string[]> {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
212
|
+
* 对命令(含所有嵌套命令)求值:
|
|
213
|
+
* - deny 优先:任一命令命中 deny 规则即整体拒绝
|
|
214
|
+
* - allow 需全量:所有命令都命中 allow 规则才整体放行,否则返回
|
|
215
|
+
* undefined(有命令未命中规则,交给人审),避免未允许的命令被同链放行带过。
|
|
216
|
+
* 规则内后写优先(findLast,对齐 opencode PermissionV2)。
|
|
215
217
|
*/
|
|
216
|
-
export function evaluateBashApproval(
|
|
218
|
+
export async function evaluateBashApproval(
|
|
217
219
|
command: string,
|
|
218
220
|
rules: readonly ApprovalRule[],
|
|
219
221
|
): Promise<ApprovalAction | undefined> {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
const patterns = await commandPatternsFor(command);
|
|
223
|
+
if (patterns.length === 0) return;
|
|
224
|
+
let allowed = 0;
|
|
225
|
+
for (const pattern of patterns) {
|
|
226
|
+
const rule = rules.findLast((r) => matchRule(pattern, r.pattern));
|
|
227
|
+
if (rule?.action === "deny") return "deny";
|
|
228
|
+
if (rule?.action === "allow") allowed++;
|
|
229
|
+
}
|
|
230
|
+
return allowed === patterns.length ? "allow" : undefined;
|
|
229
231
|
}
|