Skip to content

feat(cli): add mureo install-desktop for Claude Desktop onboarding#75

Merged
hyoshi merged 3 commits intomainfrom
feat/install-desktop-cmd
May 1, 2026
Merged

feat(cli): add mureo install-desktop for Claude Desktop onboarding#75
hyoshi merged 3 commits intomainfrom
feat/install-desktop-cmd

Conversation

@hyoshi
Copy link
Copy Markdown
Collaborator

@hyoshi hyoshi commented May 1, 2026

Summary

Phase 2-1 of the non-engineer onboarding roadmap. Adds mureo install-desktop to wire mureo into Claude Desktop chat in a single command.

Why

Today, getting mureo running in Claude Desktop chat requires four manual steps that each have their own footgun:

  1. Find ~/Library/Application Support/Claude/claude_desktop_config.json
  2. Edit JSON by hand (the GUI strips fields like cwd on save)
  3. Discover that Claude Desktop ignores the cwd field anyway and the MCP process inherits cwd=/, which is read-only on macOS
  4. Write a shell wrapper that cds into the workspace before exec'ing the MCP server

A non-engineer cannot reasonably be expected to do any of those. This command does all four at once.

What it does

Step Detail
1. Workspace mkdir -p ~/mureo (or --workspace PATH)
2. Wrapper Generates ~/.local/bin/mureo-mcp-wrapper.sh with chmod 0o755, paths shlex.quote-d so spaces/metacharacters cannot break the script
3. Backup Snapshots existing claude_desktop_config.json to <name>.bak.<UTC-microsecond-timestamp> before any mutation
4. Config merge Adds the mureo MCP entry without clobbering other servers (filesystem, github, ...) or top-level keys (preferences, ...)
5. Atomic write tempfile + os.fsync + os.replace so a disk-full or power loss mid-write cannot leave Claude Desktop without mcpServers
6. Demo (optional) --with-demo seasonality-trap seeds the workspace with a demo scenario; validated up-front so a typo cannot leave a half-populated state

Refusal cases

  • Non-macOS host (Phase 1 scope; Linux/Windows tracked as follow-up)
  • Existing mureo entry without --force
  • Corrupt JSON / non-object top-level / non-object mcpServers (refuses rather than silently overwriting hand-edited config)
  • Symlinked config (Dropbox/iCloud sync setups) — refuses to follow rather than silently writing through the link
  • Unknown --with-demo scenario — raised before any mutation

Flags

--workspace, -w  PATH    Defaults to ~/mureo
--with-demo      TEXT    seasonality-trap | halo-effect | hidden-champion | strategy-drift
--force, -f              Overwrite existing entry (backup is always saved)
--dry-run                Preview; mutates nothing

Test plan

  • 22 unit tests in tests/test_desktop_installer.py cover:
    • Fresh install (workspace, wrapper, config dir creation)
    • Existing config preserved (other MCP servers, top-level prefs)
    • Force overwrite + timestamped backup
    • Refusal when mureo entry exists without --force
    • --dry-run mutates nothing
    • Non-macOS refusal
    • --with-demo invokes demo materialise with correct args
    • Unknown demo scenario rejected before any mutation
    • Idempotence including mureo key not duplicated on re-run
    • Corrupt JSON, non-object top-level, non-object mcpServers
    • Permissive handling of {"mcpServers": null}
    • Refusal on symlinked config
    • Wrapper quotes workspace paths containing spaces
  • ruff check, black --check, mypy --strict clean on all 4 modified files
  • Broader regression run (136 tests across CLI, MCP tools, demo, BYOD) — all green
  • Smoke-tested locally: mureo install-desktop --help and --dry-run --force work
  • python-reviewer agent ran end-to-end; addressed both CRITICAL findings (shell injection via shlex.quote, non-atomic config write via tempfile + os.replace + symlink refusal) and all HIGH findings

Out of scope

  • mureo uninstall-desktop — Phase 2 follow-up PR
  • mureo doctor desktop — Phase 2 follow-up PR
  • Linux/Windows support — Phase 5 once Phase 2-4 lands
  • Skills MCP migration so /daily-check works in Desktop — Phase 3

hyoshi added 3 commits May 1, 2026 12:47
…boarding

Phase 2-1 of the non-engineer onboarding roadmap. Wires mureo into
Claude Desktop's MCP config in a single command so users no longer
need to:
  1. find ~/Library/Application Support/Claude/claude_desktop_config.json
  2. edit JSON by hand
  3. work around Claude Desktop ignoring the `cwd` field
  4. write a shell wrapper

The command takes care of all four:
  - creates the workspace (default ~/mureo)
  - generates ~/.local/bin/mureo-mcp-wrapper.sh that `cd`s into the
    workspace before exec'ing python -m mureo.mcp (with shlex-quoted
    paths so HOMEs with spaces or metacharacters do not break)
  - merges a `mureo` entry into the Desktop config without clobbering
    other MCP servers (filesystem, github, ...) or top-level prefs
  - writes the config atomically (tempfile + os.replace + fsync) so a
    disk-full or power loss mid-write cannot leave Claude Desktop
    without an mcpServers section
  - timestamped backup before any mutation

Flags: --workspace, --with-demo, --force, --dry-run.

Refuses cleanly on:
  - non-macOS (Phase 1 scope, Linux/Windows tracked as follow-up)
  - existing `mureo` entry without --force
  - corrupt JSON / non-object top-level / non-object mcpServers
  - symlinked config (Dropbox/iCloud sync setups -- surfaces the
    constraint instead of silently writing through the link)
  - unknown --with-demo scenario (validated before any mutation, so
    a typo cannot leave a half-populated workspace)

22 unit tests cover fresh install, config preservation, force
overwrite + backup, dry-run, idempotence (including key-count check),
all corrupt-config flavours, symlinked config, and shell quoting of
spaces in workspace paths.
Phase 2-1.5 of the non-engineer onboarding roadmap. Lets each Claude
Desktop workspace have its own BYOD directory so demo and real-data
setups can coexist without manual cleanup of ~/.mureo/byod/ between
them.

Changes:
  - byod_data_dir() now consults MUREO_BYOD_DIR env var first; legacy
    ~/.mureo/byod/ remains the default when the var is unset or empty
    (existing CLI / Claude Code users see no change).
  - mureo install-desktop wrapper now exports
    MUREO_BYOD_DIR=<workspace>/byod so the MCP runtime spawned by
    Claude Desktop reads/writes BYOD inside the workspace.
  - During --with-demo seeding the install process itself temporarily
    sets the same env var so materialize writes the demo bundle into
    the workspace, then restores the prior value (or unsets it if it
    was unset) to keep install_desktop clean when called as a library.

User-visible effect:
  mureo install-desktop --workspace ~/mureo-demo --with-demo seasonality-trap
  mureo install-desktop --workspace ~/mureo-real --force
  Both workspaces now have independent byod/ directories.

Tests:
  - 5 new tests in test_byod_runtime_env.py covering precedence
    (env > home), expanduser, empty-string fallback, and
    composability through manifest_path.
  - 3 new tests in test_desktop_installer.py for the wrapper export
    line, env var being set during demo seed, and proper restoration
    after seed (preserving prior value, leaving unset when initially
    unset).

The 4 production callers of byod_data_dir() (_client_factory,
byod/installer, byod/bundle, cli/byod_cmd) and 25 test sites all
call it dynamically so they pick up the override transparently.
…olve, e2e tests)

- runtime.py: reject whitespace-only MUREO_BYOD_DIR (was only rejecting empty)

- desktop_installer.py: drop redundant .resolve() (workspace is already resolved); add not-thread-safe note

- runtime.py docstring: mention install_desktop() also sets the env var

- test_byod_runtime_env.py: add whitespace-only fallback test, skip expanduser test on win32

- test_desktop_installer.py: align byod_target assertion with new unresolved path

- test_desktop_installer.py: add 2 integration tests covering real materialize and two-workspace isolation (the headline guarantee of the env-var feature)
@hyoshi hyoshi merged commit 006cc12 into main May 1, 2026
8 checks passed
@hyoshi hyoshi deleted the feat/install-desktop-cmd branch May 1, 2026 08:30
hyoshi added a commit that referenced this pull request May 1, 2026
…e EVERY commit (#76)

Codifies the rule reinforced after PR #20 (OAuth helper, 2 HIGH issues found post-hoc) and PR #75 fixup (review-response commit pushed without re-review). Fixup, lint, and review-response commits all require their own review pass — a previous review on the same branch does NOT exempt follow-up commits.

Exceptions (visual review only): docs/README/CHANGELOG, version bumps, Dependabot Actions bumps, typo fixes in comments.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant