@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.
Files changed (3) hide show
  1. package/README.md +54 -0
  2. package/index.ts +107 -0
  3. 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
+ }