mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 14:03:49 +00:00
feat(extensions): add inline-bash example for expanding !{command} in prompts (#881)
* feat(extensions): add inline-bash example for expanding !{command} in prompts
Adds an example extension that expands inline bash commands within user
prompts before sending to the agent. Uses the `input` event to transform
patterns like `!{pwd}` or `!{git status}` into their output.
Preserves existing `!command` whole-line bash behavior.
* docs(extensions): add inline-bash to README
* chore: fix stupid bug
This commit is contained in:
parent
565488fde6
commit
82cc0fe866
3 changed files with 96 additions and 0 deletions
|
|
@ -63,6 +63,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|||
| `doom-overlay/` | DOOM game running as an overlay at 35 FPS (demonstrates real-time game rendering) |
|
||||
| `shutdown-command.ts` | Adds `/quit` command demonstrating `ctx.shutdown()` |
|
||||
| `interactive-shell.ts` | Run interactive commands (vim, htop) with full terminal via `user_bash` hook |
|
||||
| `inline-bash.ts` | Expands `!{command}` patterns in prompts via `input` event transformation |
|
||||
|
||||
### Git Integration
|
||||
|
||||
|
|
|
|||
94
packages/coding-agent/examples/extensions/inline-bash.ts
Normal file
94
packages/coding-agent/examples/extensions/inline-bash.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Inline Bash Extension - expands inline bash commands in user prompts.
|
||||
*
|
||||
* Start pi with this extension:
|
||||
* pi -e ./examples/extensions/inline-bash.ts
|
||||
*
|
||||
* Then type prompts with inline bash:
|
||||
* What's in !{pwd}?
|
||||
* The current branch is !{git branch --show-current} and status: !{git status --short}
|
||||
* My node version is !{node --version}
|
||||
*
|
||||
* The !{command} patterns are executed and replaced with their output before
|
||||
* the prompt is sent to the agent.
|
||||
*
|
||||
* Note: Regular !command syntax (whole-line bash) is preserved and works as before.
|
||||
*/
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
const PATTERN = /!\{([^}]+)\}/g;
|
||||
const TIMEOUT_MS = 30000;
|
||||
|
||||
pi.on("input", async (event, ctx) => {
|
||||
const text = event.text;
|
||||
|
||||
// Don't process if it's a whole-line bash command (starts with !)
|
||||
// This preserves the existing !command behavior
|
||||
if (text.trimStart().startsWith("!") && !text.trimStart().startsWith("!{")) {
|
||||
return { action: "continue" };
|
||||
}
|
||||
|
||||
// Check if there are any inline bash patterns
|
||||
if (!PATTERN.test(text)) {
|
||||
return { action: "continue" };
|
||||
}
|
||||
|
||||
// Reset regex state after test()
|
||||
PATTERN.lastIndex = 0;
|
||||
|
||||
let result = text;
|
||||
const expansions: Array<{ command: string; output: string; error?: string }> = [];
|
||||
|
||||
// Find all matches first (to avoid issues with replacing while iterating)
|
||||
const matches: Array<{ full: string; command: string }> = [];
|
||||
let match = PATTERN.exec(text);
|
||||
while (match) {
|
||||
matches.push({ full: match[0], command: match[1] });
|
||||
match = PATTERN.exec(text);
|
||||
}
|
||||
|
||||
// Execute each command and collect results
|
||||
for (const { full, command } of matches) {
|
||||
try {
|
||||
const bashResult = await pi.exec("bash", ["-c", command], {
|
||||
timeout: TIMEOUT_MS,
|
||||
});
|
||||
|
||||
const output = bashResult.stdout || bashResult.stderr || "";
|
||||
const trimmed = output.trim();
|
||||
|
||||
if (bashResult.code !== 0 && bashResult.stderr) {
|
||||
expansions.push({
|
||||
command,
|
||||
output: trimmed,
|
||||
error: `exit code ${bashResult.code}`,
|
||||
});
|
||||
} else {
|
||||
expansions.push({ command, output: trimmed });
|
||||
}
|
||||
|
||||
result = result.replace(full, trimmed);
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
expansions.push({ command, output: "", error: errorMsg });
|
||||
result = result.replace(full, `[error: ${errorMsg}]`);
|
||||
}
|
||||
}
|
||||
|
||||
// Show what was expanded (if UI available)
|
||||
if (ctx.hasUI && expansions.length > 0) {
|
||||
const summary = expansions
|
||||
.map((e) => {
|
||||
const status = e.error ? ` (${e.error})` : "";
|
||||
const preview = e.output.length > 50 ? `${e.output.slice(0, 50)}...` : e.output;
|
||||
return `!{${e.command}}${status} -> "${preview}"`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
ctx.ui.notify(`Expanded ${expansions.length} inline command(s):\n${summary}`, "info");
|
||||
}
|
||||
|
||||
return { action: "transform", text: result, images: event.images };
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue