Compare commits

...
3 Commits
Author SHA1 Message Date
tgrosinger fccb75298e Atuin: Install hooks for Claude Code and Pi 2026-08-19 14:37:28 -07:00
tgrosinger 3208abe464 Claude: Allow tn and Atrium access 2026-08-19 14:37:05 -07:00
tgrosinger 08c7ca752a Claude: Reordering config file 2026-08-19 14:36:49 -07:00
2 changed files with 155 additions and 13 deletions
+50 -13
View File
@@ -33,7 +33,32 @@
], ],
"defaultMode": "auto" "defaultMode": "auto"
}, },
"model": "opus[1m]",
"disableClaudeAiConnectors": true,
"remoteControlAtStartup": false,
"hooks": { "hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "atuin hook claude-code"
}
]
}
],
"PostToolUseFailure": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "atuin hook claude-code"
}
]
}
],
"PreToolUse": [ "PreToolUse": [
{ {
"matcher": "Bash", "matcher": "Bash",
@@ -47,22 +72,33 @@
"command": "~/.claude/hooks/block-file-deletion.sh" "command": "~/.claude/hooks/block-file-deletion.sh"
} }
] ]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "atuin hook claude-code"
}
]
} }
] ]
}, },
"disableWorkflows": true,
"disableArtifact": true,
"statusLine": { "statusLine": {
"type": "command", "type": "command",
"command": "bash /home/tgrosinger/.claude/statusline-command.sh" "command": "bash /home/tgrosinger/.claude/statusline-command.sh"
}, },
"enabledPlugins": { "enabledPlugins": {
"pr-review-toolkit@claude-plugins-official": true,
"gopls-lsp@claude-plugins-official": true,
"frontend-design@claude-plugins-official": true,
"code-simplifier@claude-plugins-official": true,
"skill-creator@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true,
"claude-md-management@claude-plugins-official": true, "claude-md-management@claude-plugins-official": true,
"security-guidance@claude-plugins-official": true "code-simplifier@claude-plugins-official": true,
"frontend-design@claude-plugins-official": true,
"gopls-lsp@claude-plugins-official": true,
"pr-review-toolkit@claude-plugins-official": true,
"security-guidance@claude-plugins-official": true,
"skill-creator@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true
}, },
"sandbox": { "sandbox": {
"enabled": true, "enabled": true,
@@ -86,19 +122,23 @@
"filesystem": { "filesystem": {
"allowWrite": [ "allowWrite": [
"~/.local/share/pnpm", "~/.local/share/pnpm",
"~/.cache/pnpm" "~/.cache/pnpm",
"~/Documents/Atrium"
], ],
"denyRead": [ "denyRead": [
"~/.ssh", "~/.ssh",
"~/.config/Signal", "~/.config/Signal",
"~/Documents" "~/Documents"
],
"allowRead": [
"~/Documents/Atrium"
] ]
}, },
"excludedCommands": [ "excludedCommands": [
"git push *", "git push *",
"brew *", "brew *",
"devbox add *", "tn *",
"nix *" "obsidian *"
] ]
}, },
"spinnerVerbs": { "spinnerVerbs": {
@@ -119,8 +159,5 @@
"theme": "light", "theme": "light",
"editorMode": "vim", "editorMode": "vim",
"agentPushNotifEnabled": true, "agentPushNotifEnabled": true,
"disableClaudeAiConnectors": true,
"disableWorkflows": true,
"disableArtifact": true,
"skipAutoPermissionPrompt": true "skipAutoPermissionPrompt": true
} }
+105
View File
@@ -0,0 +1,105 @@
/**
* Atuin extension for pi.
*
* Tracks bash commands executed by pi in Atuin history with author `pi`.
*
* Install with:
* atuin hook install pi
*
* Then restart pi or run /reload.
*/
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
const ATUIN_AUTHOR = "pi";
const ATUIN_TIMEOUT_MS = 10_000;
async function startHistory(
pi: ExtensionAPI,
cwd: string,
command: string,
): Promise<string | undefined> {
try {
const result = await pi.exec(
"atuin",
["history", "start", "--author", ATUIN_AUTHOR, "--", command],
{ cwd, timeout: ATUIN_TIMEOUT_MS },
);
if (result.code !== 0) return undefined;
const id = result.stdout.trim();
return id.length > 0 ? id : undefined;
} catch {
return undefined;
}
}
async function endHistory(
pi: ExtensionAPI,
cwd: string,
historyId: string,
exitCode: number,
): Promise<void> {
try {
await pi.exec(
"atuin",
["history", "end", historyId, "--exit", String(exitCode)],
{ cwd, timeout: ATUIN_TIMEOUT_MS },
);
} catch {
// Ignore Atuin failures so command execution is never blocked.
}
}
// The bash tool reports failures by appending a status line to the result
// text rather than exposing a numeric exit code, so recover it from there.
function exitCodeFromResult(result: unknown, isError: boolean): number {
if (!isError) return 0;
const content = (result as { content?: unknown } | undefined)?.content;
const text = Array.isArray(content)
? content
.map((part) => {
const t = (part as { text?: unknown } | undefined)?.text;
return typeof t === "string" ? t : "";
})
.join("\n")
: "";
const exited = text.match(/Command exited with code (\d+)\s*$/);
if (exited) return Number(exited[1]);
if (/Command aborted\s*$/.test(text)) return 130;
if (/Command timed out after \S+ seconds\s*$/.test(text)) return 124;
return 1;
}
export default function atuinPiExtension(pi: ExtensionAPI) {
// Atuin history IDs for in-flight bash tool calls, keyed by tool call ID.
const pending = new Map<string, string>();
// Observe bash executions through events instead of registering a bash
// tool: registering one conflicts with other extensions that provide
// their own bash tool (sandboxes, RTK, remote runners), while events
// fire no matter which extension's bash tool ends up executing the
// command.
pi.on("tool_call", async (event, ctx: ExtensionContext) => {
if (event.toolName !== "bash") return;
const command = (event.input as { command?: unknown }).command;
if (typeof command !== "string" || command.length === 0) return;
const historyId = await startHistory(pi, ctx.cwd, command);
if (historyId) pending.set(event.toolCallId, historyId);
});
// tool_execution_end also fires when another extension blocks the call,
// unlike tool_result, so entries started above are always closed.
pi.on("tool_execution_end", async (event, ctx: ExtensionContext) => {
const historyId = pending.get(event.toolCallId);
if (!historyId) return;
pending.delete(event.toolCallId);
await endHistory(pi, ctx.cwd, historyId, exitCodeFromResult(event.result, event.isError));
});
}