mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 16:04:03 +00:00
* 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
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
/**
|
|
* 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 };
|
|
});
|
|
}
|