lshed 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 — 2026-09-03
4
+
5
+ Hand-configured MCP servers travel with the profile. Secret values do not.
6
+
7
+ - New category `mcp` for Claude Code: each user-scope server in `~/.claude.json` becomes `mcp/<name>.json` in the shed. `restore` writes only `mcpServers.<name>` and leaves the rest of the file (machine ID, session state, servers you added by hand) untouched. The file is replaced atomically.
8
+ - `init` masks values under `env` and `headers` whose key looks like a secret with `${VAR}` (`EXA_API_KEY` → `${EXA_API_KEY}`, `Authorization: Bearer …` → `Bearer ${NOTION_AUTHORIZATION}`). Claude Code expands `${VAR}` from the environment in every scope, so `restore` places the placeholder verbatim and no secret passes through lshed. Verified against the real binary.
9
+ - `restore` and `status` list the variables a profile needs that are not set in the current shell.
10
+ - `diff` shows entry changes by key path; `save` keeps placeholders that still match and masks newly added secret-looking keys.
11
+ - Profile switches remove only the servers lshed placed, backing each up as JSON.
12
+ - Adapter interface: `entries()` returns categories that live as JSON entries instead of files, with `secretKeys` and `expandsEnv` policy. The default root honours `CLAUDE_CONFIG_DIR`.
13
+ - Managed-set paths for entries are written as `mcp:<name>`; a colon cannot appear in a path segment, so they never collide with files.
14
+
15
+ ## 0.3.0 — 2026-09-02
16
+
17
+ Claude Code plugins are packages now. On the machine this was built against, the five installed plugins were the only thing a fresh `restore` still left out, and two of them carry MCP servers.
18
+
19
+ - `init` records each user-scope plugin as `claude-plugin:<name>@<marketplace>` and each GitHub-backed marketplace as `claude-marketplace:<owner/repo>`. Project-scope plugins belong to their project and are skipped.
20
+ - `restore` adds missing marketplaces first, then installs missing plugins through `claude plugin install`. Both are the agent's own package manager, so they run without `--yes`; `--yes` is forwarded as `-y` to accept a marketplace-declared install command.
21
+ - Plugins cannot be pinned. `lshed.lock` records the version that actually got installed and `status` shows when it differs from what another machine had. `update` runs `claude plugin update`.
22
+ - Installers are an interface now. `github:`/`git:` live in core; an adapter contributes its own (`ClaudeCodeAdapter` provides the two above). Install order follows installer priority, then manifest order.
23
+ - Lock entries use `rev` instead of `commit`. Old locks still read.
24
+
3
25
  ## 0.2.1 — 2026-09-02
4
26
 
5
27
  - `lshed list [--unused]` shows everything in the shed and which profiles use it.
package/README.md CHANGED
@@ -64,12 +64,15 @@ components:
64
64
  instructions:
65
65
  - id: base # file:./instructions/base.md
66
66
  - id: research-style
67
+ mcp:
68
+ - id: exa # file:./mcp/exa.json — secrets replaced by ${VAR}
67
69
 
68
70
  profiles:
69
71
  research:
70
72
  skills: [paper-review]
71
73
  agents: [reviewer]
72
74
  instructions: [base, research-style] # order matters
75
+ mcp: [exa]
73
76
  teaching:
74
77
  skills: [grading-helper]
75
78
  commands: [summarize]
@@ -77,10 +80,25 @@ profiles:
77
80
  ```
78
81
 
79
82
  - Component `source` accepts `file:<path relative to the shed>`. Package `source` accepts `github:owner/repo@ref` or `git:<url>#ref`.
80
- - Category names come from the adapter. For Claude Code: `skills`, `agents`, `commands`, `instructions`.
83
+ - Category names come from the adapter. For Claude Code: `skills`, `agents`, `commands`, `instructions`, `mcp`.
81
84
  - `ignore:` at the top level adds to the built-in list of things never copied: `node_modules`, `.git`, `__pycache__`, `.venv`, cache directories, `*.log`. Build output like `dist/` is not ignored by default, since some skills ship it. Add it yourself if your parts rebuild from source.
82
85
  - Instructions are not merged. `restore` writes a `CLAUDE.md` that `@`-imports each fragment in order, so a fragment edit shows up without re-running anything. Your original `CLAUDE.md` is backed up the first time.
83
86
 
87
+ ## MCP servers and secrets
88
+
89
+ User-scope MCP servers live in `~/.claude.json`, next to machine IDs and session state. lshed treats each server as a component of category `mcp`: the shed holds `mcp/<name>.json`, and `restore` edits only the `mcpServers.<name>` key of `~/.claude.json`, leaving everything else in that file alone.
90
+
91
+ **No secret value enters the shed.** `init` replaces values under `env` and `headers` whose key looks like a secret (`key`, `token`, `secret`, `pass`, `auth`, `credential`, `cookie`, `session`) with a `${VAR}` placeholder:
92
+
93
+ ```json
94
+ { "type": "stdio", "command": "npx", "args": ["-y", "exa-mcp-server"],
95
+ "env": { "EXA_API_KEY": "${EXA_API_KEY}" } }
96
+ { "type": "http", "url": "https://mcp.notion.com/mcp",
97
+ "headers": { "Authorization": "Bearer ${NOTION_AUTHORIZATION}" } }
98
+ ```
99
+
100
+ `restore` writes the placeholder as is. Claude Code expands `${VAR}` from the environment when it starts the server, so the value only ever lives in your shell (`export EXA_API_KEY=...` in `~/.zshrc`, or however you manage secrets). `restore` and `status` list the variables the profile needs that are not set. The heuristic is a suggestion: edit the JSON in the shed to add or remove placeholders, and `init` warns when something in `args` or `url` looks like a token. `save` keeps existing placeholders and masks new secret-looking keys, so a rotated key never leaks into the shed by accident.
101
+
84
102
  ## Three kinds of things
85
103
 
86
104
  A real `~/.claude` mixes three kinds of content, and they need different handling:
@@ -108,10 +126,22 @@ profiles:
108
126
 
109
127
  `lshed.lock` pins each package to a commit, so a fresh machine gets the same version you had. `lshed update` moves it forward.
110
128
 
129
+ Claude Code plugins are packages too, with their own scheme. `init` finds them in `~/.claude/plugins`:
130
+
131
+ ```yaml
132
+ packages:
133
+ - id: claude-plugins-official
134
+ source: claude-marketplace:anthropics/claude-plugins-official
135
+ - id: exa
136
+ source: claude-plugin:exa@claude-plugins-official
137
+ ```
138
+
139
+ `restore` adds the marketplace, then runs `claude plugin install exa@claude-plugins-official`. Plugins cannot be pinned to a version, so the lock records whatever got installed and `status` tells you when it differs from the machine you came from. Plugins that bundle MCP servers bring them along.
140
+
111
141
  Rules that keep this safe:
112
142
 
113
143
  - A package that is already present is never touched by `restore`. Your local checkout is yours.
114
- - `install:` is a shell command. `restore` and `update` **print it and stop** unless you pass `--yes`.
144
+ - `install:` is a shell command. `restore` and `update` **print it and stop** unless you pass `--yes`. Plugin installs go through Claude Code's own package manager and run without it; `--yes` is forwarded as `-y` for plugins that declare an install command.
115
145
  - Packages are not part of the managed set. Switching profiles never deletes a clone.
116
146
  - Installers sometimes create aliases without symlinks, which `init` cannot tell from authored skills. Leave those out with `--exclude`:
117
147
 
@@ -156,22 +186,24 @@ The shed is the source of truth for authored parts: `save` copies local edits ba
156
186
  <shed>/
157
187
  lshed.yaml
158
188
  skills/<id>/ agents/<id>.md commands/<id>.md instructions/<id>.md
189
+ mcp/<id>.json ← secrets as ${VAR}
159
190
 
160
191
  ~/.claude/
161
192
  skills/ agents/ commands/ CLAUDE.md ← placed by restore
193
+ ~/.claude.json mcpServers.<id> ← one key per mcp component; the rest of the file is untouched
162
194
  lshed/state.json ← which profile, which paths are managed
163
195
  lshed/instructions/<id>.md ← fragments imported by CLAUDE.md
164
196
  lshed/backups/<timestamp>/ ← whatever restore replaced
165
197
  ```
166
198
 
167
- `state.json` is per machine and is not part of the shed.
199
+ `state.json` is per machine and is not part of the shed. If `CLAUDE_CONFIG_DIR` is set, lshed uses it as the root and expects `.claude.json` inside it, as Claude Code does.
168
200
 
169
201
  ## Not in scope (yet)
170
202
 
171
- - MCP servers and secrets. Planned: the manifest names the keys, values are injected locally, nothing secret enters the shed.
203
+ - Secrets beyond "name the variable". Encrypted values, `op://` references and OS keychains are possible later; today lshed is deliberately no better than dotfiles here.
204
+ - Project-scope MCP servers (`.mcp.json`, `~/.claude.json` `projects.*`). They belong to the project.
172
205
  - `settings.json` merging (hooks, permissions).
173
206
  - `sync` (a git pull/push wrapper). Use git in the shed directly for now.
174
- - Plugins installed through Claude Code's marketplace. They are packages too; recording them is next.
175
207
  - Windows and macOS have not been tested. The code avoids platform-specific paths, but treat 0.1 as Linux/WSL.
176
208
 
177
209
  ## Troubleshooting