diff --git a/CLAUDE.md b/CLAUDE.md index 0fbfa60..434cd60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,7 @@ Use the `/add-agent` skill — see `.claude/skills/add-agent/skill.md` for the f | opencode | `opencode` | `upgrade` | | cursor-agent | `cursor-agent` | `update` | | copilot | `copilot` | `update` | +| pi | `pi` | `update pi` | ## Key files @@ -34,4 +35,4 @@ Use the `/add-agent` skill — see `.claude/skills/add-agent/skill.md` for the f ## Versioning -`getVersion` extracts the first `/\d+\.\d+\.\d+/` match from ` --version` stdout. If the output format doesn't match, set `versioned: false`. +`getVersion` extracts the first `/\d+\.\d+\.\d+/` match from ` --version` stdout or stderr. If the output format doesn't match, set `versioned: false`. diff --git a/README.md b/README.md index 8a98f6a..ec9efa5 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Each tool runs concurrently. After updating, aiupdate shows the version change ( | [OpenCode](https://opencode.ai) | `opencode upgrade` | | [Cursor Agent](https://cursor.com) | `cursor-agent update` | | [GitHub Copilot CLI](https://githubnext.com/projects/copilot-cli) | `copilot update` | +| [pi](https://pi.dev/) | `pi update pi` | | [skills](https://github.com/anthropics/skills) | `npx skills update -g -p -y` | Tools not installed on your machine are silently skipped. diff --git a/src/cli.test.ts b/src/cli.test.ts index 22852d9..0f878f0 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -1,5 +1,8 @@ +import { mkdtemp, writeFile, chmod, rm } from 'fs/promises'; +import { tmpdir } from 'os'; +import { join } from 'path'; import { describe, it, expect } from 'vitest'; -import { selectTools, run, formatTitle, AI_TOOLS, SKILLS_TASK } from './cli.js'; +import { selectTools, run, formatTitle, getVersion, AI_TOOLS, SKILLS_TASK } from './cli.js'; const allInstalled: (cmd: string) => Promise = async () => true; const noneInstalled: (cmd: string) => Promise = async () => false; @@ -26,6 +29,22 @@ describe('formatTitle', () => { }); }); +describe('getVersion', () => { + it('extracts versions from stderr', async () => { + const dir = await mkdtemp(join(tmpdir(), 'aiupdate-')); + const command = join(dir, 'version-stderr'); + + try { + await writeFile(command, "#!/usr/bin/env node\nprocess.stderr.write('fake 9.8.7\\n');\n"); + await chmod(command, 0o755); + + await expect(getVersion(command)).resolves.toBe('9.8.7'); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); +}); + describe('selectTools', () => { it('returns all AI tools + skills when no args', () => { const { selectedAITools, includeSkills } = selectTools([]); diff --git a/src/cli.ts b/src/cli.ts index 238fbec..ab07893 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -21,6 +21,7 @@ export const AI_TOOLS: Tool[] = [ { name: 'opencode', command: 'opencode', args: ['upgrade'], versioned: true }, { name: 'cursor-agent', command: 'cursor-agent', args: ['update'], versioned: true }, { name: 'copilot', command: 'copilot', args: ['update'], versioned: true }, + { name: 'pi', command: 'pi', args: ['update', 'pi'], versioned: true }, ]; export const SKILLS_TASK: Tool = { @@ -38,10 +39,10 @@ async function defaultChecker(command: string): Promise { } } -async function getVersion(command: string): Promise { +export async function getVersion(command: string): Promise { try { - const { stdout } = await execa(command, ['--version']); - const match = stdout.match(/\d+\.\d+\.\d+/); + const { stdout, stderr } = await execa(command, ['--version']); + const match = `${stdout}\n${stderr}`.match(/\d+\.\d+\.\d+/); return match ? match[0] : null; } catch { return null;