Skip to content
Merged
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
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent setup

One shared base for Claude Code and Codex on a Mac: settings, hooks, plugins, skills, and the tools the machine needs. It prefers Alárinà when the user has access and installation succeeds, otherwise it installs QP Skills (`qp-skills`).
One shared base for Claude Code and Codex on a Mac: settings, hooks, plugins, skills, and the tools the machine needs. It installs QP Skills (`qp-skills`) for the engineering workflow and personal defaults.

**How to use it:** install Claude Code or Codex, open it, and give it this repo link. The agent checks what your machine has, tells you what it would add or change, and asks before it touches your own files. It installs the other CLI if it is missing.

Expand All @@ -17,7 +17,7 @@ Goal: this machine runs the base setup for Claude Code and Codex, and the user's
3. **Do.** After the user agrees, install only what is missing, apply the files (back up first), and refresh plugins and skills. Use `steps/` when it fits.
4. **SIGIDI.** If no `SIGIDI*.app` is in `/Applications` (and the Mac is arm64), install the latest arm64 DMG from the pre-release builds of `quantipixels/sigidi-release`.
5. **Verify.** Run `mise run doctor`. Fix what fails, or report the exact error.
6. **Engineering workflow.** Setup checks GitHub read access to `quantipixels/alarina` and tries its plugin separately on each host. With access it installs the core `alarina` plugin and the optional packs listed under `packs` (software engineering, productivity, sharing, host maintenance, Yorùbá) from the same marketplace. If core access or installation fails, it installs the public `qp-skills` plugin from `quantipixels/skills`. A failed domain or optional pack is reported without replacing the installed core. Restart each host and confirm the selected plugin's `alarina` router loads. If Alárinà was selected, run `alarina setup`; qp-skills has no `idasile` setup skill, so report requested migration, linking or configuration work as pending. Keep tech-stack as the standards source.
6. **Engineering workflow.** Setup installs the public `qp-skills` plugin from `quantipixels/skills`, or the preferred workflow from an [optional private overlay](#optional-private-overlay). It tries the preferred plugin separately on each host and falls back to the public plugin if repository access or core installation fails. A failed optional pack is reported without replacing the installed core. Restart each host and confirm the selected workflow plugin loads. If that plugin has its own setup skill, run it; otherwise report requested migration, linking or configuration work as pending. Keep tech-stack as the standards source.
7. **Scan.** Read-only. Look in the Claude, Codex, and tool setup for stale or duplicate items, broken paths, leftover temp and cache folders, outdated tools, and secrets in config. Change nothing; end with recommended cleanups, each with its reason and exact command.

Impeccable manages its own design hook per project; turn it on inside a project with `$impeccable hooks on`. The base installs no Impeccable hook.
Expand All @@ -32,13 +32,13 @@ Done when doctor passes and the report lists what changed, what was skipped, ava

## Personal instructions and project tools

Alárinà is preferred; QP Skills is the public fallback/default. Both provide the workflow router. The `productivity` pack’s `asami` skill (or the public fallback’s copy) owns personal defaults in one user-owned skill, `ofin`, at `~/.agents/skills/ofin/SKILL.md`; core `alarina` (or the public fallback) supplies `asoju` for models and delegation rules. Agent-setup seeds `ofin` from `base/shared/instructions.md` (two lines on how to talk) only when missing, and never overwrites an existing one. It is never installed through a skill manager.
Your workflow plugin owns personal defaults and the model set. Personal defaults live in one user-owned skill, `ofin`, at `~/.agents/skills/ofin/SKILL.md`. Agent-setup seeds it from `base/shared/instructions.md` (two lines on how to talk) only when missing, and never overwrites an existing one. It is never installed through a skill manager.

Claude Code reads it through `~/.claude/CLAUDE.md`, whose first line is `@~/.agents/skills/ofin/SKILL.md` (the file, not the folder); Claude-only lines may follow the import and are kept. Codex ignores `@path` imports, so `~/.codex/AGENTS.md` is a symlink to the same file. Existing host instruction files that need replacement require `--yes` after approval and are backed up under `~/.agent-setup/backups/`. `mise run plan`, `steps/apply --dry-run`, and `steps/setup --dry-run` show the changes and backups without writing. Doctor checks this layout.

Excluding files: add `--skip <target>` (repeatable) to `steps/diff`, `steps/apply`, `steps/setup`, or `steps/doctor`. Targets are `instructions` (`~/.agents/skills/ofin/SKILL.md`, `~/.codex/AGENTS.md`, `~/.claude/CLAUDE.md`), `codex-config` (`~/.codex/config.toml`), or any path inside HOME (absolute, `~/...`, or relative to HOME), for example `--skip .claude/settings.json`. Skipped files are not written, linked, or created; plan and `--dry-run` list them as skipped, and doctor reports them as skipped instead of failing. Apply and setup hash each skipped path before running and stop with an error naming any that changed, including changes made by plugin or tool installs.

Removing a plugin or skill from the base stops installing it; it does not uninstall it. Neither supported workflow is treated as retired.
Removing a plugin or skill from the base stops installing it; it does not uninstall it. Existing workflows are not treated as retired.

The `tech-stack` skill owns per-project tool choices. Project-specific decisions belong in the project.

Expand All @@ -51,13 +51,25 @@ Agents should run a repository's own check command (for example, `npm run check`
## Non-goals

- Agent-setup does not store credentials, history, sessions, plugin caches, or MCP OAuth data in this repository.
- It does not own the user's instruction text or model set; QP Skills owns those defaults. Host settings remain machine configuration.
- It does not own the user's instruction text or model set; the workflow plugin owns those defaults. Host settings remain machine configuration.
- It does not choose project tools; it installs only what the machine needs.
- Checks run locally; there is no hosted CI.

The successful choice for each host is recorded in `~/.agent-setup/workflow.json`; rendering and doctor use that selection. Plan, dry-run and offline mode do not check network access or install plugins; a fresh machine defaults to the public qp-skills configuration. A later online run retries the preferred plugin. Preserve existing plugins and records; do not uninstall either workflow automatically.

If the preferred install fails, report its command/error and the fallback selection. If fallback installation also fails, setup fails instead of recording success. Failures from unrelated plugins still stop setup. Installation checks do not prove skill loading or completion of `alarina setup`; verify those separately.
If the preferred install fails, report its command/error and the fallback selection. If fallback installation also fails, setup fails instead of recording success. Failures from unrelated plugins still stop setup. Installation checks do not prove skill loading or completion of workflow configuration; verify those separately.

## Optional private overlay

Put private plugin preferences in `~/.agent-setup/overlay.yaml`. Each `job` must match a row in `base/plugins.yaml`; its other keys are merged into that row. An unknown job stops setup with its name.

```yaml
plugins:
- job: Engineering workflow and personal defaults
preferred: {source: example-org/workflow, marketplace: workflow, claude: workflow@workflow, codex: workflow@workflow, packs: [extra-a]}
```

Without the file or without repository read access, only public plugins install. The overlay stays on your machine.

## Contributing

Expand Down
7 changes: 0 additions & 7 deletions base/plugins.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ plugins:
- job: Engineering workflow and personal defaults
claude: qp-skills@qp-skills
codex: qp-skills@qp-skills
preferred:
source: quantipixels/alarina
marketplace: alarina
claude: alarina@alarina
codex: alarina@alarina
# Optional Alárinà packs from the same private marketplace, installed with the core.
packs: [software-eng, productivity, private-share, host-maintenance, yoruba]
- job: Modern web guidance
claude: modern-web-guidance@claude-plugins-official
codex: modern-web-guidance@openai-curated-remote
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Instruction text belongs to the workflow plugin

Agent-setup and the workflow plugin both wrote personal instructions, so two owners overwrote each other and `apply` broke on the Codex symlink. Agent-setup now seeds the user-owned `ofin` skill (`~/.agents/skills/ofin/SKILL.md`) once, links Codex to it, and starts the Claude file with its import. The workflow plugin owns the instruction text and model set. This keeps one source of personal defaults while agent-setup maintains the machine and host configuration.
2 changes: 1 addition & 1 deletion examples/personal.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Optional personal instructions

Add these to `~/.agents/skills/ofin/SKILL.md` after setup, or ask your agent to update your defaults through Alárinà’s `asami` skill.
Add these to `~/.agents/skills/ofin/SKILL.md` after setup, or ask your agent to update your defaults through your workflow plugin.

- Use English and conversational Yorùbá naturally, with emoji where they fit.
17 changes: 13 additions & 4 deletions steps/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,19 @@ def load_yaml(name):
if name not in _YAML:
if not shutil.which('yq'):
raise ValueError('yq is required to read setup lists; install it first with mise install yq.')
command = ['yq']
proc = subprocess.run([*command, '-o=json', '.', str(ROOT / 'base' / name)], text=True, capture_output=True)
if proc.returncode: raise ValueError('Cannot read base/' + name + ': ' + proc.stderr.strip())
_YAML[name] = json.loads(proc.stdout)
def read(path):
proc = subprocess.run(['yq', '-o=json', '.', str(path)], text=True, capture_output=True)
if proc.returncode: raise ValueError('Cannot read ' + str(path) + ': ' + proc.stderr.strip())
return json.loads(proc.stdout)
data = read(ROOT / 'base' / name)
overlay = STATE / 'overlay.yaml'
if name == 'plugins.yaml' and overlay.exists():
rows = {row['job']: row for row in data['plugins']}
for row in read(overlay)['plugins']:
job = row['job']
if job not in rows: raise ValueError('Unknown overlay plugin job: ' + job)
rows[job].update({key: value for key, value in row.items() if key != 'job'})
_YAML[name] = data
return _YAML[name]


Expand Down
14 changes: 7 additions & 7 deletions tests/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_fresh_machine_seeds_one_source_and_links_hosts(self):
def test_current_instruction_layout_is_untouched(self):
source, codex, claude = self.instruction_paths()
for path in (source, codex, claude): path.parent.mkdir(parents=True, exist_ok=True)
source.write_text('My own defaults, maintained by asami.\n')
source.write_text('My own defaults, maintained by your workflow plugin.\n')
codex.symlink_to(source)
claude.write_text('@~/.agents/skills/ofin/SKILL.md\n\nClaude-only rule.\n')
before = {path: path.lstat().st_mtime_ns for path in (source, codex, claude)}
Expand Down Expand Up @@ -242,16 +242,16 @@ def test_plugin_and_skill_lists_install_qp_skills_without_uninstalling(self):
settings = json.loads((self.home / '.claude/settings.json').read_text())
expected = {'plugin-dev', 'typescript-lsp', 'pyright-lsp', 'rust-analyzer-lsp', 'jdtls-lsp', 'kotlin-lsp', 'swift-lsp', 'hookify', 'playwright', 'modern-web-guidance'}
expected = {name + '@claude-plugins-official' for name in expected} | {'codex@openai-codex', 'ast-grep@ast-grep-marketplace', 'qp-skills@qp-skills'}
self.assertEqual(set(settings['enabledPlugins']), expected - {'qp-skills@qp-skills'})
self.assertEqual(set(settings['enabledPlugins']), expected)
self.assertEqual(settings['extraKnownMarketplaces']['qp-skills'], {'source': {'source': 'github', 'repo': 'quantipixels/skills'}, 'autoUpdate': True})
self.assertNotIn('alarina', settings['extraKnownMarketplaces'])
self.assertNotIn('workflow', settings['extraKnownMarketplaces'])
config = tomllib.loads((self.home / '.codex/config.toml').read_text())
self.assertEqual(config['marketplaces'], {'qp-skills': {'source_type': 'git', 'source': 'https://github.com/quantipixels/skills.git'}})
# Removed rows are no longer installed, but existing artifacts stay.
removed = self.home / '.agents/skills/skill-doctor/SKILL.md'
removed.parent.mkdir(parents=True)
removed.write_text('User-owned existing skill.\n')
settings['enabledPlugins']['alarina@alarina'] = True
settings['enabledPlugins']['workflow@workflow'] = True
(self.home / '.claude/settings.json').write_text(json.dumps(settings))
self.run_step('apply', '--offline')
self.fake_clients()
Expand All @@ -266,7 +266,7 @@ def test_plugin_and_skill_lists_install_qp_skills_without_uninstalling(self):
self.assertEqual(installed_skills, {'shadcn', 'agent-browser', 'vercel-react-best-practices', 'vercel-composition-patterns', 'frontend-design', 'impeccable', 'better-interface', 'swiftui-pro', 'appllama-app-design-skill', 'appllama-usage', 'animate', 'animation-vocabulary', 'apple-design', 'emil-design-eng', 'improve-animations', 'fframes-video', 'tech-stack'})
self.assertFalse(any(word in command for command in commands for word in ('uninstall', 'remove')))
self.assertEqual(removed.read_text(), 'User-owned existing skill.\n')
self.assertTrue(json.loads((self.home / '.claude/settings.json').read_text())['enabledPlugins']['alarina@alarina'])
self.assertTrue(json.loads((self.home / '.claude/settings.json').read_text())['enabledPlugins']['workflow@workflow'])

def test_full_doctor_checks_tools_and_warns_about_retired_plugins(self):
self.run_step('setup', '--offline', '--no-login', '--yes')
Expand All @@ -278,7 +278,7 @@ def test_full_doctor_checks_tools_and_warns_about_retired_plugins(self):
cache.mkdir()
inventory = {plugin: [{'scope': 'user', 'installPath': str(cache)}] for plugin in settings['enabledPlugins']}
inventory['qp-skills@qp-skills'] = [{'scope': 'user', 'installPath': str(cache)}]
inventory['alarina@alarina'] = [{'scope': 'user', 'installPath': str(cache)}]
inventory['workflow@workflow'] = [{'scope': 'user', 'installPath': str(cache)}]
path = self.home / '.claude/plugins/installed_plugins.json'
path.parent.mkdir(parents=True)
path.write_text(json.dumps({'plugins': inventory}))
Expand All @@ -291,7 +291,7 @@ def test_full_doctor_checks_tools_and_warns_about_retired_plugins(self):
path.parent.mkdir(parents=True)
path.write_text('Fixture installed skill.\n')
(self.home / '.agent-setup/profile.json').write_text('{"profile": "full"}\n')
self.env['TEST_CODEX_PLUGINS'] = json.dumps(['alarina@alarina', 'qp-skills@qp-skills'])
self.env['TEST_CODEX_PLUGINS'] = json.dumps(['workflow@workflow', 'qp-skills@qp-skills'])
output = self.run_step('doctor', '--dry-run').stdout
self.assertNotIn('installed retired', output)
self.assertIn('Doctor passed: full', output)
Expand Down
Loading
Loading