@diegopetrucci/pi-extensions 0.1.73 → 0.1.76
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/LICENSE +2 -0
- package/README.md +9 -4
- package/extensions/agent-workflow-audit/index.ts +1398 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +853 -767
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +1 -1
- package/extensions/code-reviewer/index.ts +1598 -1350
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +55 -51
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/index.ts +312 -304
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/index.ts +1854 -1735
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +41 -41
- package/extensions/dynamic-context-pruning/README.md +6 -38
- package/extensions/dynamic-context-pruning/index.ts +2124 -1980
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +71 -21
- package/extensions/fast/index.ts +513 -313
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +88 -88
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/index.ts +1669 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/index.ts +994 -1012
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/index.ts +1731 -1625
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/index.ts +658 -608
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/index.ts +282 -264
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +284 -265
- package/extensions/triage-comments/README.md +2 -2
- package/extensions/triage-comments/index.ts +2294 -1882
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +6 -3
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-git-footer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "A TLH-style git status add-on for pi's built-in footer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
]
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
33
|
-
"@earendil-works/pi-tui": "*"
|
|
32
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
34
33
|
},
|
|
35
34
|
"type": "module",
|
|
36
35
|
"engines": {
|
|
@@ -1,159 +1,209 @@
|
|
|
1
1
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
DEFAULT_MAX_BYTES,
|
|
4
|
+
DEFAULT_MAX_LINES,
|
|
5
|
+
formatSize,
|
|
6
|
+
truncateTail,
|
|
7
|
+
type ExtensionAPI,
|
|
8
|
+
type ToolExecutionMode,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { Type, type Static } from "typebox";
|
|
11
11
|
|
|
12
12
|
const GNOSIS_TIMEOUT_MS = 30_000;
|
|
13
13
|
|
|
14
14
|
const GnosisParams = Type.Object({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
action: StringEnum(
|
|
16
|
+
["plan", "review", "search", "latest", "show", "topics", "write", "reindex"] as const,
|
|
17
|
+
{
|
|
18
|
+
description: "The gnosis operation to run.",
|
|
19
|
+
},
|
|
20
|
+
),
|
|
21
|
+
query: Type.Optional(
|
|
22
|
+
Type.String({
|
|
23
|
+
minLength: 1,
|
|
24
|
+
description: "Search query for action=search. Use uppercase OR/NOT for FTS operators.",
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
target: Type.Optional(
|
|
28
|
+
Type.String({ minLength: 1, description: "Entry ID prefix or topic for action=show." }),
|
|
29
|
+
),
|
|
30
|
+
topics: Type.Optional(
|
|
31
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
32
|
+
minItems: 1,
|
|
33
|
+
description: "Topics for action=write. Each normalized topic must be at least 7 chars.",
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
text: Type.Optional(
|
|
37
|
+
Type.String({ minLength: 1, description: "Knowledge text for action=write." }),
|
|
38
|
+
),
|
|
39
|
+
related: Type.Optional(
|
|
40
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
41
|
+
description: "Related entry IDs or unique prefixes for action=write.",
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
limit: Type.Optional(
|
|
45
|
+
Type.Integer({
|
|
46
|
+
minimum: 1,
|
|
47
|
+
description: "Positive result limit for action=search or action=latest.",
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
31
50
|
});
|
|
32
51
|
|
|
33
52
|
type GnosisParams = Static<typeof GnosisParams>;
|
|
34
53
|
|
|
35
54
|
function positiveInteger(value: number | undefined, name: string): string | undefined {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
if (value === undefined) return undefined;
|
|
56
|
+
if (!Number.isInteger(value) || value <= 0)
|
|
57
|
+
throw new Error(`${name} must be a positive integer.`);
|
|
58
|
+
return String(value);
|
|
39
59
|
}
|
|
40
60
|
|
|
41
61
|
function requireString(value: string | undefined, name: string): string {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
const trimmed = value?.trim();
|
|
63
|
+
if (!trimmed) throw new Error(`${name} is required.`);
|
|
64
|
+
return trimmed;
|
|
45
65
|
}
|
|
46
66
|
|
|
47
67
|
function buildGnosisArgs(params: GnosisParams): string[] {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
switch (params.action) {
|
|
69
|
+
case "plan":
|
|
70
|
+
return ["help", "plan"];
|
|
71
|
+
case "review":
|
|
72
|
+
return ["help", "review"];
|
|
73
|
+
case "search": {
|
|
74
|
+
const args = ["search", requireString(params.query, "query")];
|
|
75
|
+
const limit = positiveInteger(params.limit, "limit");
|
|
76
|
+
if (limit) args.push("--limit", limit);
|
|
77
|
+
return args;
|
|
78
|
+
}
|
|
79
|
+
case "latest": {
|
|
80
|
+
const args = ["latest"];
|
|
81
|
+
const limit = positiveInteger(params.limit, "limit");
|
|
82
|
+
if (limit) args.push("--limit", limit);
|
|
83
|
+
return args;
|
|
84
|
+
}
|
|
85
|
+
case "show":
|
|
86
|
+
return ["show", requireString(params.target, "target")];
|
|
87
|
+
case "topics":
|
|
88
|
+
return ["topics"];
|
|
89
|
+
case "write": {
|
|
90
|
+
const topics = params.topics?.map((topic) => topic.trim()).filter(Boolean) ?? [];
|
|
91
|
+
if (topics.length === 0) throw new Error("topics is required for action=write.");
|
|
92
|
+
const args = ["write", topics.join(","), requireString(params.text, "text")];
|
|
93
|
+
const related = params.related?.map((id) => id.trim()).filter(Boolean) ?? [];
|
|
94
|
+
if (related.length > 0) args.push("--related", related.join(","));
|
|
95
|
+
return args;
|
|
96
|
+
}
|
|
97
|
+
case "reindex":
|
|
98
|
+
return ["reindex"];
|
|
99
|
+
default: {
|
|
100
|
+
const exhaustive: never = params.action;
|
|
101
|
+
throw new Error(`Unsupported gnosis action: ${exhaustive}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
function installHint(): string {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
return [
|
|
108
|
+
"The `gn` CLI is required for the gnosis extension.",
|
|
109
|
+
"Install it with one of:",
|
|
110
|
+
" brew install --cask skorokithakis/tap/gnosis",
|
|
111
|
+
" go install github.com/skorokithakis/gnosis/cmd/gn@latest",
|
|
112
|
+
].join("\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isEnoent(error: unknown): boolean {
|
|
116
|
+
return !!error && typeof error === "object" && (error as { code?: unknown }).code === "ENOENT";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isMissingBinaryFailure(
|
|
120
|
+
error: unknown,
|
|
121
|
+
outputText = "",
|
|
122
|
+
exitCode?: number | null,
|
|
123
|
+
): boolean {
|
|
124
|
+
if (exitCode === 127 || isEnoent(error)) return true;
|
|
125
|
+
const message = error instanceof Error ? error.message : error == null ? "" : String(error);
|
|
126
|
+
const text = `${message}\n${outputText}`;
|
|
127
|
+
return (
|
|
128
|
+
/\bENOENT\b/.test(text) ||
|
|
129
|
+
/command not found/i.test(text) ||
|
|
130
|
+
/not recognized as an internal or external command/i.test(text) ||
|
|
131
|
+
/(?:^|[\s:`])gn(?:\.exe)?:\s*not found\b/im.test(text)
|
|
132
|
+
);
|
|
93
133
|
}
|
|
94
134
|
|
|
95
135
|
function formatOutput(stdout: string, stderr: string): { text: string; truncated: boolean } {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
const raw = [stdout, stderr]
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
.join(stderr && stdout ? "\n" : "")
|
|
139
|
+
.trimEnd();
|
|
140
|
+
if (!raw) return { text: "(no output)", truncated: false };
|
|
141
|
+
|
|
142
|
+
const truncation = truncateTail(raw, {
|
|
143
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
144
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
let text = truncation.content;
|
|
148
|
+
if (truncation.truncated) {
|
|
149
|
+
text += `\n\n[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines`;
|
|
150
|
+
text += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).]`;
|
|
151
|
+
}
|
|
152
|
+
return { text, truncated: truncation.truncated };
|
|
110
153
|
}
|
|
111
154
|
|
|
112
155
|
export default function gnosisExtension(pi: ExtensionAPI) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
pi.registerTool({
|
|
157
|
+
name: "gnosis",
|
|
158
|
+
label: "Gnosis",
|
|
159
|
+
description:
|
|
160
|
+
"Search and record repo-local project knowledge using the `gn` CLI. Supports plan/review doctrine, search, latest, show, topics, write, and reindex. Does not support edit/rm; use shell only with explicit user intent for destructive gnosis maintenance.",
|
|
161
|
+
executionMode: "sequential" as ToolExecutionMode,
|
|
162
|
+
promptSnippet: "Search and record repo-local project knowledge through gnosis (`gn`).",
|
|
163
|
+
promptGuidelines: [
|
|
164
|
+
"Use the gnosis tool before implementing changes that may touch prior architecture, project decisions, rejected alternatives, or human constraints.",
|
|
165
|
+
'Use gnosis with action="search" and uppercase OR between likely topic keywords, for example `auth OR token OR session`.',
|
|
166
|
+
'Use gnosis with action="write" only for durable, non-obvious project knowledge that is not already captured in code, comments, docs, or the commit message.',
|
|
167
|
+
'Prefer code comments over gnosis with action="write" when the knowledge has an obvious specific code anchor.',
|
|
168
|
+
],
|
|
169
|
+
parameters: GnosisParams,
|
|
170
|
+
|
|
171
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
172
|
+
const args = buildGnosisArgs(params);
|
|
173
|
+
|
|
174
|
+
let result: Awaited<ReturnType<typeof pi.exec>>;
|
|
175
|
+
try {
|
|
176
|
+
result = await pi.exec("gn", args, {
|
|
177
|
+
cwd: ctx.cwd,
|
|
178
|
+
signal,
|
|
179
|
+
timeout: GNOSIS_TIMEOUT_MS,
|
|
180
|
+
});
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
+
const hint = isMissingBinaryFailure(error) ? `${installHint()}\n\n` : "";
|
|
184
|
+
throw new Error(`${hint}Execution error: ${message}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const output = formatOutput(result.stdout ?? "", result.stderr ?? "");
|
|
188
|
+
if (result.killed)
|
|
189
|
+
throw new Error(`gnosis ${params.action} timed out or was cancelled.\n\n${output.text}`);
|
|
190
|
+
if (result.code !== 0) {
|
|
191
|
+
const hint = isMissingBinaryFailure(undefined, output.text, result.code)
|
|
192
|
+
? `\n\n${installHint()}`
|
|
193
|
+
: "";
|
|
194
|
+
throw new Error(
|
|
195
|
+
`gnosis ${params.action} failed with exit code ${result.code}.\n\n${output.text}${hint}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: "text", text: output.text }],
|
|
201
|
+
details: {
|
|
202
|
+
action: params.action,
|
|
203
|
+
args,
|
|
204
|
+
truncated: output.truncated,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
});
|
|
159
209
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Inline Bash Extension - expands inline bash commands in user prompts.
|
|
3
3
|
*
|
|
4
4
|
* Start pi with this extension:
|
|
5
|
-
* pi -e ./
|
|
5
|
+
* pi -e ./extensions/inline-bash/index.ts
|
|
6
6
|
*
|
|
7
7
|
* Then type prompts with inline bash:
|
|
8
8
|
* What's in !{pwd}?
|
|
@@ -17,91 +17,91 @@
|
|
|
17
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
|
|
19
19
|
export default function (pi: ExtensionAPI) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
20
|
+
const PATTERN = /!\{([^}]+)\}/g;
|
|
21
|
+
const TIMEOUT_MS = 30000;
|
|
22
|
+
const MAX_OUTPUT_CHARS = 50000;
|
|
23
|
+
|
|
24
|
+
const trimAndLimit = (output: string) => {
|
|
25
|
+
const trimmed = output.trim();
|
|
26
|
+
if (trimmed.length <= MAX_OUTPUT_CHARS) return trimmed;
|
|
27
|
+
return `${trimmed.slice(0, MAX_OUTPUT_CHARS)}\n[inline-bash output truncated after ${MAX_OUTPUT_CHARS} characters]`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
pi.on("input", async (event, ctx) => {
|
|
31
|
+
const text = event.text;
|
|
32
|
+
|
|
33
|
+
// Only expand prompts supplied by the user/client. Extension-injected follow-ups
|
|
34
|
+
// should not get an implicit path to local shell execution.
|
|
35
|
+
if (event.source === "extension") {
|
|
36
|
+
return { action: "continue" };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (text.trimStart().startsWith("!") && !text.trimStart().startsWith("!{")) {
|
|
40
|
+
return { action: "continue" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
PATTERN.lastIndex = 0;
|
|
44
|
+
const matches: Array<{ start: number; end: number; command: string }> = [];
|
|
45
|
+
let match = PATTERN.exec(text);
|
|
46
|
+
while (match) {
|
|
47
|
+
matches.push({
|
|
48
|
+
start: match.index,
|
|
49
|
+
end: match.index + match[0].length,
|
|
50
|
+
command: match[1],
|
|
51
|
+
});
|
|
52
|
+
match = PATTERN.exec(text);
|
|
53
|
+
}
|
|
54
|
+
if (matches.length === 0) {
|
|
55
|
+
return { action: "continue" };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const parts: string[] = [];
|
|
59
|
+
let cursor = 0;
|
|
60
|
+
const expansions: Array<{ command: string; output: string; error?: string }> = [];
|
|
61
|
+
|
|
62
|
+
for (const { start, end, command } of matches) {
|
|
63
|
+
parts.push(text.slice(cursor, start));
|
|
64
|
+
cursor = end;
|
|
65
|
+
try {
|
|
66
|
+
const bashResult = await pi.exec("bash", ["-c", command], {
|
|
67
|
+
timeout: TIMEOUT_MS,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const output = bashResult.stdout || bashResult.stderr || "";
|
|
71
|
+
const trimmed = trimAndLimit(output);
|
|
72
|
+
|
|
73
|
+
if (bashResult.code !== 0 && bashResult.stderr) {
|
|
74
|
+
expansions.push({
|
|
75
|
+
command,
|
|
76
|
+
output: trimmed,
|
|
77
|
+
error: `exit code ${bashResult.code}`,
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
expansions.push({ command, output: trimmed });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
parts.push(trimmed);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
86
|
+
expansions.push({ command, output: "", error: errorMsg });
|
|
87
|
+
parts.push(`[error: ${errorMsg}]`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
parts.push(text.slice(cursor));
|
|
91
|
+
const result = parts.join("");
|
|
92
|
+
|
|
93
|
+
if (ctx.hasUI && expansions.length > 0) {
|
|
94
|
+
const summary = expansions
|
|
95
|
+
.map((e) => {
|
|
96
|
+
const status = e.error ? ` (${e.error})` : "";
|
|
97
|
+
const preview = e.output.length > 50 ? `${e.output.slice(0, 50)}...` : e.output;
|
|
98
|
+
return `!{${e.command}}${status} -> "${preview}"`;
|
|
99
|
+
})
|
|
100
|
+
.join("\n");
|
|
101
|
+
|
|
102
|
+
ctx.ui.notify(`Expanded ${expansions.length} inline command(s):\n${summary}`, "info");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { action: "transform", text: result, images: event.images };
|
|
106
|
+
});
|
|
107
107
|
}
|