From 84b9d47e6c3a6e822b22fd1c492b072bea8c3ba6 Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Wed, 15 Apr 2026 10:25:34 -0600 Subject: [PATCH 1/2] fix: improve new_user onboarding to check dependencies and reduce prompt spam - Check for `uv` at start of onboarding, install if missing (macOS/Linux/Windows) - Guide user through /reload-plugins after uv install so MCP server starts - Verify MCP server is reachable before continuing onboarding - Warn macOS users about safe-to-dismiss TCC dialogs - Pre-grant Bash permissions for common plugin operations in setup Addresses issues #3, #10, #11, #12 from onboarding testing. Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/claude/skills/new_user/SKILL.md | 56 +++++++++++++++++++++++-- src/deepwork/setup/claude.py | 16 +++++++ tests/unit/test_setup.py | 10 ++++- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/plugins/claude/skills/new_user/SKILL.md b/plugins/claude/skills/new_user/SKILL.md index 0efe2ddb..2a4ccd50 100644 --- a/plugins/claude/skills/new_user/SKILL.md +++ b/plugins/claude/skills/new_user/SKILL.md @@ -10,15 +10,65 @@ Guide a new user through what DeepWork can do and help them get started. ## Flow -### 0. Run setup +### 0. Check dependencies and run setup -Before anything else, run the setup command to ensure the user's environment is configured: +#### 0a. Check for `uv` + +The DeepWork MCP server requires `uv` (specifically `uvx`). Check if it is installed: + +```bash +command -v uv +``` + +**If `uv` is NOT found**, install it: + +- On macOS/Linux: + ```bash + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` +- On Windows (PowerShell): + ```powershell + powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + ``` + +After installing, verify it works: +```bash +uv --version +``` + +If `uv` was just installed, set `UV_WAS_INSTALLED=true` (you will need this later). + +#### 0b. Reload if `uv` was just installed + +If `UV_WAS_INSTALLED=true` (from step 0a), the MCP server could not have started when this session began because `uvx` was missing. Use `AskUserQuestion` to tell the user: + +> I just installed `uv`, which DeepWork's MCP server needs to run. For everything to work, please type `/reload-plugins` now, then come back and tell me it's done. + +Wait for the user to confirm they have reloaded. Do not proceed until they confirm. + +#### 0c. Run setup + +Run the setup command to configure Claude Code settings (marketplace, plugin, MCP permissions, auto-update): ```bash uvx deepwork setup ``` -This configures Claude Code settings (marketplace, plugin, MCP permissions, auto-update). Proceed regardless of the output. +Proceed regardless of the output. + +#### 0d. Verify the MCP server is running + +Call `get_workflows` (using the `mcp__plugin_deepwork_deepwork__get_workflows` tool). If it succeeds, the server is healthy — continue. If it errors, tell the user: + +> The DeepWork MCP server isn't responding. This usually means `uv` isn't on your PATH or the plugin needs a restart. Try quitting Claude Code completely and reopening it, then run `/deepwork:new_user` again. + +Stop the onboarding if the server is not reachable — continuing without it will just produce more confusing errors. + +#### 0e. macOS note (macOS only) + +If the platform is macOS, briefly mention: + +> **Heads up**: during reviews or workflows that scan files, macOS may pop up permission dialogs for Photos, Dropbox, or other locations outside this project. These are safe to **deny** — DeepWork only needs access to your project directory and the review will still complete fine. ### 1. GitHub star (optional) diff --git a/src/deepwork/setup/claude.py b/src/deepwork/setup/claude.py index 9ad74c9e..e0117c16 100644 --- a/src/deepwork/setup/claude.py +++ b/src/deepwork/setup/claude.py @@ -20,6 +20,16 @@ "Write(/.deepwork/**/*)", "Edit(/.deepwork/**/*)", ] +# Bash permissions for commands the plugin routinely runs. +# Without these, each invocation triggers a permission prompt. +# The uv cache glob covers hash-varying archive paths that change on reinstall. +BASH_PERMISSIONS = [ + "Bash(deepwork:*)", + "Bash(uvx deepwork:*)", + "Bash(~/.cache/uv/archive-v0/*/bin/deepwork:*)", + "Bash(command -v uv)", + "Bash(uv --version)", +] def claude_setup() -> list[str]: @@ -78,6 +88,12 @@ def claude_setup() -> list[str]: allow.append(perm) changes.append(f"Added '{perm}' to permissions.allow") + # 5. Pre-grant Bash permissions for common plugin operations + for perm in BASH_PERMISSIONS: + if perm not in allow: + allow.append(perm) + changes.append(f"Added '{perm}' to permissions.allow") + if changes: settings_path.write_text(json.dumps(settings, indent=2) + "\n") diff --git a/tests/unit/test_setup.py b/tests/unit/test_setup.py index c9ce2b55..7c019fbf 100644 --- a/tests/unit/test_setup.py +++ b/tests/unit/test_setup.py @@ -11,6 +11,7 @@ from deepwork.cli.main import cli from deepwork.setup.claude import ( + BASH_PERMISSIONS, DEEPWORK_DIR_PERMISSIONS, MARKETPLACE_KEY, MCP_PERMISSION, @@ -40,7 +41,8 @@ class TestClaudeSetupFreshFile: # YOU MUST NOT MODIFY THIS TEST UNLESS THE REQUIREMENT CHANGES def test_creates_settings(self, claude_home: Path) -> None: changes = claude_setup() - assert len(changes) == 6 + # 1 marketplace + 1 plugin + 1 MCP + 3 dir perms + 5 Bash perms = 11 + assert len(changes) == 11 settings = _read_settings(claude_home) # marketplace registered @@ -60,6 +62,10 @@ def test_creates_settings(self, claude_home: Path) -> None: for perm in DEEPWORK_DIR_PERMISSIONS: assert perm in settings["permissions"]["allow"] + # Bash permissions for common plugin operations + for perm in BASH_PERMISSIONS: + assert perm in settings["permissions"]["allow"] + class TestClaudeSetupIdempotent: """Running setup twice should be a no-op the second time.""" @@ -103,7 +109,7 @@ def test_creates_claude_dir(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatc monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) changes = claude_setup() - assert len(changes) == 6 + assert len(changes) == 11 assert (fake_home / ".claude" / "settings.json").exists() From 69d3b9f5bee692e293e3cebd816a6f54998a70d7 Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Wed, 15 Apr 2026 11:05:29 -0600 Subject: [PATCH 2/2] refactor: consolidate setup permissions into single ALLOW_PERMISSIONS list Replaces MCP_PERMISSION, DEEPWORK_DIR_PERMISSIONS, and BASH_PERMISSIONS with one ALLOW_PERMISSIONS array and a single loop. Test assertions now derive the expected count from the array length instead of hardcoding. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/deepwork/setup/claude.py | 31 ++++++++----------------------- tests/unit/test_setup.py | 21 ++++++--------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/deepwork/setup/claude.py b/src/deepwork/setup/claude.py index e0117c16..974bf42c 100644 --- a/src/deepwork/setup/claude.py +++ b/src/deepwork/setup/claude.py @@ -11,19 +11,15 @@ "repo": "Unsupervisedcom/deepwork", } PLUGIN_KEY = "deepwork@deepwork-plugins" -MCP_PERMISSION = "mcp__plugin_deepwork_deepwork__*" -# Permissions granting full access to .deepwork/ in every project. -# Leading slash makes the path project-root-relative (per Claude Code docs), -# so these rules apply to `.deepwork/**/*` in each project, not `~/.deepwork/`. -DEEPWORK_DIR_PERMISSIONS = [ +# All permissions to add to settings.permissions.allow. +ALLOW_PERMISSIONS = [ + # MCP tools + "mcp__plugin_deepwork_deepwork__*", + # .deepwork/ directory access (leading slash = project-root-relative) "Read(/.deepwork/**/*)", "Write(/.deepwork/**/*)", "Edit(/.deepwork/**/*)", -] -# Bash permissions for commands the plugin routinely runs. -# Without these, each invocation triggers a permission prompt. -# The uv cache glob covers hash-varying archive paths that change on reinstall. -BASH_PERMISSIONS = [ + # Bash commands the plugin routinely runs (uv cache glob covers hash-varying paths) "Bash(deepwork:*)", "Bash(uvx deepwork:*)", "Bash(~/.cache/uv/archive-v0/*/bin/deepwork:*)", @@ -75,21 +71,10 @@ def claude_setup() -> list[str]: enabled_plugins[PLUGIN_KEY] = True changes.append(f"Enabled plugin '{PLUGIN_KEY}'") - # 3. Ensure MCP tool permission is in allow list + # 3. Ensure all required permissions are in allow list permissions = settings.setdefault("permissions", {}) allow = permissions.setdefault("allow", []) - if MCP_PERMISSION not in allow: - allow.append(MCP_PERMISSION) - changes.append(f"Added '{MCP_PERMISSION}' to permissions.allow") - - # 4. Ensure full access to .deepwork/ in every project - for perm in DEEPWORK_DIR_PERMISSIONS: - if perm not in allow: - allow.append(perm) - changes.append(f"Added '{perm}' to permissions.allow") - - # 5. Pre-grant Bash permissions for common plugin operations - for perm in BASH_PERMISSIONS: + for perm in ALLOW_PERMISSIONS: if perm not in allow: allow.append(perm) changes.append(f"Added '{perm}' to permissions.allow") diff --git a/tests/unit/test_setup.py b/tests/unit/test_setup.py index 7c019fbf..1eb1447d 100644 --- a/tests/unit/test_setup.py +++ b/tests/unit/test_setup.py @@ -11,10 +11,8 @@ from deepwork.cli.main import cli from deepwork.setup.claude import ( - BASH_PERMISSIONS, - DEEPWORK_DIR_PERMISSIONS, + ALLOW_PERMISSIONS, MARKETPLACE_KEY, - MCP_PERMISSION, PLUGIN_KEY, claude_setup, ) @@ -41,8 +39,8 @@ class TestClaudeSetupFreshFile: # YOU MUST NOT MODIFY THIS TEST UNLESS THE REQUIREMENT CHANGES def test_creates_settings(self, claude_home: Path) -> None: changes = claude_setup() - # 1 marketplace + 1 plugin + 1 MCP + 3 dir perms + 5 Bash perms = 11 - assert len(changes) == 11 + # 1 marketplace + 1 plugin + len(ALLOW_PERMISSIONS) permissions + assert len(changes) == 2 + len(ALLOW_PERMISSIONS) settings = _read_settings(claude_home) # marketplace registered @@ -55,15 +53,8 @@ def test_creates_settings(self, claude_home: Path) -> None: # plugin enabled assert settings["enabledPlugins"][PLUGIN_KEY] is True - # MCP permission - assert MCP_PERMISSION in settings["permissions"]["allow"] - - # .deepwork directory permissions (project-relative via leading slash) - for perm in DEEPWORK_DIR_PERMISSIONS: - assert perm in settings["permissions"]["allow"] - - # Bash permissions for common plugin operations - for perm in BASH_PERMISSIONS: + # all permissions present + for perm in ALLOW_PERMISSIONS: assert perm in settings["permissions"]["allow"] @@ -109,7 +100,7 @@ def test_creates_claude_dir(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatc monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) changes = claude_setup() - assert len(changes) == 11 + assert len(changes) == 2 + len(ALLOW_PERMISSIONS) assert (fake_home / ".claude" / "settings.json").exists()