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
8 changes: 8 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,13 @@
},
"enabledPlugins": {
"plugin-dev@claude-plugins-official": true
},
"extraKnownMarketplaces": {
"deepwork-plugins": {
"source": {
"source": "github",
"repo": "Unsupervisedcom/deepwork"
}
}
}
}
2 changes: 2 additions & 0 deletions doc/specs/deepwork/DW-REQ-005-cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 7 additions & 1 deletion src/deepwork/cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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).")

Expand Down
39 changes: 38 additions & 1 deletion src/deepwork/setup/claude.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Ensure ~/.claude/settings.json is configured for DeepWork."""
"""Ensure Claude Code settings are configured for DeepWork."""

from __future__ import annotations

Expand Down Expand Up @@ -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
51 changes: 51 additions & 0 deletions tests/unit/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
from deepwork.setup.claude import (
ALLOW_PERMISSIONS,
MARKETPLACE_KEY,
MARKETPLACE_SOURCE,
PLUGIN_KEY,
claude_project_setup,
claude_setup,
)

Expand Down Expand Up @@ -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."""

Expand Down
Loading