@diegopetrucci/pi-inline-bash 0.1.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/README.md +54 -0
- package/index.ts +107 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# inline-bash
|
|
2
|
+
|
|
3
|
+
A pi extension that expands inline bash commands in user prompts before they are sent to the agent.
|
|
4
|
+
|
|
5
|
+
This started from the original `inline-bash.ts` example in [`earendil-works/pi-mono`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/examples/extensions/inline-bash.ts), with small packaging and safety tweaks.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Write inline commands with `!{...}`:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
What's in !{pwd}?
|
|
13
|
+
The current branch is !{git branch --show-current} and status: !{git status --short}
|
|
14
|
+
My node version is !{node --version}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The extension runs each command and replaces the `!{command}` pattern with trimmed stdout or stderr. Each expansion is capped at 50,000 characters.
|
|
18
|
+
|
|
19
|
+
Whole-line `!command` syntax is left alone so pi's built-in shell-command behavior still works.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
### Standalone npm package
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pi install npm:@diegopetrucci/pi-inline-bash
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Collection package
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pi install npm:@diegopetrucci/pi-extensions
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### GitHub package
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pi install git:github.com/diegopetrucci/pi-extensions
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then reload pi:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
/reload
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- Hooks the `input` event.
|
|
50
|
+
- Inline commands run through `bash -c` with a 30-second timeout.
|
|
51
|
+
- Extension-injected follow-up messages are ignored to avoid implicit shell execution from other extensions.
|
|
52
|
+
- In interactive mode, pi shows a notification summarizing the expanded commands.
|
|
53
|
+
- Treat prompt text containing `!{...}` as shell code; only use this extension where prompt authors are trusted.
|
|
54
|
+
- `permission-gate` does not intercept these user-prompt expansions because they run before agent tool calls.
|
package/index.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline Bash Extension - expands inline bash commands in user prompts.
|
|
3
|
+
*
|
|
4
|
+
* Start pi with this extension:
|
|
5
|
+
* pi -e ./examples/extensions/inline-bash.ts
|
|
6
|
+
*
|
|
7
|
+
* Then type prompts with inline bash:
|
|
8
|
+
* What's in !{pwd}?
|
|
9
|
+
* The current branch is !{git branch --show-current} and status: !{git status --short}
|
|
10
|
+
* My node version is !{node --version}
|
|
11
|
+
*
|
|
12
|
+
* The !{command} patterns are executed and replaced with their trimmed output
|
|
13
|
+
* before the prompt is sent to the agent. Very large expansions are truncated.
|
|
14
|
+
*
|
|
15
|
+
* Note: Regular !command syntax (whole-line bash) is preserved and works as before.
|
|
16
|
+
*/
|
|
17
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
|
|
19
|
+
export default function (pi: ExtensionAPI) {
|
|
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
|
+
// Don't process if it's a whole-line bash command (starts with !)
|
|
40
|
+
// This preserves the existing !command behavior
|
|
41
|
+
if (text.trimStart().startsWith("!") && !text.trimStart().startsWith("!{")) {
|
|
42
|
+
return { action: "continue" };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if there are any inline bash patterns
|
|
46
|
+
if (!PATTERN.test(text)) {
|
|
47
|
+
return { action: "continue" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Reset regex state after test()
|
|
51
|
+
PATTERN.lastIndex = 0;
|
|
52
|
+
|
|
53
|
+
let result = text;
|
|
54
|
+
const expansions: Array<{ command: string; output: string; error?: string }> = [];
|
|
55
|
+
|
|
56
|
+
// Find all matches first (to avoid issues with replacing while iterating)
|
|
57
|
+
const matches: Array<{ full: string; command: string }> = [];
|
|
58
|
+
let match = PATTERN.exec(text);
|
|
59
|
+
while (match) {
|
|
60
|
+
matches.push({ full: match[0], command: match[1] });
|
|
61
|
+
match = PATTERN.exec(text);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Execute each command and collect results
|
|
65
|
+
for (const { full, command } of matches) {
|
|
66
|
+
try {
|
|
67
|
+
const bashResult = await pi.exec("bash", ["-c", command], {
|
|
68
|
+
timeout: TIMEOUT_MS,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const output = bashResult.stdout || bashResult.stderr || "";
|
|
72
|
+
const trimmed = trimAndLimit(output);
|
|
73
|
+
|
|
74
|
+
if (bashResult.code !== 0 && bashResult.stderr) {
|
|
75
|
+
expansions.push({
|
|
76
|
+
command,
|
|
77
|
+
output: trimmed,
|
|
78
|
+
error: `exit code ${bashResult.code}`,
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
expansions.push({ command, output: trimmed });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
result = result.replace(full, trimmed);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
87
|
+
expansions.push({ command, output: "", error: errorMsg });
|
|
88
|
+
result = result.replace(full, `[error: ${errorMsg}]`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Show what was expanded (if UI available)
|
|
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
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@diegopetrucci/pi-inline-bash",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A pi extension that expands inline bash commands in user prompts.",
|
|
5
|
+
"keywords": ["pi-package", "pi", "bash", "input", "prompt"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/diegopetrucci/pi-extensions.git",
|
|
10
|
+
"directory": "extensions/inline-bash"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.ts",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"pi": {
|
|
20
|
+
"extensions": [
|
|
21
|
+
"index.ts"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
26
|
+
}
|
|
27
|
+
}
|