soul.demarkus.io/plans/claude-code-plugin.md/v2 draft reader meta

Plan: Claude Code Plugin

Make demarkus usable in Claude Code with a single marketplace install. No manual server setup, no token generation, no .mcp.json hand editing. The plugin reuses the existing demarkus server/client/protocol surface — no core code changes are required to ship it.

Goal

After /plugin marketplace add latebit-io/demarkus and /plugin install demarkus-memory, the user has a working memory agent. mark_fetch, mark_publish, mark_append, and the rest of the MCP tool set are available. A local demarkus-server backs them. Memory persists across sessions. The TUI and browser can hit the same server on the same machine.

Non-Goals

  • Cross-machine sync of the local soul. Users who want that run a networked server and reconfigure the plugin.
  • Windows native. WSL only, matching the rest of demarkus.
  • Embedded (in-process) MCP mode. We keep server and client as separate processes; the plugin just automates the spin-up.
  • Remote marketplace hosting. The plugin lives in the monorepo and is published via a marketplace.json at the repo root.
  • Core code changes. The plugin is built entirely on existing primitives; any need for a new flag or endpoint must be discussed and justified before landing.

User Flow

  1. /plugin marketplace add latebit-io/demarkus
  2. /plugin install demarkus-memory@demarkus
  3. Postinstall downloads platform-specific binaries to ${CLAUDE_PLUGIN_ROOT}/bin/
  4. On the next session, a SessionStart hook ensures the server is up and the token exists
  5. The agent has the full MCP tool set pointed at mark://localhost:<port>

No config files to touch, no prompts, no decisions forced on the user.

Architecture

Process Lifecycle: Lazy Spawn

The SessionStart hook:

  • Checks for an existing server with pgrep -f "demarkus-server.*\.demarkus/soul"; if running, noop
  • Otherwise spawns it detached: ${CLAUDE_PLUGIN_ROOT}/bin/demarkus-server -root ~/.demarkus/soul -port $PORT -log-file ~/.demarkus/soul/.log & followed by disown
  • Server persists across sessions; the next session reuses it

No service manager (launchd, systemd user unit) in v1. If the process model proves fragile later, promote to a user service.

Content Root

~/.demarkus/soul/ is the default content directory. Created on first run. All versioning, graph data, tokens, and logs live under it.

Port Handling

Default port: 6310. On every SessionStart, use primitives the server already exposes:

  1. Try FETCH /health against mark://127.0.0.1:6310
  2. If it succeeds (QUIC handshake completes with ALPN "mark" and /health returns OK), treat the port as "a demarkus server we can use" and reuse it. Any other service would fail the ALPN negotiation, so there is no meaningful risk of mistaking a non-demarkus process for ours.
  3. If the handshake fails or the port is unreachable, pick the next free port in a small range, spawn our server there, and write the chosen port to ~/.demarkus/soul/.port
  4. The MCP client reads the port from .port on startup (falls back to 6310 if absent)

No server-side changes needed — /health and ALPN "mark" are already in the protocol.

Auth

On first run the hook generates a token via demarkus-token generate, scoped /* with ops publish,archive. The existing CLI writes the raw token to stdout and context messages to stderr, so the hook captures it with shell redirection and restricts permissions:

demarkus-token generate -label claude-code-plugin -paths "/*" -ops "publish,archive" \
  -tokens ~/.demarkus/soul/tokens.toml \
  2>/dev/null > ~/.demarkus/soul/.token
chmod 600 ~/.demarkus/soul/.token

The MCP client picks up the token via the existing DEMARKUS_AUTH environment variable, which tokens.Resolve already honors. The session-start.sh hook exports it before Claude Code launches the MCP process:

export DEMARKUS_AUTH="$(cat ~/.demarkus/soul/.token)"

No server-side or client-side changes needed — tokens.Resolve, demarkus-token generate, and the DEMARKUS_AUTH env var are all already shipped.

First-Run Seeding

The plugin ships a seed/index.md template. The SessionStart hook copies it to ~/.demarkus/soul/index.md only when the content root is empty. The agent expands from there.

Minimal seed:

# My Soul

Personal memory for Claude Code agents.

## Sections

- [Journal](/journal.md) — session notes
- [Notes](/notes.md) — anything worth remembering

Plugin Layout

plugins/claude-code/
  .claude-plugin/plugin.json
  hooks/
    postinstall.sh      # download + verify binaries
    session-start.sh    # ensure server, ensure token, export DEMARKUS_AUTH, seed index
  .mcp.json             # demarkus-mcp -host mark://localhost:<port>
  commands/
    soul.md             # /soul → display /index.md
    soul-journal.md     # /soul-journal <entry> → mark_append /journal.md
  skills/
    memory/SKILL.md     # triggers on remember/save/note/recall
  seed/
    index.md

At the monorepo root: .claude-plugin/marketplace.json listing the plugin and pointing at plugins/claude-code/.

Binary Distribution

postinstall.sh:

  1. Detects platform via uname -sm (darwin/arm64, darwin/amd64, linux/amd64, linux/arm64)
  2. Reads the plugin version from plugin.json
  3. Downloads the three binaries from https://github.com/latebit-io/demarkus/releases/download/v<version>/...
  4. Verifies SHA256 checksums against a bundled checksums file
  5. Places binaries in ${CLAUDE_PLUGIN_ROOT}/bin/ and chmod +x them

Binaries required: demarkus-server, demarkus-mcp, demarkus-token. GoReleaser already produces these per platform; the only possible pipeline tweak is ensuring the release assets include a consolidated SHA256SUMS file. Confirm the GoReleaser config before hardcoding download URLs.

Core Code Changes

None. Every piece of plumbing the plugin needs already exists:

  • Token injection: DEMARKUS_AUTH env var (honored by tokens.Resolve)
  • Server identity / port probe: /health endpoint + ALPN "mark" negotiation
  • Writing a generated token to a file: shell redirection of demarkus-token generate stdout + chmod 600

If any future iteration of the plugin surfaces a genuine need for a new core primitive, discuss and justify it before landing. Plugin-driven polish is not a reason to touch core.

Commands and Skills

Slash Commands

  • /soulmark_fetch /index.md and render
  • /soul-journal <entry>mark_append /journal.md with the entry, auto-resolving expected_version

Skill: Memory

Triggers on phrases like "remember this", "save to memory", "what do I know about X", "note that", "recall".

  • Read intents → mark_fetch, then mark_backlinks or mark_graph for exploration
  • Write intents → mark_append /notes.md by default, or mark_publish to a topic page when the agent decides the note deserves its own document

Release Pipeline

  • Plugin version tags follow the monorepo convention: claude-code-plugin/v0.1.0
  • Releases are cut from main; the marketplace.json is updated in the same commit
  • Users who already added the marketplace get the update via /plugin marketplace update

Open Questions and Deferred Work

  • Service installer: launchd plist / systemd --user unit. Deferred. Lazy spawn is enough for v1.
  • Binary auto-update: v1 downloads on postinstall only. A later version can verify against latest on SessionStart and prompt.
  • Cross-machine access: document the path for users who want to run a networked server and point the plugin at it, but don't automate the migration.
  • Port collision UX: if the probe can't find a free port in the scanned range, the hook should surface a clear error rather than silently fail.

Phasing

Phase 1: Core plugin (ships usable v1)

  1. Write postinstall.sh (detect platform, fetch binaries from GitHub releases, verify checksums)
  2. Write session-start.sh (probe 6310, spawn server if absent, generate token on first run, export DEMARKUS_AUTH, seed index.md)
  3. Author .mcp.json, plugin.json, seed index.md
  4. Publish .claude-plugin/marketplace.json at the monorepo root
  5. Manual end-to-end test on darwin/arm64 and linux/amd64

Phase 2: Agent surface

  1. Slash commands (/soul, /soul-journal)
  2. Memory skill
  3. Documentation for migrating to a networked server

Phase 3: Polish

  1. Optional user-level service installer
  2. Binary update flow on SessionStart
  3. Better port-collision error messaging

Success Criteria

  • Fresh Claude Code install + marketplace add + plugin install → a working mark_fetch /index.md on first session with zero prompts
  • Existing demarkus users can disable the plugin-managed server and point MCP at their own server via a documented override
  • Plugin works offline on subsequent sessions (no network needed once binaries are cached)
  • Total install time under 10 seconds on typical broadband
  • Zero changes to protocol/, server/, or client/ modules outside plugins/claude-code/
trail
  1. soul.demarkus.io v2