diff --git a/package.json b/package.json index da39f8de..da87ad34 100644 --- a/package.json +++ b/package.json @@ -36,16 +36,18 @@ "research": "bin/ds.js" }, "dependencies": { - "@anthropic-ai/claude-code": "^2.1.109", - "@openai/codex": "^0.114.0", "ink": "npm:@jrichman/ink@6.4.6", "ink-gradient": "^3.0.0", - "opencode-ai": "^1.14.28", "qrcode": "^1.5.4", "react": "^19.2.0", "react-dom": "^19.2.0", "string-width": "^8.1.0" }, + "optionalDependencies": { + "@anthropic-ai/claude-code": "^2.1.109", + "@openai/codex": "^0.114.0", + "opencode-ai": "^1.14.28" + }, "scripts": { "ds": "node ./bin/ds.js", "start": "node ./bin/ds.js daemon", diff --git a/src/deepscientist/daemon/app.py b/src/deepscientist/daemon/app.py index 6172bcb6..6b26883e 100644 --- a/src/deepscientist/daemon/app.py +++ b/src/deepscientist/daemon/app.py @@ -206,7 +206,7 @@ def __init__( self.runtime_config = self.config_manager.load_runtime_config() self.runners_config = self.config_manager.load_runners_config() self.connectors_config = self.config_manager.load_named_normalized("connectors") - self.skill_installer = SkillInstaller(self.repo_root, home) + self.skill_installer = SkillInstaller(self.repo_root, home, runners_config=self.runners_config) self.quest_service = QuestService(home, skill_installer=self.skill_installer) self.latex_service = QuestLatexService(self.quest_service) self.memory_service = MemoryService(home) diff --git a/src/deepscientist/skills/installer.py b/src/deepscientist/skills/installer.py index 66a6ddc0..8d06fa4d 100644 --- a/src/deepscientist/skills/installer.py +++ b/src/deepscientist/skills/installer.py @@ -16,46 +16,63 @@ class SkillInstaller: - def __init__(self, repo_root: Path, home: Path) -> None: + def __init__(self, repo_root: Path, home: Path, runners_config: dict | None = None) -> None: self.repo_root = repo_root self.home = home + self.runners_config = runners_config or {} + + def _is_runner_enabled(self, runner_name: str) -> bool: + runner = self.runners_config.get(runner_name, {}) + return bool(runner.get("enabled", False)) def discover(self): return discover_skill_bundles(self.repo_root) def sync_global(self) -> dict: - codex_root = ensure_dir(Path.home() / ".codex" / "skills") - claude_root = ensure_dir(Path.home() / ".claude" / "agents") - kimi_root = ensure_dir(Path.home() / ".kimi" / "skills") - opencode_root = ensure_dir(Path.home() / ".config" / "opencode" / "skills") copied_codex: list[str] = [] copied_claude: list[str] = [] copied_kimi: list[str] = [] copied_opencode: list[str] = [] - expected_codex: set[str] = set() - expected_claude: set[str] = set() - expected_kimi: set[str] = set() - expected_opencode: set[str] = set() - for bundle in self.discover(): - target = codex_root / f"deepscientist-{bundle.skill_id}" - expected_codex.add(target.name) - self._sync_bundle_tree(bundle.root, target) - copied_codex.append(str(target)) - claude_target = self._sync_claude_projection(bundle, claude_root) - expected_claude.add(claude_target.name) - copied_claude.append(str(claude_target)) - kimi_target = kimi_root / f"deepscientist-{bundle.skill_id}" - expected_kimi.add(kimi_target.name) - self._sync_bundle_tree(bundle.root, kimi_target) - copied_kimi.append(str(kimi_target)) - opencode_target = opencode_root / f"deepscientist-{bundle.skill_id}" - expected_opencode.add(opencode_target.name) - self._sync_bundle_tree(bundle.root, opencode_target) - copied_opencode.append(str(opencode_target)) - self._prune_bundle_targets(codex_root, expected_codex) - self._prune_bundle_targets(claude_root, expected_claude) - self._prune_bundle_targets(kimi_root, expected_kimi) - self._prune_bundle_targets(opencode_root, expected_opencode) + + if self._is_runner_enabled("codex"): + codex_root = ensure_dir(Path.home() / ".codex" / "skills") + expected_codex: set[str] = set() + for bundle in self.discover(): + target = codex_root / f"deepscientist-{bundle.skill_id}" + expected_codex.add(target.name) + self._sync_bundle_tree(bundle.root, target) + copied_codex.append(str(target)) + self._prune_bundle_targets(codex_root, expected_codex) + + if self._is_runner_enabled("claude"): + claude_root = ensure_dir(Path.home() / ".claude" / "agents") + expected_claude: set[str] = set() + for bundle in self.discover(): + claude_target = self._sync_claude_projection(bundle, claude_root) + expected_claude.add(claude_target.name) + copied_claude.append(str(claude_target)) + self._prune_bundle_targets(claude_root, expected_claude) + + if self._is_runner_enabled("kimi"): + kimi_root = ensure_dir(Path.home() / ".kimi" / "skills") + expected_kimi: set[str] = set() + for bundle in self.discover(): + kimi_target = kimi_root / f"deepscientist-{bundle.skill_id}" + expected_kimi.add(kimi_target.name) + self._sync_bundle_tree(bundle.root, kimi_target) + copied_kimi.append(str(kimi_target)) + self._prune_bundle_targets(kimi_root, expected_kimi) + + if self._is_runner_enabled("opencode"): + opencode_root = ensure_dir(Path.home() / ".config" / "opencode" / "skills") + expected_opencode: set[str] = set() + for bundle in self.discover(): + opencode_target = opencode_root / f"deepscientist-{bundle.skill_id}" + expected_opencode.add(opencode_target.name) + self._sync_bundle_tree(bundle.root, opencode_target) + copied_opencode.append(str(opencode_target)) + self._prune_bundle_targets(opencode_root, expected_opencode) + return { "codex": copied_codex, "claude": copied_claude, @@ -67,38 +84,50 @@ def sync_global(self) -> dict: def sync_quest(self, quest_root: Path, *, installed_version: str | None = None) -> dict: prompt_sync = self.sync_quest_prompts(quest_root, installed_version=installed_version) prompts_root = ensure_dir(quest_root / ".codex" / "prompts") - codex_root = ensure_dir(quest_root / ".codex" / "skills") - claude_root = ensure_dir(quest_root / ".claude" / "agents") - kimi_root = ensure_dir(quest_root / ".kimi" / "skills") - opencode_root = ensure_dir(quest_root / ".opencode" / "skills") copied_codex: list[str] = [] copied_claude: list[str] = [] copied_kimi: list[str] = [] copied_opencode: list[str] = [] - expected_codex: set[str] = set() - expected_claude: set[str] = set() - expected_kimi: set[str] = set() - expected_opencode: set[str] = set() - for bundle in self.discover(): - target = codex_root / f"deepscientist-{bundle.skill_id}" - expected_codex.add(target.name) - self._sync_bundle_tree(bundle.root, target) - copied_codex.append(str(target)) - claude_target = self._sync_claude_projection(bundle, claude_root) - expected_claude.add(claude_target.name) - copied_claude.append(str(claude_target)) - kimi_target = kimi_root / f"deepscientist-{bundle.skill_id}" - expected_kimi.add(kimi_target.name) - self._sync_bundle_tree(bundle.root, kimi_target) - copied_kimi.append(str(kimi_target)) - opencode_target = opencode_root / f"deepscientist-{bundle.skill_id}" - expected_opencode.add(opencode_target.name) - self._sync_bundle_tree(bundle.root, opencode_target) - copied_opencode.append(str(opencode_target)) - self._prune_bundle_targets(codex_root, expected_codex) - self._prune_bundle_targets(claude_root, expected_claude) - self._prune_bundle_targets(kimi_root, expected_kimi) - self._prune_bundle_targets(opencode_root, expected_opencode) + + if self._is_runner_enabled("codex"): + codex_root = ensure_dir(quest_root / ".codex" / "skills") + expected_codex: set[str] = set() + for bundle in self.discover(): + target = codex_root / f"deepscientist-{bundle.skill_id}" + expected_codex.add(target.name) + self._sync_bundle_tree(bundle.root, target) + copied_codex.append(str(target)) + self._prune_bundle_targets(codex_root, expected_codex) + + if self._is_runner_enabled("claude"): + claude_root = ensure_dir(quest_root / ".claude" / "agents") + expected_claude: set[str] = set() + for bundle in self.discover(): + claude_target = self._sync_claude_projection(bundle, claude_root) + expected_claude.add(claude_target.name) + copied_claude.append(str(claude_target)) + self._prune_bundle_targets(claude_root, expected_claude) + + if self._is_runner_enabled("kimi"): + kimi_root = ensure_dir(quest_root / ".kimi" / "skills") + expected_kimi: set[str] = set() + for bundle in self.discover(): + kimi_target = kimi_root / f"deepscientist-{bundle.skill_id}" + expected_kimi.add(kimi_target.name) + self._sync_bundle_tree(bundle.root, kimi_target) + copied_kimi.append(str(kimi_target)) + self._prune_bundle_targets(kimi_root, expected_kimi) + + if self._is_runner_enabled("opencode"): + opencode_root = ensure_dir(quest_root / ".opencode" / "skills") + expected_opencode: set[str] = set() + for bundle in self.discover(): + opencode_target = opencode_root / f"deepscientist-{bundle.skill_id}" + expected_opencode.add(opencode_target.name) + self._sync_bundle_tree(bundle.root, opencode_target) + copied_opencode.append(str(opencode_target)) + self._prune_bundle_targets(opencode_root, expected_opencode) + return { "prompts": [str(path) for path in sorted(prompts_root.rglob("*")) if path.is_file()], "prompt_sync": prompt_sync,