---
phase: 060-setup-modularization
plan: 03
type: execute
wave: 1
depends_on:
  - 060-01
files_modified:
  - paperforge/setup/agent.py
autonomous: true
requirements:
  - SETP-05

must_haves:
  truths:
    - "AgentInstaller deploys skills for all 3 deployment formats: skill_directory (Claude/Codex/Cursor/Windsurf/GitHub Copilot/Augment/Trae), flat_command (OpenCode), and rules_file (Cline)"
    - "AgentInstaller deploys pf-*.md skill scripts from paperforge/skills/literature-qa/scripts/ — each .md becomes a skill directory with SKILL.md"
    - "AgentInstaller deploys pf-deep extras: ld_deep.py script, prompt_deep_subagent.md, and chart-reading/ directory of markdown files"
    - "AgentInstaller deploys pf-paper as lightweight SKILL.md (no scripts/charts)"
    - "AgentInstaller creates agent config files when defined (Claude skills.json, Cursor settings.json, GitHub Copilot copilot-instructions.md)"
    - "AgentInstaller deploys Obsidian plugin files (main.js, manifest.json, styles.css, versions.json) incrementally — preserves existing plugin files"
    - "AgentInstaller deploys docs/ directory tree incrementally and AGENTS.md with variable substitution"
    - "AgentInstaller deploys worker script files (sync.py, ocr.py, etc.) to vault/<system_dir>/PaperForge/worker/scripts/"
    - "All deployment uses incremental copy/write — existing user files are NEVER overwritten"
  artifacts:
    - path: "paperforge/setup/agent.py"
      provides: "AgentInstaller class — all agent skill/command/rules/plugin/docs/worker deployment"
      min_lines: 220
  key_links:
    - from: "paperforge/setup/agent.py"
      to: "paperforge/setup_wizard.py"
      via: "function extraction _deploy_skill_directory, _deploy_flat_command, _deploy_rules_file"
      pattern: "from paperforge.setup.agent import AgentInstaller"
    - from: "paperforge/setup/agent.py"
      to: "paperforge/skills/literature-qa/scripts/"
      via: "reads pf-*.md files for skill content"
      pattern: "glob.*pf-"
    - from: "paperforge/setup/agent.py"
      to: "paperforge/setup/vault.py (helpers)"
      via: "calls _copy_file_incremental, _write_text_incremental, _copy_tree_incremental, _substitute_vars"
      pattern: "_copy_file_incremental|_write_text_incremental|_copy_tree_incremental|_substitute_vars"

---

<objective>
Create the AgentInstaller class that handles all agent-specific deployment: skills, commands, rules files, plugin binaries, documentation, and AGENTS.md. This is extracted from `_deploy_skill_directory`, `_deploy_flat_command`, `_deploy_rules_file`, and the inline deployment code in `headless_setup()`.

Purpose: Agent deployment is the largest single concern in setup_wizard.py (~350 lines across 3 deploy functions + inline plugin/docs/worker code). Extracting into one focused class makes each deployment path explicit, testable in isolation, and disconnected from the orchestration flow.

Output:
- `paperforge/setup/agent.py` — `AgentInstaller` class with all deployment methods
</objective>

<execution_context>
../.opencode/get-shit-done/workflows/execute-plan.md
../.opencode/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/phases/060-setup-modularization/060-01-PLAN.md
@.planning/phases/060-setup-modularization/060-CONTEXT.md

# Existing code being extracted
@paperforge/setup_wizard.py
  (lines 515-628: _deploy_skill_directory, _deploy_flat_command, _deploy_rules_file)
  (lines 825-931: inline deployment of workers, docs, plugin, AGENTS.md)

# SetupStepResult and helpers used
@paperforge/setup/__init__.py

# Existing tests that exercise agent deployment paths
@tests/test_setup_wizard.py
</context>

<interfaces>
SetupStepResult (from Plan 01):
```python
@dataclass
class SetupStepResult:
    step: str
    ok: bool
    message: str
    error: PFError | None = None

    @classmethod
    def ok(cls, step: str, message: str) -> SetupStepResult: ...
    @classmethod
    def fail(cls, step: str, message: str, error=None) -> SetupStepResult: ...
```

Helper functions from vault.py (Plan 02 — already extracted):
```python
def _substitute_vars(text, system_dir, resources_dir, literature_dir, control_dir, base_dir, skill_dir, prefix="/") -> str: ...
def _copy_file_incremental(src: Path, dst: Path) -> bool: ...
def _write_text_incremental(dst: Path, text: str) -> bool: ...
def _copy_tree_incremental(src_dir: Path, dst_dir: Path) -> tuple[int, int]: ...
```

AGENT_CONFIGS (from __init__.py, Plan 01):
```python
AGENT_CONFIGS = {
    "opencode": { "name": "OpenCode", "skill_dir": ".opencode/skills", "command_dir": ".opencode/command", "format": "flat_command", ... },
    "claude": { "name": "Claude Code", "skill_dir": ".claude/skills", "format": "skill_directory", ... },
    # ... 9 total platforms
}
```
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: Create AgentInstaller class with skill/command/rules deployment</name>
  <files>paperforge/setup/agent.py</files>
  <action>
    Create `paperforge/setup/agent.py` with the `AgentInstaller` class. Extract the three deploy functions and inline deployment code into a unified class.

    **Class: `AgentInstaller`**

    `__init__(self, vault: Path, agent_key: str, repo_root: Path, system_dir: str = "System", resources_dir: str = "Resources", literature_dir: str = "Literature", control_dir: str = "LiteratureControl", base_dir: str = "Bases")`
    - Looks up `agent_config` from `AGENT_CONFIGS[agent_key]` or raises ValueError if unknown
    - Extracts: `skill_dir`, `command_dir`, `format`, `prefix`, `config_file` from agent config
    - If `skill_dir_path` differs from `skill_dir` (for rules_file format where .clinerules != .clinerules/skills), tracks both

    `deploy_skills(self) -> SetupStepResult`
    - Extracts logic from `_deploy_skill_directory()` (lines 515-563) and `_deploy_flat_command()` (lines 567-593) and `_deploy_rules_file()` (lines 597-628)
    - Dispatches based on `self.format`:
      - **`"skill_directory"`**: deploys each `pf-*.md` in `scripts/` as `<skill_dir>/<name>/SKILL.md`, with pf-deep extras (ld_deep.py, prompt, chart-reading)
      - **`"flat_command"`**: deploys `pf-*.md` from `command_files/` to `<command_dir>/<name>.md` AND also calls skill_directory for pf-deep extras
      - **`"rules_file"`**: deploys pf-deep content (scripts, prompt, charts) under `<skill_dir>/pf-deep/` (i.e., `.clinerules/pf-deep/`)
    - Returns `SetupStepResult.ok("agent", f"{count} skill(s): {names}")` on success
    - Returns `SetupStepResult.fail(...)` on source directory not found, with descriptive error

    `deploy_worker_scripts(self) -> SetupStepResult`
    - Extracted from headless_setup() lines 828-842
    - Copies from `repo_root / "paperforge/worker/"` to `vault / system_dir / "PaperForge" / "worker/scripts/"`
    - Files: sync.py, ocr.py, repair.py, status.py, deep_reading.py, update.py, base_views.py, __init__.py, _utils.py, _progress.py, _retry.py
    - Uses incremental copy (never overwrites)
    - Returns `SetupStepResult.ok("agent", "worker scripts deployed")` or fail if source missing

    `deploy_plugin(self) -> SetupStepResult`
    - Extracted from headless_setup() lines 899-911
    - Copies from `repo_root / "paperforge/plugin/"` to `vault / ".obsidian" / "plugins" / "paperforge/"`
    - Incremental: for each file in plugin dir, `_copy_file_incremental(src, dst)`
    - Returns `SetupStepResult.ok("agent", "Obsidian plugin (created N, preserved M)")`
    - Handles missing plugin source gracefully

    `deploy_docs(self) -> SetupStepResult`
    - Extracted from headless_setup() lines 889-897
    - Copies `repo_root / "docs/"` to `vault / "docs/"` via `_copy_tree_incremental`
    - Returns `SetupStepResult.ok("agent", "docs (created N, preserved M)")` or `ok` with "source not found" if docs missing

    `deploy_agents_md(self) -> SetupStepResult`
    - Extracted from headless_setup() lines 913-928
    - Reads `repo_root / "AGENTS.md"`, runs `_substitute_vars` on it, writes to `vault / "AGENTS.md"` incrementally
    - Returns `SetupStepResult.ok("agent", "AGENTS.md")` or `ok("agent", "AGENTS.md source not found; skipping")` (non-fatal)

    `create_config_file(self) -> SetupStepResult`
    - Extracted from headless_setup() lines 874-887
    - If agent_config has a `config_file` and it doesn't exist:
      - JSON files -> write `{}\n`
      - Other files -> write empty string
    - Returns `SetupStepResult.ok("agent", f"{config_file} created")` or `ok("agent", "no config file needed")`

    _deploy_skill_directory(self, ...) -> list[str] — private implementation detail
    _deploy_flat_command(self, ...) -> list[str] — private implementation detail  
    _deploy_rules_file(self, ...) -> list[str] — private implementation detail
    - These are the internal implementations of the three format paths, extracted from the original functions

    **Edge cases:**
    - Source directories missing (no scripts/, no plugin/, no docs/) — each returns ok=False gracefully, not crash
    - Unknown agent_key — raises ValueError at init time, not deep in deployment
    - AGENTS.md missing — non-fatal, returns ok with warning message
    - Plugin source directory exists but has no main.js — deploy_plugin returns ok=False with descriptive message
  </action>
  <verify>
    <automated>python -c "from paperforge.setup.agent import AgentInstaller; from pathlib import Path; import tempfile; d = tempfile.mkdtemp(); try: a = AgentInstaller(Path(d), 'opencode', Path(d)); print('init ok'); except ValueError: print('init failed (expected if repo_path missing)')"</automated>
  </verify>
  <done>AgentInstaller class created with deploy_skills/deploy_worker_scripts/deploy_plugin/deploy_docs/deploy_agents_md/create_config_file methods; all 3 deployment formats supported; incremental copy preserved</done>
</task>

</tasks>

<verification>
1. `python -c "from paperforge.setup.agent import AgentInstaller"` succeeds
2. AgentInstaller can be instantiated with vault, agent_key, and repo_root
3. AgentInstaller raises ValueError for unknown agent_key
4. verify all deployment methods return SetupStepResult instances
</verification>

<success_criteria>
1. AgentInstaller class defined with all deployment methods returning SetupStepResult
2. Three skill deployment formats supported: skill_directory, flat_command, rules_file
3. All agent platforms in AGENT_CONFIGS have corresponding format handling
4. pf-deep extras (ld_deep.py, prompt, chart-reading) bundled for skill_directory and rules_file formats
5. pf-*.md scripts deployed individually as skill entries
6. Worker scripts, plugin files, docs, and AGENTS.md all have dedicated deployment methods
7. Incremental copy pattern preserved throughout (no overwrites of user files)
8. All deployment failures produce classified SetupStepResult, not bare exceptions
</success_criteria>

<output>
After completion, create `.planning/phases/060-setup-modularization/060-03-SUMMARY.md`
</output>
