lshed 0.17.1 → 0.17.3
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 +15 -0
- package/README.ko.md +9 -1
- package/README.md +10 -1
- package/dist/cli.js +190 -186
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.3 — 2026-09-11
|
|
4
|
+
|
|
5
|
+
- Fix: a failed `restore --yes` reported success whenever its output went through a pipe. `lshed restore prof --yes | head` (or into a pager you quit, or `| tee log` in CI) made the reader close first, stdout raised `EPIPE`, and the handler answered with `process.exit(0)` — which hard-set the status to 0 and threw away the `exit 1` that 0.17.2 had just made meaningful. lshed now drops the writes and keeps going, so the command finishes its work and exits with the code it earned.
|
|
6
|
+
- Fix: `lshed check` could hang past its `--timeout` and never come back. It waited for the child's `close` event, which only arrives once every pipe is closed — and on Windows `kill()` ends `cmd.exe` alone, leaving the `claude.cmd` → node grandchild alive with lshed's pipes in hand. A CLI that ignores the kill signal did the same thing on any OS. `ask` now kills the whole process tree on Windows (`taskkill /T`), and after the process ends it waits out a short grace period and then settles regardless, releasing the pipes. This also matters for what a hang left behind: `check`'s cleanup never ran, so the throwaway `lshed-check` skill stayed in the skills folder and every later `check` refused to start with `already exists`.
|
|
7
|
+
- Fix: 0.17.2 stopped an `install:` command from aborting a restore, but the package installers themselves — `git clone`, `claude plugin install`, `claude plugin marketplace add` — still threw, and they run *before* the placement loop. So an unreachable git host, a missing `claude` CLI or a force-pushed lock commit still ended a restore with no part placed and no profile recorded: the exact symptom the last release set out to remove, reached through a different door. A package that cannot be set up is now reported under its own name (`! package failed: …`), the restore places everything that does not depend on it, records the profile, lists the failures at the end, and exits 1.
|
|
8
|
+
- Fix: the `DEP0190` deprecation warning was still printed by `lshed report` on Windows and Node ≥ 22.9. The last release converted the two `spawn` sites it had seen fail by hand but missed `toolVersion`'s `execFile`, which also passes an argument array together with `shell: true`. All three now go through one `invocation()` helper, so there is a single place to get this right rather than a pattern to remember at each call site.
|
|
9
|
+
- `lshed check` now survives a cleanup it cannot finish. On Windows a CLI that leaves a child behind keeps lshed's temporary working directory open, and `rmdir` answered `EBUSY` — from inside the `finally`, so the error replaced the whole result and the `(timed out)` line and every attempt excerpt were lost. Removal is retried briefly, the skill folder is removed even when the temporary folder is stuck, and anything still left is reported as its own line (`! could not remove … — something still has it open.`) under a result that is otherwise intact.
|
|
10
|
+
- Fix: the Windows command line lshed builds escaped arguments for the executable's own parser but not for the `cmd.exe` that reads the line first. An argument holding `&`, `|`, `<`, `>`, `^`, `(` or `)` was passed through bare whenever it had no spaces, and an argument holding a double quote broke the quote parity `cmd` counts, so a metacharacter after it became a live operator. Arguments are now quoted whenever either parser would care, inner quotes are written `""` (which both parsers read as a literal quote), and a newline — which nothing can quote away from `cmd` — is refused. A round-trip test spawns a real child process and compares the arguments it received, so Windows CI checks the shell path end to end for the first time; the smoke suite adds the failed install and the closed pipe.
|
|
11
|
+
|
|
12
|
+
## 0.17.2 — 2026-09-09
|
|
13
|
+
|
|
14
|
+
- Fix: `restore --yes` stopped at the first `install:` command that failed, before any part was placed and before the profile was recorded. The fifth Windows pass hit it: gstack's `./setup` is an sh script, cmd.exe answered `'.' is not recognized`, and the run ended with exit 1, nine parts unplaced and no state written. A failing command now prints `! install failed: …` under the package, the restore goes on to place everything and record the profile, the failed commands are listed at the end (`N install commands failed. Everything else was placed.`), and `restore` and `update` exit 1 so a script still notices.
|
|
15
|
+
- Fix: on Windows, Node 24 printed a `DEP0190` deprecation warning on every `claude plugin install` and every `lshed check`, because the `.cmd` wrapper was spawned through the shell with an argument array. lshed now builds the one-line command itself, quoting arguments the way CreateProcess reads them, and spawns that.
|
|
16
|
+
- The `--yes` help said install commands are "shown, not run" without it, while plugin installs always run through Claude Code's own package manager; the text now says which commands the flag gates. The README's troubleshooting section says what to do when `restore --yes` reports a failed install on Windows (run it from Git Bash, and check the package's own requirements: gstack needs bun).
|
|
17
|
+
|
|
3
18
|
## 0.17.1 — 2026-09-09
|
|
4
19
|
|
|
5
20
|
- Security: a shed you `restore` could reach outside the agent's own root. A component `id` or a package `into` of `../../.bashrc` passed the manifest check, because the id pattern allowed a `..` path segment and `into` only forbade a leading slash, so `restore` would overwrite files anywhere under the home directory (a login file, an SSH `authorized_keys`, an autostart entry). The manifest now rejects `.` and `..` path segments in ids and in `into`, and the git installer refuses a package path that resolves outside the agent root. Everything a shed places stays under the root it targets.
|
package/README.ko.md
CHANGED
|
@@ -174,7 +174,7 @@ Some entries need environment variables that are not set. Secrets never go in th
|
|
|
174
174
|
mcp:notion: NOTION_AUTHORIZATION
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
그 뒤 손이 가는 것은 둘입니다. 패키지의 `install:`은 clone해 온 저장소의 셸 명령이므로 `restore`는 보여 주고 멈춥니다. 직접 돌리거나 `--yes`로 다시 실행하세요. MCP 서버는 시크릿을 `${VAR}`로 참조하니 셸에서 export하면 Claude Code가 채웁니다. 이후로는 인자 없는 `lshed restore`가 마지막 프로필을 다시 적용하고, 창고 위치도 기억합니다.
|
|
177
|
+
그 뒤 손이 가는 것은 둘입니다. 패키지의 `install:`은 clone해 온 저장소의 셸 명령이므로 `restore`는 보여 주고 멈춥니다. 직접 돌리거나 `--yes`로 다시 실행하세요. `--yes`로 돌린 명령이 실패해도 복원은 멈추지 않습니다. 부품은 그대로 놓이고, 실패는 끝에 모아 보여 주며, `restore`는 exit 1로 끝납니다. MCP 서버는 시크릿을 `${VAR}`로 참조하니 셸에서 export하면 Claude Code가 채웁니다. 이후로는 인자 없는 `lshed restore`가 마지막 프로필을 다시 적용하고, 창고 위치도 기억합니다.
|
|
178
178
|
|
|
179
179
|
### 프로필 이름 대신 골라서 넣기
|
|
180
180
|
|
|
@@ -260,6 +260,12 @@ profiles:
|
|
|
260
260
|
|
|
261
261
|
상속은 더하기만 합니다. 부모의 부품이 먼저, 자기 것이 뒤에 오고, 지침도 그 순서로 `CLAUDE.md`에 들어갑니다. 부모보다 *적게* 가지려면 상속하지 말고 원하는 것을 직접 적으세요. 없는 부모나 순환은 아무것도 건드리기 전에 `lshed.yaml` 오류로 알리고, `lshed list`는 상속받는 프로필도 그 부품을 쓰는 것으로 셉니다.
|
|
262
262
|
|
|
263
|
+
헷갈리기 쉬운 세 가지가 있는데, 일부러 나눠 두었습니다.
|
|
264
|
+
|
|
265
|
+
- **안 쓰되 남겨 두기** — 그 id 를 프로필에서 빼기만 합니다. `restore` 가 이 기계에서 치우고(백업) 부품은 창고에 남아, 다른 프로필은 계속 쓰고, id 를 다시 넣어 restore 하면 돌아옵니다. 되돌릴 수 있는 일상적 "제거"이고, 이걸 위해 창고에서 무엇을 뺄 필요는 없습니다.
|
|
266
|
+
- **상속받은 목록을 줄이기** — `extends` 는 더하기만 하므로, 프로필을 상속하면서 그중 한 부품만 빼는 것은 안 됩니다. 작은 목록을 그냥 나열하거나, 공용 base 를 갈라 그것을 상속하세요. "빼기가 없다"는 말은 오직 이 뜻입니다.
|
|
267
|
+
- **창고에서 부품을 삭제하기** — `lshed remove <id>` (어떤 프로필이 아직 그 부품을 나열하면 거부하니 먼저 프로필에서 빼세요) 또는 아무 프로필도 안 쓰는 것을 치우는 `lshed prune`. 영구적이며, 창고에서 파일을 실제로 없애는 유일한 경우입니다.
|
|
268
|
+
|
|
263
269
|
### 다른 에이전트도 같은 창고로
|
|
264
270
|
|
|
265
271
|
Codex, Gemini CLI, Copilot CLI, Cursor, Google Antigravity(`agy`)는 모두 `<자기 설정 디렉터리>/skills/<이름>/SKILL.md`를 읽습니다. Claude Code와 같은 배치이고, Antigravity를 뺀 나머지는 공용 `~/.agents/skills/`도 읽습니다. 그래서 창고 하나로 전부를 채울 수 있습니다. 대상은 `--agent`로 고릅니다. Codex의 스킬은 Codex 문서가 정한 위치인 `~/.agents/skills`에 놓으므로, 한 기기에서 스킬은 `--agent codex`와 `--agent agents` 중 하나로만 넣으세요.
|
|
@@ -542,6 +548,7 @@ lshed report [--open | --url] 이슈에 붙여 넣을 이 설
|
|
|
542
548
|
|
|
543
549
|
- 테스트, CLI 스모크, 단독 실행파일이 push마다 **Ubuntu, macOS, Windows**(Node 20, 22)에서 돕니다. Windows는 `--link`에 junction을, 플러그인 설치에 `claude.cmd`를 씁니다. 스모크에는 한글 이름 스킬이 들어 있어, macOS의 파일 이름 정규화 차이나 Windows의 코드페이지 문제는 사용자 기기가 아니라 CI에서 먼저 실패합니다.
|
|
544
550
|
- 개발자 모드가 꺼진 실제 Windows 11 PC(PowerShell 7과 cmd.exe, Node 24, 공백과 한글이 든 경로)에서도 npm 으로 설치한 lshed 0.15.1 로 같은 절차가 통과했습니다. `init`, 이미 설정이 있는 기기로의 `restore`, `--link`(스킬은 junction, 단일 파일은 안내와 함께 복사), `add`/`diff`/`save`, 프로필 전환, `codex`·`agents` 대상, 원격 없는 `sync`, 스모크까지입니다. 같은 PC 에서 두 차례 더 확인했습니다. 0.15.2 는 거기서 만든 창고가 WSL 안에서 `/home/…/` 경로로 복원되고 옛 창고가 `save` 한 번으로 정규화되는 것을, 0.15.3 은 링크 모드가 복사로 놓았던 파일 부품을 `=` 로 안정시키고 창고가 바뀔 때만 다시 복사하는 것을 확인했습니다.
|
|
551
|
+
- 같은 PC 에서 다섯 번째로(Windows PowerShell 5.1, Node 24, git 2.55, npm 으로 설치한 lshed 0.17.1) 빈 상태부터 다시 걸었습니다. private 창고를 HTTPS 로 clone 하고 `restore default` 를 돌리자 모든 패키지가 락에 적힌 리비전으로 왔고(Linux 기기에서 올린 gstack 업데이트가 같은 커밋으로 도착), `status` 는 아홉 패키지 모두 in sync, `lshed check` 는 첫 시도에 Claude Code 가 암호어를 돌려줬으며, `report` 는 홈 디렉터리를 `~` 로 가렸습니다. 이 검증이 `restore --yes` 가 첫 `install:` 실패(gstack 의 `./setup` 은 cmd.exe 에서 시작조차 못 함)에서 아무것도 놓지 않고 멈추는 것을 찾았고, 0.17.2 가 고친 뒤 같은 PC 에서 확인했습니다. 실패는 찍히고, 부품 아홉은 놓이고, 실패한 명령은 끝에 목록으로 나옵니다.
|
|
545
552
|
- 개발하는 Linux 기기에서는 CI 너머까지 전체 명령을 돌려 봅니다. `install:`이 있는 git·GitHub 패키지의 `restore`와 `update`, 충돌까지 포함한 실제 원격과의 `sync`, `remove`/`prune`, 모든 `--agent` 대상, 환경변수 기본값, 실제 터미널을 거친 `restore --pick`, 컴파일된 Linux 바이너리까지입니다.
|
|
546
553
|
- 다른 에이전트는 문서만이 아니라 도구 자체로 확인합니다. `scripts/vm/probe.sh`는 임시 창고를 도구의 실제 루트에 복원한 뒤, 스킬에 든 암호어, 지침 파일에 든 코드워드, `--link` 링크를 거친 같은 스킬을 비대화형으로 물어봅니다. Codex 0.153.4와 Antigravity CLI 1.1.27은 모든 검사를 통과했고(마지막 실행 2026-09-09, lshed 0.15.5), Gemini CLI·Copilot CLI·Cursor는 아직 파일 배치와 형식까지만 확인했습니다. 자세한 내용과 새 VM에서 전부 돌리는 cloud-init 파일은 `scripts/vm/README.md`에 있습니다.
|
|
547
554
|
- 개발하는 기기에서는 실제 창고 하나를 매일 씁니다. Claude Code, Codex, Antigravity가 모두 `--link`로 그 창고를 읽고, 셋 다 `status`에 드리프트가 없습니다.
|
|
@@ -564,6 +571,7 @@ lshed report [--open | --url] 이슈에 붙여 넣을 이 설
|
|
|
564
571
|
- **`status`가 같은 새 항목을 계속 보여 준다** — 설치기 별칭이거나 임시 파일입니다. `lshed.yaml`의 `exclude:`에 넣으세요.
|
|
565
572
|
- **restore가 MCP 변수가 없다고 한다** — 셸 프로필에서 export하고 Claude Code를 다시 시작하세요. `~/.claude.json`의 자리표시자는 맞게 들어간 것이고, Claude Code가 시작할 때 채웁니다.
|
|
566
573
|
- **restore가 훅 경로를 엉뚱하게 썼다** — 창고는 홈 경로를 `${HOME}/…`로 담습니다. 이 기기에서 다른 곳을 가리켜야 하면 창고의 JSON을 `${HOME}`이나 다른 변수로 고치고 다시 `restore`하세요. 홈 밖의 경로(`D:\tools\x.exe`, `/opt/x`)는 쓴 그대로 옮겨지며, 휴대성은 사용자 몫입니다.
|
|
574
|
+
- **Windows에서 `restore --yes`가 install 명령이 실패했다고 한다** — Windows에서 `install:`은 cmd.exe로 돌기 때문에 sh용으로 쓴 `./setup`은 시작조차 못 합니다(`'.' is not recognized`). 나머지는 다 놓였으니 Git Bash에서 그 명령을 직접 돌리고(`cd ~/.claude/skills/<패키지> && ./setup`), 패키지 자체의 요구 사항도 확인하세요(gstack은 bun이 필요합니다).
|
|
567
575
|
- **Windows에서 `--link`가 파일을 복사했다** — 파일 하나짜리 링크는 개발자 모드가 필요합니다. 켜고 다시 `restore`하거나, 복사본을 그대로 두세요. 이후 `restore`는 그것을 `= … (copy; …)`로 알리고 건드리지 않으며, 편집은 `save`로 되가져옵니다.
|
|
568
576
|
- **sync가 충돌로 멈췄다** — `cd <창고> && git pull --rebase`, 해결, `git rebase --continue`, 그리고 다시 `lshed sync`.
|
|
569
577
|
|
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ Some entries need environment variables that are not set. Secrets never go in th
|
|
|
174
174
|
mcp:notion: NOTION_AUTHORIZATION
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
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.
|
|
177
|
+
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`. A command that fails under `--yes` does not stop the restore: the parts are still placed, the failure is listed at the end, and `restore` exits 1. 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.
|
|
178
178
|
|
|
179
179
|
### Picking instead of naming a profile
|
|
180
180
|
|
|
@@ -260,6 +260,12 @@ profiles:
|
|
|
260
260
|
|
|
261
261
|
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.
|
|
262
262
|
|
|
263
|
+
Three things are easy to confuse. They are separate on purpose:
|
|
264
|
+
|
|
265
|
+
- **Stop using a part, keep it** — leave its id out of the profile. `restore` takes it off this machine (backed up); the part stays in the shed, another profile still uses it, and putting the id back and restoring brings it back. This is the reversible, everyday "remove", and nothing has to leave the shed for it.
|
|
266
|
+
- **A smaller inherited set** — `extends` only adds, so you cannot inherit a profile and drop one part from it. List the smaller set outright, or split a shared base out and extend that. This is the only sense in which there is "no subtraction".
|
|
267
|
+
- **Delete a part from the shed** — `lshed remove <id>` (it refuses while any profile still lists the part, so take it out of profiles first) or `lshed prune` for whatever no profile uses. This is the permanent one, and the only one that removes the file from the shed.
|
|
268
|
+
|
|
263
269
|
### Other agents, same shed
|
|
264
270
|
|
|
265
271
|
Codex, Gemini CLI, Copilot CLI, Cursor and Google Antigravity (`agy`) all read skills from `<their config dir>/skills/<name>/SKILL.md`, the same layout Claude Code uses, and all but Antigravity also read the shared `~/.agents/skills/`. So one shed can serve them all. Pick the target with `--agent`. For Codex, skills go to `~/.agents/skills` because that is the location Codex documents, so use either `--agent codex` or `--agent agents` for skills on one machine, not both:
|
|
@@ -542,6 +548,8 @@ A shed is executable, not just data. `restore` places files into your agent's co
|
|
|
542
548
|
|
|
543
549
|
- Tests, a CLI smoke run and the standalone binaries run on every push on **Ubuntu, macOS and Windows** (Node 20 and 22). Windows uses junctions for `--link` and `claude.cmd` for plugin installs. The smoke run includes a skill with a Korean name, so a filename-normalization difference on macOS or a code-page problem on Windows would fail there, not on a user's machine.
|
|
544
550
|
- On a real Windows 11 PC without Developer Mode (PowerShell 7 and cmd.exe, Node 24, paths with spaces and Korean) the same walk passed with lshed 0.15.1 from npm: `init`, `restore` next to an existing setup, `--link` (junctions for skills, copies with a notice for single files), `add`/`diff`/`save`, a profile switch, the `codex` and `agents` targets, `sync` without a remote, and the smoke suite. Two follow-up passes on the same PC confirmed 0.15.2 (a shed made there restores with `/home/…/` paths inside WSL, an older shed is normalized by one `save`) and 0.15.3 (link mode settles on `=` for the file parts it had to copy, and re-copies them only when the shed changes).
|
|
551
|
+
- A fifth pass on the same PC (Windows PowerShell 5.1, Node 24, git 2.55, lshed 0.17.1 from npm) started from nothing: a private shed cloned over HTTPS, `restore default` fetched every package at the revision in the lock (a gstack update made on the Linux machine arrived at the same commit), `status` reported all nine packages in sync, `lshed check` got the passphrase back from Claude Code on the first attempt, and `report` showed the home directory as `~`. The pass found that `restore --yes` stopped at the first failing `install:` (gstack's `./setup` cannot start under cmd.exe) before placing anything; 0.17.2 fixed that and the same PC confirmed the fix: the failure is printed, the nine parts are placed, and the failed command is listed at the end.
|
|
552
|
+
- A sixth pass on the same PC (Windows PowerShell, Node 24, lshed 0.17.3 built from source) checked what the 0.17.2 review had turned up: nine arguments holding `&`, `|`, `(`, `^`, a quote and a trailing backslash came back from a real child process through `cmd.exe` unchanged, `lshed report` printed no `DEP0190` warning even under `--throw-deprecation`, `check` got the passphrase back on the first attempt and left no skill behind, a package whose clone cannot succeed no longer stops the restore, and `check` against a CLI that leaves a child holding the pipes returned in 2.2 seconds instead of hanging. It found that the cleanup after such a run hit `EBUSY` from inside a `finally` and replaced the whole result with that error; the fix in this release retries, keeps the result, and names whatever is left over.
|
|
545
553
|
- On the Linux development machine the whole command set is exercised beyond CI: git and GitHub packages with `install:` through `restore` and `update`, `sync` against a real remote including a conflict, `remove`/`prune`, every `--agent` target, the environment-variable defaults, `restore --pick` through a real terminal, and the compiled Linux binary.
|
|
546
554
|
- The other agents are checked against the tools themselves, not just their docs. `scripts/vm/probe.sh` restores a throwaway shed into a tool's real root and asks the tool, non-interactively, for a passphrase kept in a skill, a codeword kept in the instructions file, and the same skill again through a `--link` symlink. Codex 0.153.4 and Antigravity CLI 1.1.27 pass every check (last run 2026-09-09 with lshed 0.15.5); Gemini CLI, Copilot CLI and Cursor are verified for file placement and format so far. `scripts/vm/README.md` has the details and a cloud-init file for running the whole thing on a fresh VM.
|
|
547
555
|
- One real shed is in daily use on the machine this is developed on: Claude Code, Codex and Antigravity all read it through `--link`, and `status` reports no drift for any of the three.
|
|
@@ -564,6 +572,7 @@ A shed is executable, not just data. `restore` places files into your agent's co
|
|
|
564
572
|
- **`status` keeps listing the same new things** — they are installer aliases or scratch. Add them to `exclude:` in `lshed.yaml`.
|
|
565
573
|
- **restore says an MCP variable is missing** — export it in your shell profile and restart Claude Code. The placeholder in `~/.claude.json` is correct; Claude Code fills it at startup.
|
|
566
574
|
- **restore wrote a hook with the wrong path** — the shed stores home paths as `${HOME}/…`. If a command points elsewhere on this machine, edit the JSON in the shed to use `${HOME}` or another variable and `restore` again. Paths outside your home directory (`D:\tools\x.exe`, `/opt/x`) are copied as written and are your job to keep portable.
|
|
575
|
+
- **`restore --yes` says an install command failed on Windows** — `install:` runs in cmd.exe there, so a `./setup` written for sh cannot start (`'.' is not recognized`). Everything else is placed; run the command from Git Bash (`cd ~/.claude/skills/<package> && ./setup`) and check the package's own requirements (gstack needs bun).
|
|
567
576
|
- **`--link` copied a file on Windows** — single-file links need Developer Mode. Turn it on and `restore` again, or keep the copy: later restores report it as `= … (copy; …)` and leave it alone, and `save` brings edits back from it.
|
|
568
577
|
- **sync stopped on a conflict** — `cd <shed> && git pull --rebase`, resolve, `git rebase --continue`, then `lshed sync` again.
|
|
569
578
|
|