lshed 0.7.4 → 0.10.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 +42 -0
- package/README.md +105 -8
- package/dist/cli.js +235 -184
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 — 2026-09-04
|
|
4
|
+
|
|
5
|
+
Link instead of copy, on the machines where you edit.
|
|
6
|
+
|
|
7
|
+
- `lshed restore --link` places skills, agents, commands and instruction fragments as links into the shed. Edits in `~/.claude` land in the shed directly; `diff` and `save` have nothing to do and `lshed sync` is the whole loop. MCP entries, settings keys and the generated `CLAUDE.md` are still written as before.
|
|
8
|
+
- The choice is per machine and remembered in `state.json`: later bare `lshed restore` calls keep linking, `lshed status` shows `배치 link`, and `restore --no-link` goes back to copies. Other machines keep copying.
|
|
9
|
+
- Turning a copy into a link backs the copy up only if it differs from the shed (an unsaved edit); turning a link back into a copy backs up nothing. Switching profiles removes the links and never touches the shed behind them.
|
|
10
|
+
- Windows: directories become junctions and need no permission. Single-file parts need Developer Mode for a link; without it the file is copied, the log says so, and it behaves like any other copy.
|
|
11
|
+
|
|
12
|
+
— 2026-09-04
|
|
13
|
+
|
|
14
|
+
Profiles can build on each other.
|
|
15
|
+
|
|
16
|
+
- `extends: default` (or `extends: [a, b]`) inside a profile pulls in everything the parent lists, then adds the profile's own parts. The parent's parts come first and duplicates appear once, so instructions fragments keep a predictable order in `CLAUDE.md`. Inheritance only adds; for less than the parent, list what you want instead of extending.
|
|
17
|
+
- A parent that does not exist, or profiles that extend each other in a cycle, is a `lshed.yaml` error reported before anything is touched.
|
|
18
|
+
- Everything that reads a profile sees the resolved one: `restore`, package installs, `lshed list` (a part counts as used by profiles that inherit it), the `add` hint, and `restore <profile> --pick` (inherited parts start checked).
|
|
19
|
+
|
|
20
|
+
## 0.8.0 — 2026-09-04
|
|
21
|
+
|
|
22
|
+
Pick what a machine gets instead of writing a profile by hand.
|
|
23
|
+
|
|
24
|
+
- `lshed restore --pick` walks the shed one category at a time — packages, then skills, agents, commands, instructions, MCP servers, settings keys — and shows a checklist for each. Categories the shed has nothing in are skipped, so a shed with no packages never asks about packages. The choice is saved to `lshed.yaml` as a profile (named after the machine by default, or whatever you type) and then applied like any other `restore`. Saving is not optional: the next bare `lshed restore` reapplies the same choice, and `lshed sync` carries it to your other machines.
|
|
25
|
+
- `lshed restore <profile> --pick` starts with that profile's parts checked, so you can trim or extend an existing profile instead of starting from nothing. With no argument, the last applied profile is the starting point.
|
|
26
|
+
- On a machine with no applied profile, a bare `lshed restore --shed <dir>` in a terminal opens the picker instead of failing. In a pipe or a script it still asks for a profile name.
|
|
27
|
+
- `--dry-run` shows the plan and writes neither `lshed.yaml` nor the agent root. Naming an existing profile asks before overwriting it. Ctrl+C at any screen leaves everything as it was.
|
|
28
|
+
- Prompts come from `@clack/prompts`, bundled like the other dependencies.
|
|
29
|
+
|
|
30
|
+
## 0.7.6 — 2026-09-03
|
|
31
|
+
|
|
32
|
+
Agents organised in subdirectories were silently left out of the shed.
|
|
33
|
+
|
|
34
|
+
- Claude Code reads `~/.claude/agents/` recursively (a subagent's name comes from its frontmatter, not its path), but `init` and `add` only looked at top-level `.md` files. A machine with `agents/team/reviewer.md` restored elsewhere without it, and nothing said so. File-kind categories (`agents`, `commands`) are now scanned recursively; the id keeps the path (`team/reviewer`), so your folder layout survives the round trip and two files with the same name in different folders do not collide. Skills stay one level deep, which is what Claude Code reads at the user root.
|
|
35
|
+
- Keys accept the path form everywhere: `lshed save team/reviewer`, `lshed add agents/team/newbie`, `lshed remove agents/team/reviewer`.
|
|
36
|
+
- Verified against the real binary that the generated `CLAUDE.md` import (`@lshed/instructions/main.md`) resolves relative to the file, so instruction fragments load. That had only ever been checked by reading the generated text.
|
|
37
|
+
|
|
38
|
+
## 0.7.5 — 2026-09-03
|
|
39
|
+
|
|
40
|
+
- `init` and `add` now list what they find in a fixed order. `fs.readdir` returns entries in filesystem order, which differs between machines, so two people running `init` on the same harness got manifests whose component lists were ordered differently. `lshed.yaml` is a file you commit, so that showed up as noise in diffs.
|
|
41
|
+
- The path-comparison regression test compared a resolved path against an unresolved one and failed on macOS and Windows for the wrong reason. It now compares resolved to resolved, the way the code it guards does.
|
|
42
|
+
|
|
43
|
+
0.7.4 fixed the three real bugs the first macOS and Windows CI run found; this release fixes the test that came with it.
|
|
44
|
+
|
|
3
45
|
## 0.7.4 — 2026-09-03
|
|
4
46
|
|
|
5
47
|
The first CI run on real macOS and Windows machines found three bugs. All of them were path comparisons that only hold on Linux.
|
package/README.md
CHANGED
|
@@ -11,15 +11,35 @@ The shed is a plain directory. Put it in a git repo, Dropbox, whatever. `lshed s
|
|
|
11
11
|
|
|
12
12
|
## Why
|
|
13
13
|
|
|
14
|
-
Every new laptop, server, container or WSL box means setting up `~/.claude` again.
|
|
14
|
+
Every new laptop, server, container or WSL box means setting up `~/.claude` again. The obvious fix is to put `~/.claude` itself in git, and for many people that is the right answer.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
**When a `.gitignore` is enough.** You are one person, every machine gets the same setup, and everything in `~/.claude` is yours. Then this does the job and you should not install lshed:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
cd ~/.claude && git init
|
|
20
|
+
printf 'projects/\ncache/\nsessions/\nshell-snapshots/\nhistory.jsonl\n*.bak*\n' > .gitignore
|
|
21
|
+
git add skills agents commands CLAUDE.md settings.json .gitignore && git commit -m init
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
No new concepts, no copy step: the directory you edit is the repository. Cloning it on the next machine is the whole restore.
|
|
25
|
+
|
|
26
|
+
**Where that stops working.** The repository above starts to hurt as soon as `~/.claude` is not just yours:
|
|
27
|
+
|
|
28
|
+
- **Toolkits you installed.** One cloned toolkit here is 1.6 GB and generated 53 alias skills next to the 4 you wrote. Raw git commits all of it, or you maintain the ignore list by hand.
|
|
29
|
+
- **Secrets inside one JSON file.** MCP servers and their tokens live in `~/.claude.json` together with unrelated state. File-level ignore cannot split them, so you either commit tokens or leave MCP out.
|
|
30
|
+
- **A machine that already has a setup.** `git clone` into a non-empty `~/.claude` is a merge you do by hand, and nothing tracks which files came from the repo and which were already there.
|
|
31
|
+
- **Different subsets per machine.** A headless server wants no browser toolkit and no MCP. Branches or templates can fake this, but nothing removes the parts you no longer want when you switch.
|
|
32
|
+
- **Choosing on the machine itself.** `git clone` is all or nothing. On a new box you want to look at what the shed has, category by category, and tick what this machine needs.
|
|
33
|
+
|
|
34
|
+
lshed exists for those five cases. It keeps the shed as a plain directory in git, and adds three ideas on top:
|
|
17
35
|
|
|
18
36
|
| Idea | What it gives you |
|
|
19
37
|
|---|---|
|
|
20
|
-
| **Components** | every skill / agent / command / instruction fragment / MCP server / settings key is one named part
|
|
21
|
-
| **Profiles** | named recipes — `research`, `work`, `minimal` — that pick a subset of parts |
|
|
22
|
-
| **Managed set** | lshed remembers what it placed, so switching profiles removes only its own files and never touches yours |
|
|
38
|
+
| **Components** | every skill / agent / command / instruction fragment / MCP server / settings key is one named part; toolkits you installed are recorded as a source and version, not copied |
|
|
39
|
+
| **Profiles** | named recipes — `research`, `work`, `minimal` — that pick a subset of parts; write them in `lshed.yaml`, or let `restore --pick` build one from a checklist |
|
|
40
|
+
| **Managed set** | lshed remembers what it placed, so switching profiles or restoring onto an existing machine removes only its own files and never touches yours |
|
|
41
|
+
|
|
42
|
+
The trade: you edit in `~/.claude` and run `lshed save` to copy changes into the shed (or use `restore --link` on machines where you edit a lot, and skip the copy step), and lshed has to know Claude Code's layout, which the plain repository does not. If none of the five cases applies to you, the `.gitignore` wins.
|
|
23
43
|
|
|
24
44
|
Currently supports **Claude Code** (`~/.claude`). Other agents plug in through an adapter.
|
|
25
45
|
|
|
@@ -56,6 +76,7 @@ lshed sync # commit + push
|
|
|
56
76
|
# 3. On any other machine
|
|
57
77
|
git clone <your private repo> ~/lshed
|
|
58
78
|
lshed restore research --shed ~/lshed # --shed only needed the first time
|
|
79
|
+
lshed restore --pick --shed ~/lshed # or tick what this machine gets, category by category
|
|
59
80
|
```
|
|
60
81
|
|
|
61
82
|
## How to use it
|
|
@@ -154,6 +175,46 @@ lshed restore default --shed ~/lshed
|
|
|
154
175
|
|
|
155
176
|
Two things need you afterwards. Package `install:` commands are shell commands from a repository you cloned, so `restore` shows them and stops; run them yourself or rerun with `--yes`. MCP servers reference secrets as `${VAR}`; export the variables in your shell and Claude Code fills them in. From then on `lshed restore` with no arguments reapplies the last profile, and the shed location is remembered.
|
|
156
177
|
|
|
178
|
+
### Picking instead of naming a profile
|
|
179
|
+
|
|
180
|
+
You do not have to know the profile names, or edit `lshed.yaml`, to set up a machine. `restore --pick` walks the shed one category at a time and asks what this machine should get:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
$ lshed restore --pick --shed ~/lshed
|
|
184
|
+
창고: /home/me/lshed (packages (3), skills (4), instructions (1), mcp (1))
|
|
185
|
+
◆ packages (3) — 이 기기에 둘 것을 고르세요 (space 선택, a 전체, enter 다음)
|
|
186
|
+
│ ◼ gstack github:garrytan/gstack@main
|
|
187
|
+
│ ◻ claude-plugins-official claude-marketplace:anthropics/claude-plugins-official
|
|
188
|
+
│ ◻ exa claude-plugin:exa@claude-plugins-official
|
|
189
|
+
◆ skills (4) — 이 기기에 둘 것을 고르세요
|
|
190
|
+
│ ◼ add-drivers
|
|
191
|
+
│ ◼ domain-modeling
|
|
192
|
+
│ ◻ grilling
|
|
193
|
+
│ ◻ paper-review
|
|
194
|
+
◆ instructions (1)
|
|
195
|
+
│ ◼ main
|
|
196
|
+
◆ mcp (1)
|
|
197
|
+
│ ◻ notion
|
|
198
|
+
◆ 이 선택을 저장할 프로필 이름
|
|
199
|
+
│ lab-box
|
|
200
|
+
|
|
201
|
+
프로필 "lab-box" 을 lshed.yaml 에 저장했습니다. 다른 기기에서도 쓰려면 lshed sync 로 올리세요.
|
|
202
|
+
|
|
203
|
+
+ package gstack (clone https://github.com/garrytan/gstack.git @main → 253d1df)
|
|
204
|
+
+ skills/add-drivers
|
|
205
|
+
+ skills/domain-modeling
|
|
206
|
+
+ lshed/instructions/main.md
|
|
207
|
+
+ CLAUDE.md
|
|
208
|
+
|
|
209
|
+
프로필 "lab-box" 적용: 배치 4, 제거 0, 패키지 설치 1
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Categories the shed has nothing in (here `agents`, `commands`, `settings`) are skipped, not shown empty. The choice is always saved as a profile, named after the machine unless you type another name: that is what makes the next bare `lshed restore` reapply it, and what `lshed sync` carries to your other machines. If the shed already has a profile with that name, lshed asks before overwriting it.
|
|
213
|
+
|
|
214
|
+
`lshed restore default --pick` starts with `default`'s parts checked, so you can trim a profile for this machine instead of starting from nothing. With no argument the last applied profile is the starting point. `--dry-run` shows the plan and writes neither `lshed.yaml` nor `~/.claude`. Ctrl+C at any screen leaves everything untouched.
|
|
215
|
+
|
|
216
|
+
On a machine with no applied profile, a bare `lshed restore --shed ~/lshed` in a terminal opens the picker by itself. In a script or a pipe it asks for a profile name instead.
|
|
217
|
+
|
|
157
218
|
### Profiles
|
|
158
219
|
|
|
159
220
|
A profile is a list of ids per category. Add as many as you like to `lshed.yaml`:
|
|
@@ -184,6 +245,40 @@ Switching removes only what the previous profile placed (`-`), keeps what both u
|
|
|
184
245
|
|
|
185
246
|
Instructions fragments are ordered. `restore` writes a `CLAUDE.md` that `@`-imports each fragment, so editing a fragment in the shed shows up on the next `restore` and there is nothing to merge.
|
|
186
247
|
|
|
248
|
+
### Links instead of copies
|
|
249
|
+
|
|
250
|
+
On the machine where you do most of your editing, `restore --link` places skills, agents, commands and instruction fragments as links into the shed instead of copies. Edits in `~/.claude` land in the shed directly, `diff` has nothing to report, and `save` has nothing to do; `lshed sync` is the whole loop.
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
$ lshed restore --link
|
|
254
|
+
~ skills/add-drivers (link)
|
|
255
|
+
~ agents/reviewer.md (link)
|
|
256
|
+
~ lshed/instructions/main.md (link)
|
|
257
|
+
= CLAUDE.md
|
|
258
|
+
|
|
259
|
+
프로필 "default" 적용 (link): 배치 4, 제거 0
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The choice is per machine and remembered: later `lshed restore` calls on that machine keep linking, `lshed status` shows `배치 link`, and `restore --no-link` goes back to copies. Other machines are not affected. MCP entries and settings keys are JSON values, not files, so they are always written. Switching profiles removes the links, never the shed behind them. On Windows, directories become junctions with no special permission; single-file parts (agents, commands, fragments) need Developer Mode for a link, and without it lshed copies the file, says so, and treats it like any other copy (`save` still works for it).
|
|
263
|
+
|
|
264
|
+
A profile can build on another one with `extends`, so a machine-specific profile lists only what is different:
|
|
265
|
+
|
|
266
|
+
```yaml
|
|
267
|
+
profiles:
|
|
268
|
+
default:
|
|
269
|
+
skills: [add-drivers, domain-modeling]
|
|
270
|
+
instructions: [main]
|
|
271
|
+
laptop:
|
|
272
|
+
extends: default # everything in default, plus:
|
|
273
|
+
packages: [gstack]
|
|
274
|
+
mcp: [notion]
|
|
275
|
+
lab:
|
|
276
|
+
extends: [default] # a list works too, applied in order
|
|
277
|
+
instructions: [lab-rules] # comes after default's `main`
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Inheritance only adds. The parent's parts come first, then the profile's own, and instructions keep that order in the generated `CLAUDE.md`. To get *less* than the parent, do not extend it; list what you want. A missing parent or a cycle is reported as a `lshed.yaml` error before anything is touched, and `lshed list` counts a part as used by every profile that inherits it.
|
|
281
|
+
|
|
187
282
|
### Adding things later
|
|
188
283
|
|
|
189
284
|
Write a new skill, add an MCP server with `claude mcp add`, clone a toolkit into `~/.claude/skills/`. Then:
|
|
@@ -349,7 +444,7 @@ $ lshed add
|
|
|
349
444
|
```
|
|
350
445
|
lshed init [--shed <dir>] [--profile <name>] [--exclude <id...>]
|
|
351
446
|
lshed add [keys...] [--all] put things that appeared since init into the shed
|
|
352
|
-
lshed restore [profile] [--dry-run] [--no-backup] [--yes]
|
|
447
|
+
lshed restore [profile] [--pick] [--link | --no-link] [--dry-run] [--no-backup] [--yes]
|
|
353
448
|
lshed status applied profile, drift, packages, missing env, new things
|
|
354
449
|
lshed diff files (or JSON keys) that differ between local and shed
|
|
355
450
|
lshed save [ids...] copy local edits back into the shed
|
|
@@ -368,11 +463,13 @@ Global options: `--shed <dir>` (or `LSHED_HOME`; after the first restore lshed r
|
|
|
368
463
|
|
|
369
464
|
0. Installs any package in the profile that is missing, at the version in `lshed.lock`.
|
|
370
465
|
1. Removes paths that the **previous** profile placed and the new one doesn't need.
|
|
371
|
-
2. Copies every part of the new profile into place; writes MCP entries into `~/.claude.json` and settings keys into `settings.json`.
|
|
466
|
+
2. Copies every part of the new profile into place (or links it into the shed, with `--link` or on a machine that used it before); writes MCP entries into `~/.claude.json` and settings keys into `settings.json`.
|
|
372
467
|
3. Regenerates the instructions file.
|
|
373
468
|
|
|
374
469
|
Anything it overwrites or removes is backed up first under `~/.claude/lshed/backups/<timestamp>/`, unless you pass `--no-backup`. Files lshed never placed are left alone. `--dry-run` prints the plan and writes nothing.
|
|
375
470
|
|
|
471
|
+
With `--pick`, a checklist per non-empty category comes first (packages, skills, agents, commands, instructions, MCP servers, settings keys). `[profile]` pre-checks that profile's parts. The result is written to `lshed.yaml` as a profile, and then steps 0–3 run for it.
|
|
472
|
+
|
|
376
473
|
### What `sync` does
|
|
377
474
|
|
|
378
475
|
1. Warns if `diff` shows local edits you have not saved.
|
|
@@ -384,7 +481,7 @@ On a conflict it aborts the rebase, leaves the shed clean with your commit intac
|
|
|
384
481
|
|
|
385
482
|
### Ownership
|
|
386
483
|
|
|
387
|
-
The shed is the source of truth for authored parts: `save` copies local edits back for `file:` components. Packages are owned by their upstream: `update` pulls them, `save` ignores them.
|
|
484
|
+
The shed is the source of truth for authored parts: `save` copies local edits back for `file:` components, and a linked part is the shed. Packages are owned by their upstream: `update` pulls them, `save` ignores them.
|
|
388
485
|
|
|
389
486
|
## Where things live
|
|
390
487
|
|