diff --git a/Brewfile.work b/Brewfile.work index 8426ccf6..d87154f0 100644 --- a/Brewfile.work +++ b/Brewfile.work @@ -16,6 +16,7 @@ tap "openai/tools" tap "oven-sh/bun", trusted: true tap "protonpass/tap", trusted: true tap "schpet/tap", trusted: { formulae: ["linea"] } +tap "steipete/tap", trusted: true tap "tw93/tap" # Easily download, build, install, upgrade, and uninstall Python packages brew "python-setuptools" @@ -81,6 +82,8 @@ brew "docker-buildx" brew "docker-compose" # Platform keystore credential helper for Docker brew "docker-credential-helper" +# Docker Credential Helper for Amazon ECR +brew "docker-credential-helper-ecr" # Command-line DNS Client for Humans brew "doggo" # View disk space usage and delete unwanted data, fast @@ -249,6 +252,10 @@ brew "sevenzip" brew "solo2-cli" # Cross-shell prompt for astronauts brew "starship" +# Feature-rich command-line audio/video downloader +brew "yt-dlp" +# Multi-modal AI tool to extract and summarize content +brew "summarize" # Very fast implementation of tldr in Rust brew "tealdeer" # General purpose fuzzy finder TUI @@ -291,8 +298,6 @@ brew "wrk" brew "yarn" # Blazing fast terminal file manager written in Rust, based on async I/O brew "yazi" -# Feature-rich command-line audio/video downloader -brew "yt-dlp" # Programming language designed for robustness, optimality, and clarity brew "zig" # Shell extension to navigate your filesystem faster @@ -328,9 +333,11 @@ cask "1password" # Command-line interface for 1Password cask "1password-cli" # AeroSpace is an i3-like tiling window manager for macOS -cask "nikitabobko/tap/aerospace" +cask "nikitabobko/tap/aerospace", trusted: true # Browse, search and analyse your past AI coding sessions cask "agentsview" +# Terminal interface for Antigravity agents +cask "antigravity-cli" # Compact TeX distribution as alternative to the full TeX Live / MacTeX cask "basictex" # Open source IDE for exploring and testing APIs diff --git a/dot_agents/skills/reddit/reddit.js b/dot_agents/skills/reddit/reddit.js index 5cfd83f6..f492afb1 100755 --- a/dot_agents/skills/reddit/reddit.js +++ b/dot_agents/skills/reddit/reddit.js @@ -98,10 +98,7 @@ async function cmdPost(opts) { if (!opts.target) { console.error("Error: post requires a URL or post path"); process.exit(1); } // Extract path from full URL or use as-is let path = opts.target; - try { - const u = new URL(path); - path = u.pathname; - } catch {} + if (URL.canParse(path)) path = new URL(path).pathname; // Ensure path ends cleanly path = path.replace(/\/$/, ""); diff --git a/dot_agents/skills/session-analyzer/executable_analyze.js b/dot_agents/skills/session-analyzer/executable_analyze.js index 1d8ed227..3d982d07 100644 --- a/dot_agents/skills/session-analyzer/executable_analyze.js +++ b/dot_agents/skills/session-analyzer/executable_analyze.js @@ -97,36 +97,38 @@ function runSubagent(prompt, cwd) { const rl = createInterface({ input: child.stdout }); rl.on("line", (line) => { + let event; try { - const event = JSON.parse(line); - if (event.type === "message_update" && event.assistantMessageEvent) { - if (event.assistantMessageEvent.type === "text_delta" && event.assistantMessageEvent.delta) { - textBuffer += event.assistantMessageEvent.delta; - } - } else if (event.type === "tool_execution_start" && event.toolName) { - if (textBuffer.trim()) { - console.log(` ${truncateLine(textBuffer, 100)}`); - textBuffer = ""; - } - let argsStr = ""; - if (event.args) { - if (event.toolName === "read") { - argsStr = event.args.path || ""; - if (event.args.offset) argsStr += ` offset=${event.args.offset}`; - if (event.args.limit) argsStr += ` limit=${event.args.limit}`; - } else if (event.toolName === "write") { - argsStr = event.args.path || ""; - } - } - console.log(` [${event.toolName}] ${argsStr}`); - } else if (event.type === "turn_end") { - if (textBuffer.trim()) { - console.log(` ${truncateLine(textBuffer, 100)}`); - } + event = JSON.parse(line); + } catch { + // Not a JSON event line; skip it. + } + if (!event) return; + if (event.type === "message_update" && event.assistantMessageEvent) { + if (event.assistantMessageEvent.type === "text_delta" && event.assistantMessageEvent.delta) { + textBuffer += event.assistantMessageEvent.delta; + } + } else if (event.type === "tool_execution_start" && event.toolName) { + if (textBuffer.trim()) { + console.log(` ${truncateLine(textBuffer, 100)}`); textBuffer = ""; } - } catch { - // Ignore malformed JSON + let argsStr = ""; + if (event.args) { + if (event.toolName === "read") { + argsStr = event.args.path || ""; + if (event.args.offset) argsStr += ` offset=${event.args.offset}`; + if (event.args.limit) argsStr += ` limit=${event.args.limit}`; + } else if (event.toolName === "write") { + argsStr = event.args.path || ""; + } + } + console.log(` [${event.toolName}] ${argsStr}`); + } else if (event.type === "turn_end") { + if (textBuffer.trim()) { + console.log(` ${truncateLine(textBuffer, 100)}`); + } + textBuffer = ""; } }); diff --git a/dot_default-npm-packages b/dot_default-npm-packages index 4b3d69a6..3ae80753 100644 --- a/dot_default-npm-packages +++ b/dot_default-npm-packages @@ -4,3 +4,4 @@ defuddle npm-check obsidian-headless skills +slop-scan diff --git a/dot_pi/agent/AGENTS.md b/dot_pi/agent/AGENTS.md index 62aa12f0..a685da5e 100644 --- a/dot_pi/agent/AGENTS.md +++ b/dot_pi/agent/AGENTS.md @@ -3,6 +3,7 @@ ## General preferences - Always address me using my name 'Joe' +- Keep explanations concise and easy to understand ## Shell tool preferences diff --git a/dot_pi/agent/extensions/answer/index.ts b/dot_pi/agent/extensions/answer/index.ts index 9b8b4055..8e96c6cd 100644 --- a/dot_pi/agent/extensions/answer/index.ts +++ b/dot_pi/agent/extensions/answer/index.ts @@ -167,15 +167,14 @@ function parseExtractionResult(text: string): ExtractionResult | null { } for (const candidate of candidates) { + let result: ExtractionResult | null = null; try { - const result = toExtractionResult( - parseJsonWithRepair(candidate), - ); - if (result) { - return result; - } + result = toExtractionResult(parseJsonWithRepair(candidate)); } catch { - // Try the next candidate. + // Malformed JSON; fall through to the next candidate. + } + if (result) { + return result; } } diff --git a/dot_pi/agent/extensions/commit/index.ts b/dot_pi/agent/extensions/commit/index.ts index 2d1a497d..3bb2c3ff 100644 --- a/dot_pi/agent/extensions/commit/index.ts +++ b/dot_pi/agent/extensions/commit/index.ts @@ -215,12 +215,13 @@ function buildCommitUserMessage( function extractText( content: ReadonlyArray<{ type: string; text?: string }>, -): string { - return content +): string | null { + const text = content .filter((c): c is { type: "text"; text: string } => c.type === "text") .map((c) => c.text) .join("\n") .trim(); + return text || null; } function buildCommitUrl(remoteUrl: string, hash: string): string { @@ -300,8 +301,7 @@ async function generateCommitMessage( return null; } - const text = extractText(response.content); - return text || null; + return extractText(response.content); } return ctx.ui.custom((tui, theme, _kb, done) => { @@ -332,8 +332,7 @@ async function generateCommitMessage( return null; } - const text = extractText(response.content); - return text || null; + return extractText(response.content); }; doGenerate() diff --git a/dot_pi/agent/extensions/context/index.ts b/dot_pi/agent/extensions/context/index.ts index a5a46402..0c9fd043 100644 --- a/dot_pi/agent/extensions/context/index.ts +++ b/dot_pi/agent/extensions/context/index.ts @@ -31,7 +31,6 @@ import { import os from "node:os"; import path from "node:path"; import fs from "node:fs/promises"; -import { existsSync } from "node:fs"; function formatUsd(cost: number): string { if (!Number.isFinite(cost) || cost <= 0) return "$0.00"; @@ -86,7 +85,6 @@ function getAgentDir(): string { async function readFileIfExists( filePath: string, ): Promise<{ path: string; content: string; bytes: number } | null> { - if (!existsSync(filePath)) return null; try { const buf = await fs.readFile(filePath); return { @@ -94,7 +92,8 @@ async function readFileIfExists( content: buf.toString("utf8"), bytes: buf.byteLength, }; - } catch { + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err; return null; } } @@ -160,7 +159,7 @@ function parseDisableModelInvocationFromFrontmatter( async function readDisableModelInvocation( filePath: string, ): Promise { - if (!filePath || !existsSync(filePath)) return false; + if (!filePath) return false; try { // Frontmatter sits at the top; read a small slice instead of the full file. const fh = await fs.open(filePath, "r"); @@ -173,7 +172,8 @@ async function readDisableModelInvocation( } finally { await fh.close(); } - } catch { + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err; return false; } } diff --git a/dot_pi/agent/extensions/cwd-history/index.ts b/dot_pi/agent/extensions/cwd-history/index.ts index 3884ca6e..ca5c662d 100644 --- a/dot_pi/agent/extensions/cwd-history/index.ts +++ b/dot_pi/agent/extensions/cwd-history/index.ts @@ -50,14 +50,6 @@ class HistoryEditor extends CustomEditor { } } -function extractText(content: Array<{ type: string; text?: string }>): string { - return content - .filter((item) => item.type === "text" && typeof item.text === "string") - .map((item) => item.text ?? "") - .join("") - .trim(); -} - function collectUserPromptsFromEntries(entries: Array): PromptEntry[] { const prompts: PromptEntry[] = []; @@ -66,7 +58,14 @@ function collectUserPromptsFromEntries(entries: Array): PromptEntry[] { const message = entry?.message; if (!message || message.role !== "user" || !Array.isArray(message.content)) continue; - const text = extractText(message.content); + const text = message.content + .filter( + (item: { type: string; text?: string }) => + item.type === "text" && typeof item.text === "string", + ) + .map((item: { text?: string }) => item.text ?? "") + .join("") + .trim(); if (!text) continue; const timestamp = Number( message.timestamp ?? entry.timestamp ?? Date.now(), @@ -160,11 +159,13 @@ async function loadPromptHistoryForCwd( .split("\n") .filter(Boolean) .map((line) => { + let entry: unknown; try { - return JSON.parse(line); + entry = JSON.parse(line); } catch { - return undefined; + // The tail can start mid-record; skip lines that are not valid JSON. } + return entry; }) .filter(Boolean); for (const prompt of collectUserPromptsFromEntries(entries)) { diff --git a/dot_pi/agent/extensions/pr-create/index.ts b/dot_pi/agent/extensions/pr-create/index.ts index 8109591d..e3ad113c 100644 --- a/dot_pi/agent/extensions/pr-create/index.ts +++ b/dot_pi/agent/extensions/pr-create/index.ts @@ -166,6 +166,23 @@ async function pickTitle( } } +async function tryExec( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + cmd: string, + args: string[], + timeout: number, +): Promise { + let out: string | undefined; + try { + const r = await pi.exec(cmd, args, { signal: ctx.signal, timeout }); + out = r.code === 0 ? r.stdout : undefined; + } catch { + // Command missing or aborted; callers fall back to other sources. + } + return out; +} + async function autoTitle( pi: ExtensionAPI, ctx: ExtensionCommandContext, @@ -175,17 +192,10 @@ async function autoTitle( const normalized = normalizeTitleCandidate(t); if (t && TITLE_RE.test(t)) return t; if (normalized && TITLE_RE.test(normalized)) return normalized; - try { - const r = await pi.exec("git", ["log", "-1", "--pretty=%s"], { - signal: ctx.signal, - timeout: 5_000, - }); - if (r.code === 0) { - const s = normalizeTitleCandidate(r.stdout); - if (s && TITLE_RE.test(s)) return s; - } - } catch { - // ignore + const subject = await tryExec(pi, ctx, "git", ["log", "-1", "--pretty=%s"], 5_000); + if (subject) { + const s = normalizeTitleCandidate(subject); + if (s && TITLE_RE.test(s)) return s; } return undefined; } @@ -235,19 +245,14 @@ async function resolveBaseRef( if (!candidate) return undefined; const bare = candidate.replace(/^origin\//, ""); for (const ref of [`origin/${bare}`, bare]) { - try { - const r = await pi.exec( - "git", - ["rev-parse", "--verify", "--quiet", ref], - { - signal: ctx.signal, - timeout: 5_000, - }, - ); - if (r.code === 0 && r.stdout.trim()) return ref; - } catch { - // ignore - } + const r = await tryExec( + pi, + ctx, + "git", + ["rev-parse", "--verify", "--quiet", ref], + 5_000, + ); + if (r?.trim()) return ref; } return undefined; } @@ -431,41 +436,24 @@ async function detectDefaultBranch( ctx: ExtensionCommandContext, ): Promise { // Try the symbolic-ref of origin/HEAD first (no network). - try { - const r = await pi.exec( - "git", - ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], - { signal: ctx.signal, timeout: 5_000 }, - ); - if (r.code === 0) { - const s = r.stdout.trim().replace(/^origin\//, ""); - if (s) return s; - } - } catch { - // ignore - } + const symbolic = await tryExec( + pi, + ctx, + "git", + ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], + 5_000, + ); + const local = symbolic?.trim().replace(/^origin\//, ""); + if (local) return local; // Fall back to gh repo view. - try { - const r = await pi.exec( - "gh", - [ - "repo", - "view", - "--json", - "defaultBranchRef", - "--jq", - ".defaultBranchRef.name", - ], - { signal: ctx.signal, timeout: 10_000 }, - ); - if (r.code === 0) { - const s = r.stdout.trim(); - if (s) return s; - } - } catch { - // ignore - } - return undefined; + const remote = await tryExec( + pi, + ctx, + "gh", + ["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], + 10_000, + ); + return remote?.trim() || undefined; } async function detectLinearFromBranch( diff --git a/dot_pi/agent/extensions/spawn/index.ts b/dot_pi/agent/extensions/spawn/index.ts index 983cdcaf..d8c0fbab 100644 --- a/dot_pi/agent/extensions/spawn/index.ts +++ b/dot_pi/agent/extensions/spawn/index.ts @@ -103,8 +103,9 @@ async function completeDirectories( try { entries = await fs.readdir(listDir, { withFileTypes: true }); } catch { - return null; + // Directory missing or unreadable; no completions to offer. } + if (!entries) return null; const fragmentLower = fragment.toLowerCase(); const showHidden = fragment.startsWith(".");