From 294400a35440a48f758f3edfca8f40d2d675c1b5 Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Tue, 12 May 2026 13:05:23 -0600 Subject: [PATCH] feat: configure project-level marketplace in deepwork setup When Claude Code is detected, `deepwork setup` now also writes the DeepWork marketplace entry to the project's `.claude/settings.json`. This lets team members who clone the repo discover the marketplace without running setup themselves. Co-Authored-By: Claude Opus 4.6 --- .claude/settings.json | 8 +++ doc/specs/deepwork/DW-REQ-005-cli-commands.md | 2 + src/deepwork/cli/setup.py | 8 ++- src/deepwork/setup/claude.py | 39 +++++++++++++- tests/unit/test_setup.py | 51 +++++++++++++++++++ 5 files changed, 106 insertions(+), 2 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 23d968e8..2cd66f9c 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -116,5 +116,13 @@ }, "enabledPlugins": { "plugin-dev@claude-plugins-official": true + }, + "extraKnownMarketplaces": { + "deepwork-plugins": { + "source": { + "source": "github", + "repo": "Unsupervisedcom/deepwork" + } + } } } diff --git a/doc/specs/deepwork/DW-REQ-005-cli-commands.md b/doc/specs/deepwork/DW-REQ-005-cli-commands.md index accdd277..7680fe6e 100644 --- a/doc/specs/deepwork/DW-REQ-005-cli-commands.md +++ b/doc/specs/deepwork/DW-REQ-005-cli-commands.md @@ -88,3 +88,5 @@ The DeepWork CLI provides five active commands: `serve` (starts the MCP server), 6. The command MUST create `~/.claude` and `settings.json` if they do not exist. 7. When no supported platform is detected, the command MUST print a message indicating no platforms were found. 8. After all platform configuration is complete, the command MUST open `https://www.deepwork.md/success` in the user's default browser. +9. When Claude Code is detected, the command MUST also configure the project-level `.claude/settings.json` with the DeepWork marketplace in `extraKnownMarketplaces` (source `{"source": "github", "repo": "Unsupervisedcom/deepwork"}`), so team members who clone the repo get the marketplace automatically. +10. The project-level marketplace configuration MUST be idempotent and MUST preserve existing project settings. diff --git a/src/deepwork/cli/setup.py b/src/deepwork/cli/setup.py index 5c52bc6e..5ee7d721 100644 --- a/src/deepwork/cli/setup.py +++ b/src/deepwork/cli/setup.py @@ -15,7 +15,7 @@ def setup() -> None: """ claude_dir = Path.home() / ".claude" if claude_dir.is_dir(): - from deepwork.setup.claude import claude_setup + from deepwork.setup.claude import claude_project_setup, claude_setup changes = claude_setup() if changes: @@ -24,6 +24,12 @@ def setup() -> None: click.echo(f" • {change}") else: click.echo("Claude Code — already configured, no changes needed.") + + project_changes = claude_project_setup() + if project_changes: + click.echo("Claude Code — updated .claude/settings.json:") + for change in project_changes: + click.echo(f" • {change}") else: click.echo("No supported AI agent platforms detected (~/.claude not found).") diff --git a/src/deepwork/setup/claude.py b/src/deepwork/setup/claude.py index 974bf42c..67cbeb9f 100644 --- a/src/deepwork/setup/claude.py +++ b/src/deepwork/setup/claude.py @@ -1,4 +1,4 @@ -"""Ensure ~/.claude/settings.json is configured for DeepWork.""" +"""Ensure Claude Code settings are configured for DeepWork.""" from __future__ import annotations @@ -83,3 +83,40 @@ def claude_setup() -> list[str]: settings_path.write_text(json.dumps(settings, indent=2) + "\n") return changes + + +def claude_project_setup(project_dir: Path | None = None) -> list[str]: + """Configure project-level .claude/settings.json with DeepWork marketplace. + + Adds the marketplace so team members who clone the repo get it automatically. + Returns a list of human-readable messages describing what changed. + """ + root = project_dir or Path.cwd() + settings_path = root / ".claude" / "settings.json" + + if settings_path.exists(): + settings = json.loads(settings_path.read_text()) + else: + settings_path.parent.mkdir(parents=True, exist_ok=True) + settings = {} + + changes: list[str] = [] + + marketplaces = settings.setdefault("extraKnownMarketplaces", {}) + if MARKETPLACE_KEY not in marketplaces: + marketplaces[MARKETPLACE_KEY] = {"source": MARKETPLACE_SOURCE} + changes.append(f"Added '{MARKETPLACE_KEY}' to project extraKnownMarketplaces") + else: + entry = marketplaces[MARKETPLACE_KEY] + existing_source = entry.get("source", {}) + if ( + existing_source.get("source") != MARKETPLACE_SOURCE["source"] + or existing_source.get("repo") != MARKETPLACE_SOURCE["repo"] + ): + entry["source"] = MARKETPLACE_SOURCE + changes.append(f"Updated '{MARKETPLACE_KEY}' project marketplace source") + + if changes: + settings_path.write_text(json.dumps(settings, indent=2) + "\n") + + return changes diff --git a/tests/unit/test_setup.py b/tests/unit/test_setup.py index 1eb1447d..0cc4b3a5 100644 --- a/tests/unit/test_setup.py +++ b/tests/unit/test_setup.py @@ -13,7 +13,9 @@ from deepwork.setup.claude import ( ALLOW_PERMISSIONS, MARKETPLACE_KEY, + MARKETPLACE_SOURCE, PLUGIN_KEY, + claude_project_setup, claude_setup, ) @@ -104,6 +106,55 @@ def test_creates_claude_dir(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatc assert (fake_home / ".claude" / "settings.json").exists() +class TestClaudeProjectSetupFresh: + """When project .claude/settings.json does not exist yet.""" + + # THIS TEST VALIDATES A HARD REQUIREMENT (DW-REQ-005.6.9). + # YOU MUST NOT MODIFY THIS TEST UNLESS THE REQUIREMENT CHANGES + def test_creates_project_settings(self, tmp_path: Path) -> None: + changes = claude_project_setup(project_dir=tmp_path) + assert len(changes) == 1 + settings = json.loads((tmp_path / ".claude" / "settings.json").read_text()) + + assert MARKETPLACE_KEY in settings["extraKnownMarketplaces"] + src = settings["extraKnownMarketplaces"][MARKETPLACE_KEY]["source"] + assert src["source"] == "github" + assert src["repo"] == "Unsupervisedcom/deepwork" + + +class TestClaudeProjectSetupIdempotent: + """Running project setup twice should be a no-op the second time.""" + + # THIS TEST VALIDATES A HARD REQUIREMENT (DW-REQ-005.6.10). + # YOU MUST NOT MODIFY THIS TEST UNLESS THE REQUIREMENT CHANGES + def test_no_changes_on_rerun(self, tmp_path: Path) -> None: + claude_project_setup(project_dir=tmp_path) + changes = claude_project_setup(project_dir=tmp_path) + assert changes == [] + + +class TestClaudeProjectSetupPreservesExisting: + """Existing project settings are preserved when adding DeepWork entries.""" + + # THIS TEST VALIDATES A HARD REQUIREMENT (DW-REQ-005.6.10). + # YOU MUST NOT MODIFY THIS TEST UNLESS THE REQUIREMENT CHANGES + def test_existing_settings_kept(self, tmp_path: Path) -> None: + settings_path = tmp_path / ".claude" / "settings.json" + settings_path.parent.mkdir(parents=True, exist_ok=True) + existing = { + "permissions": {"allow": ["Bash(git:*)"]}, + "enabledPlugins": {"other@marketplace": True}, + } + settings_path.write_text(json.dumps(existing)) + + claude_project_setup(project_dir=tmp_path) + settings = json.loads(settings_path.read_text()) + + assert "Bash(git:*)" in settings["permissions"]["allow"] + assert settings["enabledPlugins"]["other@marketplace"] is True + assert MARKETPLACE_KEY in settings["extraKnownMarketplaces"] + + class TestSetupCLI: """Tests for the setup CLI command itself."""