diff --git a/Brewfile b/Brewfile index 78530b6..19e0041 100644 --- a/Brewfile +++ b/Brewfile @@ -2,6 +2,8 @@ tap "oven-sh/bun" cask "1password" cask "1password-cli@beta" +cask "codex" +cask "codex-app" cask "tailscale" brew "curl" diff --git a/README.md b/README.md index 8c63e15..5e26283 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ When the script finishes, complete the manual setup checklist below. ### Codex +- Open the Brewfile-provided Codex desktop app. +- Sign in to Codex. - Plugins from `Pirostore`: - `piroplugin` - `tanaab` @@ -71,7 +73,8 @@ those prompts only when you intentionally asked Codex to run readiness. [`Brewfile`](./Brewfile) is the single source of truth for base machine dependencies. It covers Homebrew tooling plus the core CLI and runtime stack used here, including Git and GitHub CLI, -Bun/Node/Python, Stow, the 1Password desktop app and CLI, Tailscale, ImageMagick, and Zsh. +Codex CLI and the Codex desktop app, Bun/Node/Python, Stow, the 1Password desktop app and CLI, +Tailscale, ImageMagick, and Zsh. ### Dotpkgs diff --git a/skills/me-readiness/scripts/check-machine-lib.js b/skills/me-readiness/scripts/check-machine-lib.js index 2985f4c..0df3172 100644 --- a/skills/me-readiness/scripts/check-machine-lib.js +++ b/skills/me-readiness/scripts/check-machine-lib.js @@ -24,7 +24,13 @@ const ONEPASSWORD_ENVIRONMENT_VALIDATION_SCRIPT = `import { createHash } from "n const EXPECTED_NODE_FORMULA = 'node@24'; const EXPECTED_NODE_MAJOR_VERSION = 24; -export const REQUIRED_BREWFILE_CASKS = ['1password', '1password-cli@beta', 'tailscale']; +export const REQUIRED_BREWFILE_CASKS = [ + '1password', + '1password-cli@beta', + 'codex', + 'codex-app', + 'tailscale', +]; export const REQUIRED_BREWFILE_FORMULAS = [EXPECTED_NODE_FORMULA]; export const FORBIDDEN_BREWFILE_CASKS = [ { @@ -32,7 +38,17 @@ export const FORBIDDEN_BREWFILE_CASKS = [ id: 'brewfile_cask_1password_cli_stable_absent', }, ]; -export const REQUIRED_COMMANDS = ['brew', 'bun', 'git', 'gh', 'node', 'op', 'stow', 'tailscale']; +export const REQUIRED_COMMANDS = [ + 'brew', + 'bun', + 'codex', + 'git', + 'gh', + 'node', + 'op', + 'stow', + 'tailscale', +]; export const ONEPASSWORD_TOKEN_ENV_KEYS = [ 'PIROME_OP_TOKEN', 'TANAAB_OP_TOKEN', @@ -126,6 +142,7 @@ const CHECK_BUCKET_BY_ID = new Map([ ['onepassword_environment_run', 'manual_apps'], ['tailscale_app', 'manual_apps'], ['tailscale_status', 'manual_apps'], + ['codex_app', 'manual_apps'], ['bootstrap_token_env', 'manual_apps'], ['codex_piroplugin_link', 'codex_plugins'], ['codex_tanaab_link', 'codex_plugins'], @@ -627,6 +644,17 @@ async function appendAppPresenceChecks(checks, deps) { 'Rerun https://boot.pirog.me/boot.sh or install the Tailscale desktop app, then open it and sign in.', ), ); + + const codexAppPath = '/Applications/Codex.app'; + checks.push( + (await pathInfo(codexAppPath, deps)) + ? pass('codex_app', 'Codex.app was found.') + : fail( + 'codex_app', + 'Codex.app was not found.', + 'Rerun https://boot.pirog.me/boot.sh or install the Codex desktop app, then open it and sign in.', + ), + ); } async function appendOnePasswordVaultAccessCheck(checks, env, deps) { diff --git a/test/me-readiness-check-machine.spec.js b/test/me-readiness-check-machine.spec.js index 83bd95d..9ebfdd6 100644 --- a/test/me-readiness-check-machine.spec.js +++ b/test/me-readiness-check-machine.spec.js @@ -48,6 +48,7 @@ function healthyExistingPaths(...missingPaths) { return [ '/Applications/1Password.app', + '/Applications/Codex.app', '/Applications/Tailscale.app', makePath('.vimrc'), makePath('.vimrc.before'), @@ -66,6 +67,8 @@ function makeDeps({ brewfile = [ 'cask "1password"', 'cask "1password-cli@beta"', + 'cask "codex"', + 'cask "codex-app"', 'cask "tailscale"', 'brew "node@24"', ].join('\n'), @@ -301,7 +304,9 @@ describe('skills/me-readiness/scripts/check-machine-lib', () => { it('should include remediation for every warning and failure', async () => { const report = await runCheck({ brewfile: 'cask "1password-cli"\n', - commands: REQUIRED_COMMANDS.filter((command) => !['gh', 'tailscale'].includes(command)), + commands: REQUIRED_COMMANDS.filter( + (command) => !['codex', 'gh', 'tailscale'].includes(command), + ), configMode: 0o100644, env: { PIROME_OP_TOKEN: 'super-secret-token', @@ -332,6 +337,8 @@ describe('skills/me-readiness/scripts/check-machine-lib', () => { assert.ok(report.checks.some((check) => check.id === 'brewfile_cask_1password')); assert.ok(report.checks.some((check) => check.id === 'brewfile_cask_1password_cli_beta')); + assert.ok(report.checks.some((check) => check.id === 'brewfile_cask_codex')); + assert.ok(report.checks.some((check) => check.id === 'brewfile_cask_codex_app')); assert.ok(report.checks.some((check) => check.id === 'brewfile_formula_node_24')); assert.ok( report.checks.some((check) => check.id === 'brewfile_cask_1password_cli_stable_absent'), @@ -339,8 +346,10 @@ describe('skills/me-readiness/scripts/check-machine-lib', () => { assert.ok(report.checks.some((check) => check.id === 'brewfile_cask_tailscale')); assert.ok(report.checks.some((check) => check.id === 'vimrc_link')); assert.ok(report.checks.some((check) => check.id === 'vim_janus_runtime')); + assert.ok(report.checks.some((check) => check.id === 'command_codex')); assert.ok(report.checks.some((check) => check.id === 'command_gh')); assert.ok(report.checks.some((check) => check.id === 'command_tailscale')); + assert.ok(report.checks.some((check) => check.id === 'codex_app')); assert.ok(report.checks.some((check) => check.id === 'onepassword_app')); assert.ok(report.checks.some((check) => check.id === 'onepassword_environment_cli')); assert.ok(report.checks.some((check) => check.id === 'onepassword_environment_run')); @@ -395,6 +404,8 @@ describe('skills/me-readiness/scripts/check-machine-lib', () => { brewfile: [ 'cask "1password"', 'cask "1password-cli"', + 'cask "codex"', + 'cask "codex-app"', 'cask "tailscale"', 'brew "node@24"', ].join('\n'), @@ -414,7 +425,13 @@ describe('skills/me-readiness/scripts/check-machine-lib', () => { it('should require the node@24 Brewfile formula', async () => { const report = await runCheck({ - brewfile: ['cask "1password"', 'cask "1password-cli@beta"', 'cask "tailscale"'].join('\n'), + brewfile: [ + 'cask "1password"', + 'cask "1password-cli@beta"', + 'cask "codex"', + 'cask "codex-app"', + 'cask "tailscale"', + ].join('\n'), existingPaths: healthyExistingPaths(), }); const formulaCheck = report.checks.find((check) => check.id === 'brewfile_formula_node_24'); @@ -636,6 +653,27 @@ describe('skills/me-readiness/scripts/check-machine-lib', () => { assert.equal(tailscaleAppCheck.status, 'fail'); }); + it('should fail when the Codex app is missing', async () => { + const report = await runCheck({ + existingPaths: healthyExistingPaths('/Applications/Codex.app'), + }); + const codexAppCheck = report.checks.find((check) => check.id === 'codex_app'); + + assert.equal(report.ok, false); + assert.equal(codexAppCheck.status, 'fail'); + }); + + it('should fail when the Codex command is missing', async () => { + const report = await runCheck({ + commands: REQUIRED_COMMANDS.filter((command) => command !== 'codex'), + existingPaths: healthyExistingPaths(), + }); + const commandCheck = report.checks.find((check) => check.id === 'command_codex'); + + assert.equal(report.ok, false); + assert.equal(commandCheck.status, 'fail'); + }); + it('should fail Tailscale status when the command is missing', async () => { const report = await runCheck({ commands: REQUIRED_COMMANDS.filter((command) => command !== 'tailscale'),