Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ tap "oven-sh/bun"

cask "1password"
cask "1password-cli@beta"
cask "codex"
cask "codex-app"
cask "tailscale"

brew "curl"
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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

Expand Down
32 changes: 30 additions & 2 deletions skills/me-readiness/scripts/check-machine-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,31 @@ 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 = [
{
cask: '1password-cli',
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',
Expand Down Expand Up @@ -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'],
Expand Down Expand Up @@ -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) {
Expand Down
42 changes: 40 additions & 2 deletions test/me-readiness-check-machine.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ function healthyExistingPaths(...missingPaths) {

return [
'/Applications/1Password.app',
'/Applications/Codex.app',
'/Applications/Tailscale.app',
makePath('.vimrc'),
makePath('.vimrc.before'),
Expand All @@ -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'),
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -332,15 +337,19 @@ 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'),
);
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'));
Expand Down Expand Up @@ -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'),
Expand All @@ -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');
Expand Down Expand Up @@ -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'),
Expand Down