lshed 0.14.1 → 0.15.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 +23 -0
- package/README.ko.md +556 -0
- package/README.md +132 -268
- package/dist/cli.js +224 -222
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# lshed
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**English** | [한국어](README.ko.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Keep your coding-agent harness — skills, subagents, commands, instructions, MCP servers, settings — in a **shed**, and restore it on any machine with one command.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
lshed init --shed ~/lshed # scan ~/.claude into a shed + write lshed.yaml
|
|
@@ -11,21 +11,14 @@ lshed restore research # apply a profile anywhere
|
|
|
11
11
|
|
|
12
12
|
The shed is a plain directory. Put it in a git repo, Dropbox, whatever. `lshed sync` wraps the git part if you want it to. Works with Claude Code, and places the same shed into Codex, Gemini CLI, Copilot CLI, Cursor, Google Antigravity and the shared `~/.agents/skills`.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
> lshed's own output is in Korean; the terminal captures below are shown as they appear.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
> 이 문서는 모든 설명을 영어 다음 한국어 순서로 두 번 적습니다. 코드 블록과 표는 하나를 같이 봅니다.
|
|
18
|
-
|
|
19
|
-
## Why · 왜 필요한가
|
|
16
|
+
## Why
|
|
20
17
|
|
|
21
18
|
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.
|
|
22
19
|
|
|
23
|
-
새 노트북, 서버, 컨테이너, WSL을 하나 늘릴 때마다 `~/.claude`를 다시 꾸며야 합니다. 가장 뻔한 해법은 `~/.claude` 자체를 git에 넣는 것이고, 많은 사람에게는 그것이 정답입니다.
|
|
24
|
-
|
|
25
20
|
**When a plain git repository is enough.** You are one person, every machine gets the same setup, and everything in `~/.claude` is yours. Then make `~/.claude` itself a git repository and you should not install lshed. Git does the moving; the `.gitignore` only keeps machine state (sessions, caches) out of it:
|
|
26
21
|
|
|
27
|
-
**`~/.claude`를 그냥 git 저장소로 두면 충분한 경우.** 혼자 쓰고, 모든 기기가 같은 구성이며, `~/.claude` 안의 것이 전부 직접 만든 것이라면 `~/.claude` 자체를 git 저장소로 만들면 끝나고, lshed를 설치할 이유가 없습니다. 옮기는 것은 git이고, `.gitignore`는 세션 기록·캐시 같은 기기 상태값을 저장소에서 빼는 제외 목록일 뿐입니다.
|
|
28
|
-
|
|
29
22
|
```
|
|
30
23
|
cd ~/.claude && git init
|
|
31
24
|
printf 'projects/\ncache/\nsessions/\nshell-snapshots/\nhistory.jsonl\n*.bak*\n' > .gitignore
|
|
@@ -34,52 +27,35 @@ git add skills agents commands CLAUDE.md settings.json .gitignore && git commit
|
|
|
34
27
|
|
|
35
28
|
No new concepts, no copy step: the directory you edit is the repository. `git push` here and `git clone <remote> ~/.claude` on the next machine is the whole restore.
|
|
36
29
|
|
|
37
|
-
새로 배울 개념도, 복사 단계도 없습니다. 편집하는 디렉터리가 곧 저장소이고, 여기서 `git push`한 뒤 다음 기기에서 `git clone <원격> ~/.claude`하는 것이 곧 복원입니다.
|
|
38
|
-
|
|
39
30
|
**Where that stops working.** The repository above starts to hurt as soon as `~/.claude` is not just yours:
|
|
40
31
|
|
|
41
|
-
**그것이 막히는 지점.** `~/.claude`가 "내가 쓴 것"만이 아니게 되는 순간 위 저장소는 아프기 시작합니다.
|
|
42
|
-
|
|
43
32
|
- **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.
|
|
44
|
-
**설치한 툴킷.** 툴킷 하나가 1.6 GB짜리 clone이고, 직접 쓴 스킬 4개 옆에 별칭 스킬 53개를 만들어 놓습니다. 날것의 git은 이것을 전부 커밋하거나, 아니면 ignore 목록을 손으로 관리해야 합니다.
|
|
45
33
|
- **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.
|
|
46
|
-
**JSON 파일 하나 안의 시크릿.** MCP 서버와 토큰이 `~/.claude.json` 안에 무관한 상태값과 함께 있습니다. 파일 단위 ignore로는 가를 수 없어, 토큰을 커밋하거나 MCP를 포기해야 합니다.
|
|
47
34
|
- **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.
|
|
48
|
-
**이미 설정이 있는 기기.** 비어 있지 않은 `~/.claude`에 `git clone`하는 것은 손으로 하는 병합이고, 어떤 파일이 저장소에서 왔고 어떤 것이 원래 있던 것인지 아무도 기억하지 않습니다.
|
|
49
35
|
- **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.
|
|
50
|
-
**기기마다 다른 부분집합.** 헤드리스 서버에는 브라우저 툴킷도 MCP도 필요 없습니다. 브랜치나 템플릿으로 흉내낼 수는 있지만, 구성을 바꿀 때 더 이상 원치 않는 부품을 치워 주는 것은 없습니다.
|
|
51
36
|
- **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.
|
|
52
|
-
**기기에서 직접 고르기.** `git clone`은 전부 아니면 전무입니다. 새 기기에서는 창고에 뭐가 있는지 카테고리별로 보고, 이 기기에 필요한 것만 체크하고 싶습니다.
|
|
53
37
|
|
|
54
38
|
lshed exists for those five cases. It keeps the shed as a plain directory in git, and adds three ideas on top:
|
|
55
39
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
| Idea · 개념 | What it gives you · 얻는 것 |
|
|
40
|
+
| Idea | What it gives you |
|
|
59
41
|
|---|---|
|
|
60
|
-
| **Components**
|
|
61
|
-
| **Profiles**
|
|
62
|
-
| **Managed set**
|
|
42
|
+
| **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. |
|
|
43
|
+
| **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. |
|
|
44
|
+
| **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. |
|
|
63
45
|
|
|
64
46
|
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 each agent's layout, which the plain repository does not. If none of the five cases applies to you, the plain repository wins.
|
|
65
47
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
## Install · 설치
|
|
48
|
+
## Install
|
|
69
49
|
|
|
70
50
|
With Node 20 or newer:
|
|
71
51
|
|
|
72
|
-
Node 20 이상이 있으면:
|
|
73
|
-
|
|
74
52
|
```
|
|
75
53
|
npm install -g lshed # or run it once: npx lshed status
|
|
76
54
|
```
|
|
77
55
|
|
|
78
56
|
Without Node, download a standalone binary from the [latest release](https://github.com/LeeSongHeon-LSH/lshed/releases/latest) and put it on your PATH. It carries its own runtime, so it is ~80 MB.
|
|
79
57
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
| Platform · 플랫폼 | File · 파일 |
|
|
58
|
+
| Platform | File |
|
|
83
59
|
|---|---|
|
|
84
60
|
| Windows x64 | `lshed-windows-x64.exe` → rename to `lshed.exe` |
|
|
85
61
|
| macOS Apple Silicon / Intel | `lshed-darwin-arm64` / `lshed-darwin-x64` |
|
|
@@ -87,107 +63,89 @@ Node가 없으면 [최신 릴리스](https://github.com/LeeSongHeon-LSH/lshed/re
|
|
|
87
63
|
|
|
88
64
|
On macOS and Linux, `chmod +x` it first. The binaries are unsigned, so macOS warns on first run. Either way you also need `git` on the PATH for packages and `sync`, and `claude` if your shed lists plugins.
|
|
89
65
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
## Quick start · 빠른 시작
|
|
66
|
+
## Quick start
|
|
93
67
|
|
|
94
68
|
```bash
|
|
95
|
-
# 1. On the machine that already has your setup
|
|
69
|
+
# 1. On the machine that already has your setup
|
|
96
70
|
lshed init --shed ~/lshed
|
|
97
71
|
cd ~/lshed && git init && git remote add origin <your private repo>
|
|
98
72
|
lshed sync # commit + push
|
|
99
73
|
|
|
100
74
|
# 2. Edit ~/lshed/lshed.yaml — add profiles, drop parts you don't need everywhere
|
|
101
|
-
# ~/lshed/lshed.yaml 을 열어 프로필을 만들고, 모든 기기에 필요하지 않은 부품은 빼세요
|
|
102
75
|
|
|
103
|
-
# 3. On any other machine
|
|
76
|
+
# 3. On any other machine
|
|
104
77
|
git clone <your private repo> ~/lshed
|
|
105
|
-
lshed restore research --shed ~/lshed # --shed only needed the first time
|
|
78
|
+
lshed restore research --shed ~/lshed # --shed only needed the first time
|
|
106
79
|
lshed restore --pick --shed ~/lshed # or tick what this machine gets, category by category
|
|
107
|
-
# 또는 카테고리별로 이 기기에 둘 것을 체크
|
|
108
80
|
```
|
|
109
81
|
|
|
110
|
-
## How to use it
|
|
82
|
+
## How to use it
|
|
111
83
|
|
|
112
|
-
### Day one: put what you have into a shed
|
|
84
|
+
### Day one: put what you have into a shed
|
|
113
85
|
|
|
114
86
|
```
|
|
115
87
|
$ lshed init --shed ~/lshed --exclude _gstack-command connect-chrome
|
|
116
|
-
|
|
117
|
-
≡ package gstack github:garrytan/gstack@main @253d1df (
|
|
118
|
-
≡ package claude-plugins-official claude-marketplace:anthropics/claude-plugins-official (
|
|
119
|
-
≡ package exa claude-plugin:exa@claude-plugins-official @3.4.1 (
|
|
120
|
-
· skills/browse (
|
|
121
|
-
· skills/review (
|
|
88
|
+
scan: /home/me/.claude → shed: /home/me/lshed
|
|
89
|
+
≡ package gstack github:garrytan/gstack@main @253d1df (reference only)
|
|
90
|
+
≡ package claude-plugins-official claude-marketplace:anthropics/claude-plugins-official (reference only)
|
|
91
|
+
≡ package exa claude-plugin:exa@claude-plugins-official @3.4.1 (reference only)
|
|
92
|
+
· skills/browse (generated by gstack → skipped)
|
|
93
|
+
· skills/review (generated by gstack → skipped)
|
|
122
94
|
- skills/_gstack-command (--exclude)
|
|
123
95
|
+ skills/add-drivers
|
|
124
96
|
+ skills/domain-modeling
|
|
125
|
-
+ mcp/notion (
|
|
97
|
+
+ mcp/notion (secrets → ${NOTION_AUTHORIZATION})
|
|
126
98
|
+ instructions/main (CLAUDE.md)
|
|
127
99
|
|
|
128
|
-
lshed.yaml
|
|
100
|
+
lshed.yaml written: /home/me/lshed/lshed.yaml (4 parts, 3 packages, 53 generated skipped, 2 excluded, profile "default")
|
|
129
101
|
```
|
|
130
102
|
|
|
131
|
-
`init` reads your agent root and writes only to the shed and `~/.claude/lshed/`. It sorts everything into [three kinds](#three-kinds-of-things
|
|
132
|
-
|
|
133
|
-
`init`은 에이전트 루트를 읽기만 하고, 쓰는 곳은 창고와 `~/.claude/lshed/`뿐입니다. 발견한 것을 [세 종류](#three-kinds-of-things--세-종류의-것)로 나눕니다. 직접 만든 것은 복사(`+`), 설치한 것은 출처와 버전만 적은 패키지(`≡`), 설치기가 만든 파일은 건너뜀(`·`)입니다. 설치기가 심볼릭 링크 없이 만든 별칭은 직접 만든 것처럼 보이므로 `--exclude`로 빼세요. 그 선택은 매니페스트의 `exclude:`에 남습니다.
|
|
103
|
+
`init` reads your agent root and writes only to the shed and `~/.claude/lshed/`. It sorts everything into [three kinds](#three-kinds-of-things): authored parts are copied (`+`), things you installed become packages recorded by source and version (`≡`), and files an installer generated are skipped (`·`). Aliases an installer created without symlinks look authored; leave them out with `--exclude`, and lshed remembers that under `exclude:` in the manifest.
|
|
134
104
|
|
|
135
105
|
Then open `lshed.yaml`. It has one profile, `default`, listing everything. Fill in `install:` for git packages that need a post-clone step, and make it a git repo:
|
|
136
106
|
|
|
137
|
-
그다음 `lshed.yaml`을 여세요. 전부를 담은 `default` 프로필 하나가 있습니다. clone 뒤 설치 단계가 필요한 git 패키지에는 `install:`을 적고, 창고를 git 저장소로 만듭니다.
|
|
138
|
-
|
|
139
107
|
```
|
|
140
108
|
cd ~/lshed && git init && git remote add origin git@github.com:me/harness.git
|
|
141
109
|
lshed sync
|
|
142
110
|
```
|
|
143
111
|
|
|
144
|
-
### Every day: edit, save, sync
|
|
112
|
+
### Every day: edit, save, sync
|
|
145
113
|
|
|
146
114
|
You edit skills where the agent reads them, in `~/.claude`. The shed does not change by itself.
|
|
147
115
|
|
|
148
|
-
스킬은 에이전트가 읽는 자리인 `~/.claude`에서 편집합니다. 창고는 저절로 바뀌지 않습니다.
|
|
149
|
-
|
|
150
116
|
```
|
|
151
|
-
lshed status # what profile is applied, what drifted, what is new
|
|
152
|
-
lshed diff # file-level differences between ~/.claude and the shed
|
|
153
|
-
lshed save # copy local edits into the shed (or: lshed save skills/add-drivers)
|
|
154
|
-
lshed sync # commit the shed, pull, push
|
|
117
|
+
lshed status # what profile is applied, what drifted, what is new
|
|
118
|
+
lshed diff # file-level differences between ~/.claude and the shed
|
|
119
|
+
lshed save # copy local edits into the shed (or: lshed save skills/add-drivers)
|
|
120
|
+
lshed sync # commit the shed, pull, push
|
|
155
121
|
```
|
|
156
122
|
|
|
157
123
|
`save` is the only path from `~/.claude` to the shed, and it only works for parts the shed owns (`file:` sources). `sync` warns if you have unsaved edits so you do not push a shed that is behind your machine.
|
|
158
124
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
### A machine that already has a setup · 이미 설정이 있는 기기
|
|
125
|
+
### A machine that already has a setup
|
|
162
126
|
|
|
163
127
|
The common case is not an empty machine: it already runs the agent and has skills, settings and MCP servers of its own. `restore` is built for that. With no prior lshed state it **removes nothing** — it places what the profile lists, and anything it overwrites goes to `~/.claude/lshed/backups/<timestamp>/` first. Parts that exist only on that machine are untouched.
|
|
164
128
|
|
|
165
|
-
빈 기기보다 흔한 것은 이미 에이전트를 쓰고 있어 자기 스킬·설정·MCP 서버가 있는 기기입니다. `restore`는 그 경우를 위해 만들어졌습니다. 이전 lshed 상태가 없으면 **아무것도 지우지 않습니다.** 프로필이 적은 것을 놓고, 덮어쓰는 것은 먼저 `~/.claude/lshed/backups/<시각>/`에 백업합니다. 그 기기에만 있는 부품은 그대로 둡니다.
|
|
166
|
-
|
|
167
129
|
```
|
|
168
130
|
$ lshed restore default --shed ~/lshed --dry-run
|
|
169
131
|
+ skills/mine
|
|
170
132
|
~ skills/shared # same name, different content → backed up, then replaced
|
|
171
133
|
+ mcp:exa (${EXA_API_KEY})
|
|
172
134
|
~ settings:model
|
|
173
|
-
(dry-run)
|
|
135
|
+
(dry-run) nothing changed. Would place 5, remove 0, back up 3
|
|
174
136
|
```
|
|
175
137
|
|
|
176
138
|
Always run `--dry-run` first. `+` is new, `~` replaces with a backup, `-` removes with a backup. If the plan looks right, drop the flag. Then push that machine's own parts up into the shed and both machines have everything:
|
|
177
139
|
|
|
178
|
-
먼저 `--dry-run`을 돌리세요. `+`는 새로 놓음, `~`는 백업 뒤 교체, `-`는 백업 뒤 제거입니다. 계획이 맞으면 플래그를 빼고 다시 돌립니다. 그다음 그 기기만의 부품을 창고에 올리면 두 기기가 같은 것을 갖게 됩니다.
|
|
179
|
-
|
|
180
140
|
```
|
|
181
|
-
lshed add # lists what this machine has that the shed does not
|
|
141
|
+
lshed add # lists what this machine has that the shed does not
|
|
182
142
|
lshed add windows-only mcp/my-local-server
|
|
183
143
|
lshed sync
|
|
184
144
|
```
|
|
185
145
|
|
|
186
146
|
Do not run `init` on such a machine just to look around. `init` claims what it finds as lshed-managed, so a later `restore` from your real shed would treat those parts as removable (backed up, but removed). Use `lshed scan`, which only prints. If you do it anyway, `restore` warns you before removing anything and `lshed add` is the way out.
|
|
187
147
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
### A new machine · 새 기기
|
|
148
|
+
### A new machine
|
|
191
149
|
|
|
192
150
|
```
|
|
193
151
|
git clone git@github.com:me/harness.git ~/lshed
|
|
@@ -197,40 +155,36 @@ lshed restore default --shed ~/lshed
|
|
|
197
155
|
```
|
|
198
156
|
+ package gstack (clone https://github.com/garrytan/gstack.git @main → 253d1df)
|
|
199
157
|
+ package claude-plugins-official (claude plugin marketplace add anthropics/claude-plugins-official)
|
|
200
|
-
+ package exa (claude plugin install exa@claude-plugins-official (
|
|
158
|
+
+ package exa (claude plugin install exa@claude-plugins-official (was 3.4.1; cannot be pinned))
|
|
201
159
|
+ skills/add-drivers
|
|
202
160
|
+ skills/domain-modeling
|
|
203
161
|
+ mcp:notion (${NOTION_AUTHORIZATION})
|
|
204
162
|
+ lshed/instructions/main.md
|
|
205
163
|
+ CLAUDE.md
|
|
206
164
|
|
|
207
|
-
|
|
165
|
+
Profile "default" applied: placed 5, removed 0, installed 3 packages
|
|
208
166
|
|
|
209
|
-
|
|
167
|
+
1 install command was not run. Check it, then rerun with '--yes' or run it yourself:
|
|
210
168
|
cd /home/me/.claude/skills/gstack && ./setup
|
|
211
169
|
|
|
212
|
-
|
|
170
|
+
Some entries need environment variables that are not set. Secrets never go in the shed, so export them in this machine's shell (e.g. in ~/.zshrc):
|
|
213
171
|
mcp:notion: NOTION_AUTHORIZATION
|
|
214
172
|
```
|
|
215
173
|
|
|
216
174
|
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.
|
|
217
175
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
### Picking instead of naming a profile · 프로필 이름 대신 골라서 넣기
|
|
176
|
+
### Picking instead of naming a profile
|
|
221
177
|
|
|
222
178
|
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:
|
|
223
179
|
|
|
224
|
-
기기를 꾸미려고 프로필 이름을 알거나 `lshed.yaml`을 고칠 필요는 없습니다. `restore --pick`은 창고를 카테고리 하나씩 보여 주며 이 기기에 둘 것을 묻습니다.
|
|
225
|
-
|
|
226
180
|
```
|
|
227
181
|
$ lshed restore --pick --shed ~/lshed
|
|
228
|
-
|
|
229
|
-
◆ packages (3) —
|
|
182
|
+
shed: /home/me/lshed (packages (3), skills (4), instructions (1), mcp (1))
|
|
183
|
+
◆ packages (3) — pick what this machine gets (space to toggle, a for all, enter for next)
|
|
230
184
|
│ ◼ gstack github:garrytan/gstack@main
|
|
231
185
|
│ ◻ claude-plugins-official claude-marketplace:anthropics/claude-plugins-official
|
|
232
186
|
│ ◻ exa claude-plugin:exa@claude-plugins-official
|
|
233
|
-
◆ skills (4) —
|
|
187
|
+
◆ skills (4) — pick what this machine gets
|
|
234
188
|
│ ◼ add-drivers
|
|
235
189
|
│ ◼ domain-modeling
|
|
236
190
|
│ ◻ grilling
|
|
@@ -239,10 +193,10 @@ $ lshed restore --pick --shed ~/lshed
|
|
|
239
193
|
│ ◼ main
|
|
240
194
|
◆ mcp (1)
|
|
241
195
|
│ ◻ notion
|
|
242
|
-
◆
|
|
196
|
+
◆ Profile name to save this selection as
|
|
243
197
|
│ lab-box
|
|
244
198
|
|
|
245
|
-
|
|
199
|
+
Profile "lab-box" saved to lshed.yaml. To use it on other machines, push it with lshed sync.
|
|
246
200
|
|
|
247
201
|
+ package gstack (clone https://github.com/garrytan/gstack.git @main → 253d1df)
|
|
248
202
|
+ skills/add-drivers
|
|
@@ -250,23 +204,17 @@ $ lshed restore --pick --shed ~/lshed
|
|
|
250
204
|
+ lshed/instructions/main.md
|
|
251
205
|
+ CLAUDE.md
|
|
252
206
|
|
|
253
|
-
|
|
207
|
+
Profile "lab-box" applied: placed 4, removed 0, installed 1 package
|
|
254
208
|
```
|
|
255
209
|
|
|
256
210
|
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.
|
|
257
211
|
|
|
258
|
-
창고에 아무것도 없는 카테고리(여기서는 `agents`, `commands`, `settings`)는 빈 화면 대신 건너뜁니다. 선택은 반드시 프로필로 저장되며, 이름을 따로 치지 않으면 기기 이름이 됩니다. 그래야 다음 번 인자 없는 `lshed restore`가 같은 것을 다시 적용하고, `lshed sync`가 다른 기기로 실어 나릅니다. 같은 이름의 프로필이 이미 있으면 덮어쓰기 전에 묻습니다.
|
|
259
|
-
|
|
260
212
|
`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. 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.
|
|
261
213
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
### Profiles · 프로필
|
|
214
|
+
### Profiles
|
|
265
215
|
|
|
266
216
|
A profile is a list of ids per category. Add as many as you like to `lshed.yaml`:
|
|
267
217
|
|
|
268
|
-
프로필은 카테고리별 id 목록입니다. `lshed.yaml`에 얼마든지 추가하세요.
|
|
269
|
-
|
|
270
218
|
```yaml
|
|
271
219
|
profiles:
|
|
272
220
|
default:
|
|
@@ -274,7 +222,7 @@ profiles:
|
|
|
274
222
|
skills: [add-drivers, domain-modeling, grilling]
|
|
275
223
|
instructions: [main]
|
|
276
224
|
mcp: [notion]
|
|
277
|
-
server: # headless box: no browser toolkit, no MCP
|
|
225
|
+
server: # headless box: no browser toolkit, no MCP
|
|
278
226
|
skills: [add-drivers]
|
|
279
227
|
instructions: [main, server-rules]
|
|
280
228
|
```
|
|
@@ -291,44 +239,36 @@ lshed restore server
|
|
|
291
239
|
|
|
292
240
|
Switching removes only what the previous profile placed (`-`), keeps what both use (`=`), and rewrites what changed (`~`). Everything removed or overwritten goes to `~/.claude/lshed/backups/<timestamp>/` first. Packages are additive: a profile that does not list `gstack` leaves the clone alone. `--dry-run` prints this plan without touching anything. 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.
|
|
293
241
|
|
|
294
|
-
전환은 이전 프로필이 놓은 것만 치우고(`-`), 둘 다 쓰는 것은 두고(`=`), 내용이 바뀐 것만 다시 씁니다(`~`). 치우거나 덮어쓰는 것은 모두 `~/.claude/lshed/backups/<시각>/`에 먼저 갑니다. 패키지는 더하기만 합니다. `gstack`을 적지 않은 프로필도 clone은 그대로 둡니다. `--dry-run`은 이 계획만 출력합니다. 지침 조각은 순서가 있습니다. `restore`는 조각을 `@`-import하는 `CLAUDE.md`를 만들므로, 창고에서 조각을 고치면 다음 `restore`에 반영되고 병합할 것이 없습니다.
|
|
295
|
-
|
|
296
242
|
A profile can build on another one with `extends`, so a machine-specific profile lists only what is different:
|
|
297
243
|
|
|
298
|
-
프로필은 `extends`로 다른 프로필 위에 쌓을 수 있어, 기기별 프로필에는 다른 점만 적으면 됩니다.
|
|
299
|
-
|
|
300
244
|
```yaml
|
|
301
245
|
profiles:
|
|
302
246
|
default:
|
|
303
247
|
skills: [add-drivers, domain-modeling]
|
|
304
248
|
instructions: [main]
|
|
305
249
|
laptop:
|
|
306
|
-
extends: default # everything in default, plus:
|
|
250
|
+
extends: default # everything in default, plus:
|
|
307
251
|
packages: [gstack]
|
|
308
252
|
mcp: [notion]
|
|
309
253
|
lab:
|
|
310
|
-
extends: [default] # a list works too, applied in order
|
|
311
|
-
instructions: [lab-rules] # comes after default's `main`
|
|
254
|
+
extends: [default] # a list works too, applied in order
|
|
255
|
+
instructions: [lab-rules] # comes after default's `main`
|
|
312
256
|
```
|
|
313
257
|
|
|
314
258
|
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.
|
|
315
259
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
### Other agents, same shed · 다른 에이전트도 같은 창고로
|
|
260
|
+
### Other agents, same shed
|
|
319
261
|
|
|
320
262
|
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:
|
|
321
263
|
|
|
322
|
-
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` 중 하나로만 넣으세요.
|
|
323
|
-
|
|
324
264
|
```
|
|
325
|
-
lshed restore --agent agents # ~/.agents/skills: every tool that follows the convention reads it
|
|
265
|
+
lshed restore --agent agents # ~/.agents/skills: every tool that follows the convention reads it
|
|
326
266
|
lshed restore --agent codex # ~/.agents/skills + ~/.codex/AGENTS.md + config.toml
|
|
327
|
-
lshed restore --agent gemini --link # ~/.gemini/skills + ~/.gemini/GEMINI.md, as links
|
|
267
|
+
lshed restore --agent gemini --link # ~/.gemini/skills + ~/.gemini/GEMINI.md, as links
|
|
328
268
|
lshed restore --agent agy # ~/.gemini/config/skills + ~/.gemini/AGENTS.md (Antigravity IDE and CLI)
|
|
329
269
|
```
|
|
330
270
|
|
|
331
|
-
| `--agent` | root
|
|
271
|
+
| `--agent` | root | skills | instructions file | MCP servers |
|
|
332
272
|
|---|---|---|---|---|
|
|
333
273
|
| `claude-code` (default) | `~/.claude` or `$CLAUDE_CONFIG_DIR` | yes, plus agents, commands, settings | `CLAUDE.md`, `@`-imports fragments | `~/.claude.json` |
|
|
334
274
|
| `codex` | `~/.codex` or `$CODEX_HOME` | yes, in `~/.agents/skills` (Codex's documented location; `~/.codex/skills` is deprecated) | `AGENTS.md`, fragments concatenated | `config.toml` `[mcp_servers.*]` |
|
|
@@ -340,18 +280,12 @@ lshed restore --agent agy # ~/.gemini/config/skills + ~/.gemini/AG
|
|
|
340
280
|
|
|
341
281
|
MCP entries are stored in the shed in Claude Code's shape and translated on the way out: Gemini gets `httpUrl` and no `type`, Antigravity gets `serverUrl`, Copilot gets `type: local` and `tools: ["*"]`, Cursor gets `${env:VAR}` placeholders and Codex gets `env_vars` / `bearer_token_env_var` / `env_http_headers` with the variable *names*, so for those two the secret values never touch the config file. Gemini, Antigravity and Copilot do not expand placeholders, so lshed fills them from your shell at `restore`. Codex's `config.toml` is edited table by table; your comments and other settings stay as they are. `lshed init --agent gemini` reads the same files back into the shed shape, secrets masked.
|
|
342
282
|
|
|
343
|
-
MCP 항목은 창고에 Claude Code 형식으로 두고 나갈 때 바꿉니다. Gemini는 `type` 없이 `httpUrl`, Antigravity는 `serverUrl`, Copilot은 `type: local`과 `tools: ["*"]`, Cursor는 `${env:VAR}` 자리표시자, Codex는 `env_vars` / `bearer_token_env_var` / `env_http_headers`에 변수 *이름*을 적습니다. 그래서 Cursor와 Codex의 설정 파일에는 시크릿 값이 아예 들어가지 않습니다. Gemini, Antigravity, Copilot은 자리표시자를 스스로 채우지 않으므로 `restore`가 셸 환경에서 채웁니다. Codex의 `config.toml`은 해당 표만 골라 고치므로 주석과 다른 설정은 그대로입니다. `lshed init --agent gemini`처럼 반대 방향도 되며, 시크릿은 마스킹돼 창고에 들어갑니다.
|
|
344
|
-
|
|
345
283
|
Each agent root keeps its own `lshed/state.json`, so restoring into `~/.codex` never touches what lshed placed in `~/.claude`, and each can use a different profile or `--link` choice. Parts the target does not understand are announced and skipped: a profile with settings keys restores into Codex without them, with a line saying `codex 은 settings 를 다루지 않아 건너뜁니다`. Claude plugin packages are skipped the same way; `github:`/`git:` packages are cloned into every agent root that restores the profile, so give the other agents a profile without them if that is not what you want. `lshed init --agent codex` works too, and a shed made from Codex restores into Claude Code with `CLAUDE.md` generated from the same fragments. The shed's `agent:` is only a default for `--agent` (`$LSHED_AGENT` also works).
|
|
346
284
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
### Links instead of copies · 복사 대신 링크
|
|
285
|
+
### Links instead of copies
|
|
350
286
|
|
|
351
287
|
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.
|
|
352
288
|
|
|
353
|
-
편집을 주로 하는 기기에서는 `restore --link`가 스킬·에이전트·명령·지침 조각을 복사본 대신 창고를 가리키는 링크로 놓습니다. `~/.claude`에서 한 편집이 곧바로 창고에 있으므로 `diff`는 보고할 것이 없고 `save`는 할 일이 없습니다. `lshed sync`가 순환의 전부입니다.
|
|
354
|
-
|
|
355
289
|
```
|
|
356
290
|
$ lshed restore --link
|
|
357
291
|
~ skills/add-drivers (link)
|
|
@@ -359,91 +293,80 @@ $ lshed restore --link
|
|
|
359
293
|
~ lshed/instructions/main.md (link)
|
|
360
294
|
= CLAUDE.md
|
|
361
295
|
|
|
362
|
-
|
|
296
|
+
Profile "default" applied (link): placed 4, removed 0
|
|
363
297
|
```
|
|
364
298
|
|
|
365
|
-
The choice is per machine and remembered: later `lshed restore` calls on that machine keep linking, `lshed status` shows
|
|
366
|
-
|
|
367
|
-
선택은 기기별이고 기억됩니다. 그 기기의 이후 `lshed restore`도 계속 링크로 놓고, `lshed status`는 `배치 link`를 보여 주며, `restore --no-link`로 복사로 돌아갑니다. 다른 기기에는 영향이 없습니다. MCP 항목과 설정 키는 파일이 아니라 JSON 값이라 항상 씁니다. 프로필을 바꾸면 링크만 지우고 그 뒤의 창고는 절대 지우지 않습니다. Windows에서는 디렉터리가 특별한 권한 없이 junction이 되고, 파일 하나짜리 부품(에이전트, 명령, 조각)은 링크에 개발자 모드가 필요합니다. 없으면 복사하고 그렇다고 알린 뒤 보통 복사본처럼 다룹니다(`save`도 됩니다).
|
|
299
|
+
The choice is per machine and remembered: later `lshed restore` calls on that machine keep linking, `lshed status` shows `placement links`, 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).
|
|
368
300
|
|
|
369
|
-
### Adding things later
|
|
301
|
+
### Adding things later
|
|
370
302
|
|
|
371
303
|
Write a new skill, add an MCP server with `claude mcp add`, clone a toolkit into `~/.claude/skills/`. Then:
|
|
372
304
|
|
|
373
|
-
새 스킬을 쓰거나, `claude mcp add`로 MCP 서버를 넣거나, 툴킷을 `~/.claude/skills/`에 clone한 뒤:
|
|
374
|
-
|
|
375
305
|
```
|
|
376
306
|
$ lshed add
|
|
377
|
-
|
|
307
|
+
3 outside the shed (to add: lshed add <key...> or --all):
|
|
378
308
|
skills/paper-review
|
|
379
309
|
mcp/linear
|
|
380
310
|
≡ packages/superpowers github:obra/superpowers@main
|
|
381
|
-
·
|
|
311
|
+
· 53 generated by package gstack are left out
|
|
382
312
|
|
|
383
313
|
$ lshed add paper-review mcp/linear
|
|
384
314
|
+ skills/paper-review
|
|
385
|
-
+ mcp/linear (
|
|
315
|
+
+ mcp/linear (secrets → ${LINEAR_API_KEY})
|
|
386
316
|
|
|
387
|
-
2
|
|
317
|
+
2 added to the shed and to profile "default". Commit the shed: /home/me/lshed
|
|
388
318
|
```
|
|
389
319
|
|
|
390
|
-
`add` classifies exactly like `init`, appends to `lshed.yaml` without disturbing your comments, adds the parts to the current profile and to the managed set. Without keys it only lists. `status` shows the count
|
|
391
|
-
|
|
392
|
-
`add`는 `init`과 똑같이 분류하고, 주석을 흐트러뜨리지 않고 `lshed.yaml`에 덧붙이며, 현재 프로필과 관리 집합에 넣습니다. 키 없이 부르면 나열만 합니다. `status`는 그 수를 `창고 밖`으로 보여 줍니다. 이미 창고에 있는 부품을 다른 프로필에 넣는 것은 `profiles:`를 직접 고치는 일이고, 그런 경우 `add`가 알려 줍니다.
|
|
320
|
+
`add` classifies exactly like `init`, appends to `lshed.yaml` without disturbing your comments, adds the parts to the current profile and to the managed set. Without keys it only lists. `status` shows the count on its `outside` row. To put a part that is already in the shed into another profile, edit `profiles:` by hand; `add` tells you when that is the case.
|
|
393
321
|
|
|
394
|
-
### Keeping packages current
|
|
322
|
+
### Keeping packages current
|
|
395
323
|
|
|
396
324
|
```
|
|
397
|
-
lshed status # shows "253d1df ≠ lock 0d1bd56
|
|
398
|
-
lshed update
|
|
399
|
-
lshed update
|
|
325
|
+
lshed status # shows "! gstack 253d1df ≠ lock 0d1bd56 → lshed update" when a clone moved
|
|
326
|
+
lshed update --dry-run # asks upstream what would move, touches nothing
|
|
327
|
+
lshed update # fast-forward every package in the profile, refresh lshed.lock
|
|
328
|
+
lshed update gstack --yes # one package, and run its install: afterwards
|
|
400
329
|
```
|
|
401
330
|
|
|
402
331
|
Git packages are pinned by commit in `lshed.lock`; a new machine gets exactly that commit. Plugins cannot be pinned, so the lock records what got installed and `status` says when it differs from the machine you came from.
|
|
403
332
|
|
|
404
|
-
|
|
333
|
+
`update --dry-run` reads upstream without changing anything: `= (최신)` (up to date) when the clone already sits on the remote tip, `~ 0d1bd56 → 0530392` when a pull would move it, `?` when that cannot be known in advance. Git packages and marketplaces Claude Code cloned with git can be checked; Claude plugins and the official marketplace (not a git clone) cannot, so they show `?` and only a real `update` tells.
|
|
405
334
|
|
|
406
|
-
### Housekeeping
|
|
335
|
+
### Housekeeping
|
|
407
336
|
|
|
408
337
|
```
|
|
409
|
-
lshed list # everything in the shed and which profiles use it
|
|
410
|
-
lshed list --unused # parts no profile lists
|
|
411
|
-
lshed remove skills/old # delete from the shed (refused while a profile uses it)
|
|
412
|
-
lshed prune --yes # delete everything unused
|
|
338
|
+
lshed list # everything in the shed and which profiles use it
|
|
339
|
+
lshed list --unused # parts no profile lists
|
|
340
|
+
lshed remove skills/old # delete from the shed (refused while a profile uses it)
|
|
341
|
+
lshed prune --yes # delete everything unused
|
|
413
342
|
```
|
|
414
343
|
|
|
415
344
|
`remove` and `prune` delete from the shed without a backup; the shed lives in git, so commit before you prune.
|
|
416
345
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
### Reading the output · 출력 읽기
|
|
346
|
+
### Reading the output
|
|
420
347
|
|
|
421
|
-
| Mark
|
|
348
|
+
| Mark | Meaning |
|
|
422
349
|
|---|---|
|
|
423
|
-
| `+` | placed / added
|
|
424
|
-
| `=` | already identical, nothing done
|
|
425
|
-
| `~` | existed with different content, replaced (backed up)
|
|
426
|
-
| `-` | removed (backed up) or excluded
|
|
427
|
-
| `≡` | package: recorded by source, not copied
|
|
428
|
-
| `·` | generated by an installer, skipped
|
|
429
|
-
| `!` | needs your attention
|
|
430
|
-
| `↑` `↓` | pushed / pulled (sync), updated (update)
|
|
350
|
+
| `+` | placed / added |
|
|
351
|
+
| `=` | already identical, nothing done |
|
|
352
|
+
| `~` | existed with different content, replaced (backed up) |
|
|
353
|
+
| `-` | removed (backed up) or excluded |
|
|
354
|
+
| `≡` | package: recorded by source, not copied |
|
|
355
|
+
| `·` | generated by an installer, skipped |
|
|
356
|
+
| `!` | needs your attention |
|
|
357
|
+
| `↑` `↓` | pushed / pulled (sync), updated (update) |
|
|
431
358
|
|
|
432
359
|
Errors go to stderr with exit code 1. Everything else is on stdout.
|
|
433
360
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
## The manifest · 매니페스트
|
|
361
|
+
## The manifest
|
|
437
362
|
|
|
438
363
|
`lshed.yaml` lives at the root of the shed. `init` generates it; edit it by hand from then on. `add` and `remove` edit it for you and keep your comments.
|
|
439
364
|
|
|
440
|
-
`lshed.yaml`은 창고 루트에 있습니다. `init`이 만들고, 그다음부터는 직접 고칩니다. `add`와 `remove`도 주석을 보존하며 고쳐 줍니다.
|
|
441
|
-
|
|
442
365
|
```yaml
|
|
443
366
|
version: 1
|
|
444
367
|
agent: claude-code
|
|
445
|
-
exclude: [skills/_gstack-command] # things init/add must not pick up
|
|
446
|
-
ignore: [dist] # extra names never copied (adds to the built-in list)
|
|
368
|
+
exclude: [skills/_gstack-command] # things init/add must not pick up
|
|
369
|
+
ignore: [dist] # extra names never copied (adds to the built-in list)
|
|
447
370
|
|
|
448
371
|
components:
|
|
449
372
|
skills:
|
|
@@ -457,9 +380,9 @@ components:
|
|
|
457
380
|
- id: base # file:./instructions/base.md
|
|
458
381
|
- id: research-style
|
|
459
382
|
mcp:
|
|
460
|
-
- id: exa # file:./mcp/exa.json — secrets replaced by ${VAR}
|
|
383
|
+
- id: exa # file:./mcp/exa.json — secrets replaced by ${VAR}
|
|
461
384
|
settings:
|
|
462
|
-
- id: permissions # file:./settings/permissions.json — one top-level key of settings.json
|
|
385
|
+
- id: permissions # file:./settings/permissions.json — one top-level key of settings.json
|
|
463
386
|
- id: hooks
|
|
464
387
|
|
|
465
388
|
packages:
|
|
@@ -473,7 +396,7 @@ profiles:
|
|
|
473
396
|
packages: [gstack]
|
|
474
397
|
skills: [paper-review]
|
|
475
398
|
agents: [reviewer]
|
|
476
|
-
instructions: [base, research-style] # order matters
|
|
399
|
+
instructions: [base, research-style] # order matters
|
|
477
400
|
mcp: [exa]
|
|
478
401
|
settings: [permissions, hooks]
|
|
479
402
|
teaching:
|
|
@@ -483,51 +406,34 @@ profiles:
|
|
|
483
406
|
```
|
|
484
407
|
|
|
485
408
|
- Component `source` accepts `file:<path relative to the shed>`. Package `source` accepts `github:owner/repo@ref`, `git:<url>#ref`, `claude-marketplace:<owner/repo>`, `claude-plugin:<name>@<marketplace>`.
|
|
486
|
-
부품의 `source`는 `file:<창고 기준 상대 경로>`입니다. 패키지의 `source`는 `github:owner/repo@ref`, `git:<url>#ref`, `claude-marketplace:<owner/repo>`, `claude-plugin:<name>@<marketplace>`를 받습니다.
|
|
487
409
|
- Category names come from the adapter. For Claude Code: `skills`, `agents`, `commands`, `instructions`, `mcp`, `settings`. Other agents have `skills`, and `instructions` / `mcp` where the table above says so.
|
|
488
|
-
카테고리 이름은 어댑터가 정합니다. Claude Code는 `skills`, `agents`, `commands`, `instructions`, `mcp`, `settings`이고, 다른 에이전트는 `skills`에 더해 위 표에 있는 대로 `instructions` / `mcp`를 가집니다.
|
|
489
410
|
- `ignore:` 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.
|
|
490
|
-
`ignore:`는 기본 목록(`node_modules`, `.git`, `__pycache__`, `.venv`, 캐시 디렉터리, `*.log`)에 더해집니다. `dist/` 같은 빌드 산출물은 스킬에 따라 필요하므로 기본으로는 빼지 않습니다.
|
|
491
411
|
- `exclude:` lists parts that exist locally but must not enter the shed. `init --exclude` writes it.
|
|
492
|
-
`exclude:`는 로컬에는 있지만 창고에 들어가면 안 되는 부품입니다. `init --exclude`가 적어 줍니다.
|
|
493
412
|
|
|
494
|
-
## Three kinds of things
|
|
413
|
+
## Three kinds of things
|
|
495
414
|
|
|
496
415
|
A real `~/.claude` mixes three kinds of content, and they need different handling:
|
|
497
416
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
| Kind · 종류 | Example · 예 | What lshed does · lshed 가 하는 일 |
|
|
417
|
+
| Kind | Example | What lshed does |
|
|
501
418
|
|---|---|---|
|
|
502
|
-
| **Authored**
|
|
503
|
-
| **Installed**
|
|
504
|
-
| **Generated**
|
|
419
|
+
| **Authored** | a skill you wrote, your `CLAUDE.md`, an MCP server you added | copies it into the shed |
|
|
420
|
+
| **Installed** | a toolkit you `git clone`d, a plugin | records source + commit; `restore` clones or installs it back |
|
|
421
|
+
| **Generated** | stub skills an installer wrote for you | skips them; they return when the installer runs |
|
|
505
422
|
|
|
506
423
|
A directory with a `.git` and a remote becomes a **package**. A skill whose symlink points inside a package is treated as generated and skipped. Everything else is authored and copied. The rules that keep this safe:
|
|
507
424
|
|
|
508
|
-
`.git`과 remote가 있는 디렉터리는 **패키지**가 됩니다. 심볼릭 링크가 패키지 안을 가리키는 스킬은 생성물로 보고 건너뜁니다. 나머지는 직접 만든 것으로 보고 복사합니다. 이것을 안전하게 지키는 규칙은 다음과 같습니다.
|
|
509
|
-
|
|
510
425
|
- A package that is already present is never touched by `restore`. Your local checkout is yours.
|
|
511
|
-
이미 있는 패키지는 `restore`가 절대 건드리지 않습니다. 로컬 체크아웃은 여러분 것입니다.
|
|
512
426
|
- `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.
|
|
513
|
-
`install:`은 셸 명령입니다. `--yes`가 없으면 `restore`와 `update`는 **보여 주고 멈춥니다.** 플러그인 설치는 Claude Code 자체 패키지 관리자를 거치므로 그 없이도 돌고, 설치 명령을 선언한 플러그인에는 `--yes`가 `-y`로 전달됩니다.
|
|
514
427
|
- Packages are not part of the managed set. Switching profiles never deletes a clone.
|
|
515
|
-
패키지는 관리 집합에 들어가지 않습니다. 프로필을 바꿔도 clone은 지워지지 않습니다.
|
|
516
428
|
|
|
517
429
|
Claude Code plugins are packages with their own scheme. `init` finds user-scope ones in `~/.claude/plugins`; `restore` adds the marketplace first, then runs `claude plugin install`. Project-scope plugins belong to their project and are not recorded.
|
|
518
430
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
## MCP servers and secrets · MCP 서버와 시크릿
|
|
431
|
+
## MCP servers and secrets
|
|
522
432
|
|
|
523
433
|
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.
|
|
524
434
|
|
|
525
|
-
사용자 범위 MCP 서버는 `~/.claude.json` 안에 기기 ID, 세션 상태와 함께 있습니다. lshed는 서버 하나를 `mcp` 카테고리의 부품 하나로 봅니다. 창고에는 `mcp/<이름>.json`이 있고, `restore`는 `~/.claude.json`의 `mcpServers.<이름>` 키만 고치고 나머지는 그대로 둡니다.
|
|
526
|
-
|
|
527
435
|
**No secret value enters the shed.** `init` and `add` replace values under `env` and `headers` whose key contains a secret-looking word (`key`, `token`, `secret`, `password`, `auth`, `authorization`, `credential`, `cookie`, `session` — whole words, so `MAX_OUTPUT_TOKENS` is left alone) with a `${VAR}` placeholder:
|
|
528
436
|
|
|
529
|
-
**시크릿 값은 창고에 들어가지 않습니다.** `init`과 `add`는 `env`와 `headers` 아래에서 키 이름에 시크릿처럼 보이는 단어(`key`, `token`, `secret`, `password`, `auth`, `authorization`, `credential`, `cookie`, `session` — 단어 단위라 `MAX_OUTPUT_TOKENS`는 해당 없음)가 있는 값을 `${VAR}` 자리표시자로 바꿉니다.
|
|
530
|
-
|
|
531
437
|
```json
|
|
532
438
|
{ "type": "stdio", "command": "npx", "args": ["-y", "exa-mcp-server"],
|
|
533
439
|
"env": { "EXA_API_KEY": "${EXA_API_KEY}" } }
|
|
@@ -537,158 +443,116 @@ User-scope MCP servers live in `~/.claude.json`, next to machine IDs and session
|
|
|
537
443
|
|
|
538
444
|
`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. `diff` compares with placeholders as wildcards, so a machine holding real values is not drift.
|
|
539
445
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
## Settings · 설정
|
|
446
|
+
## Settings
|
|
543
447
|
|
|
544
448
|
`~/.claude/settings.json` holds hooks, permissions, `env`, the model, the theme, and some state Claude Code writes for itself. lshed does not merge it. Each **top-level key is one component** of category `settings`: the shed holds `settings/permissions.json`, `settings/hooks.json`, and so on, and `restore` writes exactly those keys, leaving the rest of the file alone. A profile can carry `permissions` and `hooks` and leave `model` to each machine.
|
|
545
449
|
|
|
546
|
-
`~/.claude/settings.json`에는 훅, 권한, `env`, 모델, 테마, 그리고 Claude Code가 스스로 쓰는 상태값이 있습니다. lshed는 이 파일을 병합하지 않습니다. **최상위 키 하나가 `settings` 카테고리의 부품 하나**입니다. 창고에는 `settings/permissions.json`, `settings/hooks.json` 같은 파일이 있고, `restore`는 딱 그 키만 쓰고 나머지는 둡니다. 프로필은 `permissions`와 `hooks`만 실어 나르고 `model`은 기기마다 다르게 둘 수 있습니다.
|
|
547
|
-
|
|
548
450
|
```
|
|
549
451
|
$ lshed add
|
|
550
|
-
|
|
551
|
-
settings/hooks !
|
|
452
|
+
3 outside the shed:
|
|
453
|
+
settings/hooks ! points into package gstack. If that install created it, exclude it: settings/hooks
|
|
552
454
|
settings/model
|
|
553
455
|
settings/theme
|
|
554
456
|
```
|
|
555
457
|
|
|
556
458
|
- `enabledPlugins` and `extraKnownMarketplaces` are never taken: the plugin and marketplace packages own them, and `restore` rebuilds them by installing those.
|
|
557
|
-
`enabledPlugins`와 `extraKnownMarketplaces`는 담지 않습니다. 플러그인·마켓플레이스 패키지의 몫이고, `restore`가 설치하면서 다시 만듭니다.
|
|
558
459
|
- Absolute paths under your home directory become `${HOME}/…` in the shed, so a hook command written on one machine works on another. Claude Code does not expand variables in `settings.json`, so `restore` fills `${HOME}` and any `${VAR}` itself from your shell; unset variables are reported and left as placeholders.
|
|
559
|
-
홈 아래 절대 경로는 창고에서 `${HOME}/…`가 되어, 한 기기에서 쓴 훅 명령이 다른 기기에서도 돕니다. Claude Code는 `settings.json`의 변수를 채우지 않으므로 `restore`가 `${HOME}`과 `${VAR}`를 셸에서 직접 채우고, 없는 변수는 알린 뒤 자리표시자로 둡니다.
|
|
560
460
|
- `env` is treated as a secret map: keys that look secret are masked, the rest (`CLAUDE_CODE_MAX_OUTPUT_TOKENS`, …) travel as they are.
|
|
561
|
-
`env`는 시크릿 맵으로 봅니다. 시크릿처럼 보이는 키는 마스킹하고 나머지(`CLAUDE_CODE_MAX_OUTPUT_TOKENS` 등)는 그대로 갑니다.
|
|
562
461
|
- A value pointing inside a package (a hook a toolkit's installer wrote) is flagged. If the installer recreates it, put it in `exclude:` and let `restore --yes` bring it back.
|
|
563
|
-
패키지 안을 가리키는 값(툴킷 설치기가 쓴 훅)은 표시됩니다. 설치기가 다시 만들어 주는 것이면 `exclude:`에 넣고 `restore --yes`가 되살리게 두세요.
|
|
564
462
|
- Since the shed owns the whole key, extra permissions you grant locally show up in `diff` and go into the shed with `save`, like any other edit.
|
|
565
|
-
창고가 키 전체를 소유하므로, 로컬에서 추가한 권한은 `diff`에 나타나고 다른 편집처럼 `save`로 창고에 들어갑니다.
|
|
566
463
|
|
|
567
|
-
## Commands
|
|
464
|
+
## Commands
|
|
568
465
|
|
|
569
466
|
```
|
|
570
467
|
lshed init [--shed <dir>] [--profile <name>] [--exclude <id...>]
|
|
571
|
-
lshed add [keys...] [--all] put things that appeared since init into the shed
|
|
468
|
+
lshed add [keys...] [--all] put things that appeared since init into the shed
|
|
572
469
|
lshed restore [profile] [--pick] [--link | --no-link] [--dry-run] [--no-backup] [--yes] (--agent <name> to target another tool)
|
|
573
|
-
lshed status applied profile, drift, packages, missing env, new things
|
|
574
|
-
lshed diff files (or JSON keys) that differ between local and shed
|
|
575
|
-
lshed save [ids...] copy local edits back into the shed
|
|
470
|
+
lshed status applied profile, drift, packages, missing env, new things
|
|
471
|
+
lshed diff files (or JSON keys) that differ between local and shed
|
|
472
|
+
lshed save [ids...] copy local edits back into the shed
|
|
576
473
|
lshed sync [-m <msg>] [--no-push] [--dry-run] commit the shed, pull --rebase, push
|
|
577
|
-
lshed update [ids...] [--dry-run] [--yes] pull packages forward, refresh lshed.lock
|
|
578
|
-
lshed list [--unused] what is in the shed, and which profiles use it
|
|
579
|
-
lshed remove <key> drop a component or package from the shed
|
|
580
|
-
lshed prune [--yes] drop everything no profile uses
|
|
581
|
-
lshed scan list what the agent root holds, without writing anything
|
|
474
|
+
lshed update [ids...] [--dry-run] [--yes] pull packages forward, refresh lshed.lock; --dry-run asks upstream only
|
|
475
|
+
lshed list [--unused] what is in the shed, and which profiles use it
|
|
476
|
+
lshed remove <key> drop a component or package from the shed
|
|
477
|
+
lshed prune [--yes] drop everything no profile uses
|
|
478
|
+
lshed scan list what the agent root holds, without writing anything
|
|
582
479
|
```
|
|
583
480
|
|
|
584
481
|
Keys are `category/id`, or just `id` when unambiguous: `skills/paper-review`, `mcp/exa`, `packages/gstack`.
|
|
585
482
|
|
|
586
|
-
키는 `카테고리/id`이고, 모호하지 않으면 `id`만 써도 됩니다: `skills/paper-review`, `mcp/exa`, `packages/gstack`.
|
|
587
|
-
|
|
588
483
|
Global options: `--shed <dir>` (or `LSHED_HOME`; after the first restore lshed remembers it), `--agent <name>` (or `LSHED_AGENT`; default is the shed's `agent:`, then `claude-code`), `--root <dir>` (agent config root, default is the agent's own, e.g. `~/.claude` or `~/.codex`).
|
|
589
484
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
### What `restore` does · `restore` 가 하는 일
|
|
485
|
+
### What `restore` does
|
|
593
486
|
|
|
594
487
|
0. Installs any package in the profile that is missing, at the version in `lshed.lock`.
|
|
595
|
-
프로필의 패키지 중 없는 것을 `lshed.lock`의 버전으로 설치합니다.
|
|
596
488
|
1. Removes paths that the **previous** profile placed and the new one doesn't need.
|
|
597
|
-
**이전** 프로필이 놓았고 새 프로필에는 없는 경로를 치웁니다.
|
|
598
489
|
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`.
|
|
599
|
-
새 프로필의 부품을 제자리에 복사합니다(`--link`이거나 전에 링크를 쓴 기기라면 창고로 가는 링크로). MCP 항목은 `~/.claude.json`에, 설정 키는 `settings.json`에 씁니다.
|
|
600
490
|
3. Regenerates the instructions file.
|
|
601
|
-
지침 파일을 다시 만듭니다.
|
|
602
491
|
|
|
603
492
|
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. 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.
|
|
604
493
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
### What `sync` does · `sync` 가 하는 일
|
|
494
|
+
### What `sync` does
|
|
608
495
|
|
|
609
496
|
1. Warns if `diff` shows local edits you have not saved.
|
|
610
|
-
`diff`에 저장하지 않은 로컬 편집이 있으면 경고합니다.
|
|
611
497
|
2. Commits everything in the shed (message names the changed parts, or `-m`).
|
|
612
|
-
창고의 모든 것을 커밋합니다(메시지는 바뀐 부품 이름, 또는 `-m`).
|
|
613
498
|
3. If `origin` exists: `git pull --rebase`, then `git push` (sets the upstream the first time).
|
|
614
|
-
`origin`이 있으면 `git pull --rebase` 뒤 `git push`(첫 번째는 upstream 설정).
|
|
615
499
|
4. If commits came in, tells you to run `lshed restore`.
|
|
616
|
-
받은 커밋이 있으면 `lshed restore`를 돌리라고 알립니다.
|
|
617
500
|
|
|
618
501
|
On a conflict it aborts the rebase, leaves the shed clean with your commit intact, and tells you to resolve with git. Without a remote it only commits. It never runs `save` for you.
|
|
619
502
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
### Ownership · 소유권
|
|
503
|
+
### Ownership
|
|
623
504
|
|
|
624
505
|
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.
|
|
625
506
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
## Where things live · 어디에 무엇이 있나
|
|
507
|
+
## Where things live
|
|
629
508
|
|
|
630
509
|
```
|
|
631
510
|
<shed>/
|
|
632
|
-
lshed.yaml manifest
|
|
633
|
-
lshed.lock package versions (generated)
|
|
511
|
+
lshed.yaml manifest
|
|
512
|
+
lshed.lock package versions (generated)
|
|
634
513
|
skills/<id>/ agents/<id>.md commands/<id>.md instructions/<id>.md
|
|
635
|
-
mcp/<id>.json secrets as ${VAR}
|
|
636
|
-
settings/<id>.json one top-level key each; home paths as ${HOME}
|
|
514
|
+
mcp/<id>.json secrets as ${VAR}
|
|
515
|
+
settings/<id>.json one top-level key each; home paths as ${HOME}
|
|
637
516
|
|
|
638
517
|
~/.claude/
|
|
639
|
-
skills/ agents/ commands/ CLAUDE.md ← placed by restore
|
|
640
|
-
settings.json <id> ← one key per settings component; the rest is untouched
|
|
641
|
-
lshed/state.json ← which profile, which paths are managed
|
|
642
|
-
lshed/instructions/<id>.md ← fragments imported by CLAUDE.md
|
|
643
|
-
lshed/backups/<timestamp>/ ← whatever restore replaced
|
|
644
|
-
~/.claude.json mcpServers.<id> ← one key per mcp component; the rest of the file is untouched
|
|
518
|
+
skills/ agents/ commands/ CLAUDE.md ← placed by restore
|
|
519
|
+
settings.json <id> ← one key per settings component; the rest is untouched
|
|
520
|
+
lshed/state.json ← which profile, which paths are managed
|
|
521
|
+
lshed/instructions/<id>.md ← fragments imported by CLAUDE.md
|
|
522
|
+
lshed/backups/<timestamp>/ ← whatever restore replaced
|
|
523
|
+
~/.claude.json mcpServers.<id> ← one key per mcp component; the rest of the file is untouched
|
|
645
524
|
|
|
646
525
|
~/.codex/ ~/.gemini/ ~/.copilot/ ~/.cursor/ ~/.gemini/config/ ~/.agents/
|
|
647
|
-
skills/ <instructions file> <mcp file> ← the same, per --agent (see the table above; codex skills live in ~/.agents/skills)
|
|
648
|
-
lshed/state.json lshed/backups/ ← each root keeps its own
|
|
526
|
+
skills/ <instructions file> <mcp file> ← the same, per --agent (see the table above; codex skills live in ~/.agents/skills)
|
|
527
|
+
lshed/state.json lshed/backups/ ← each root keeps its own
|
|
649
528
|
```
|
|
650
529
|
|
|
651
530
|
`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 writes `.claude.json` inside it, as Claude Code does.
|
|
652
531
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
## What has been verified · 검증된 것
|
|
532
|
+
## What has been verified
|
|
656
533
|
|
|
657
534
|
- 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.
|
|
658
|
-
|
|
659
|
-
-
|
|
660
|
-
다른 에이전트는 문서만이 아니라 도구 자체로 확인합니다. `scripts/vm/probe.sh`는 임시 창고를 도구의 실제 루트에 복원한 뒤, 스킬에 든 암호어, 지침 파일에 든 코드워드, `--link` 링크를 거친 같은 스킬을 비대화형으로 물어봅니다. Codex 0.153.2와 Antigravity CLI 1.1.26은 모든 검사를 통과했고, Gemini CLI·Copilot CLI·Cursor는 아직 파일 배치와 형식까지만 확인했습니다. 자세한 내용과 새 VM에서 전부 돌리는 cloud-init 파일은 `scripts/vm/README.md`에 있습니다.
|
|
535
|
+
- 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.2 and Antigravity CLI 1.1.27 pass every check (last run 2026-09-08 with lshed 0.14.1); 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.
|
|
536
|
+
- 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.
|
|
661
537
|
|
|
662
|
-
## Not in scope (yet)
|
|
538
|
+
## Not in scope (yet)
|
|
663
539
|
|
|
664
540
|
- Secrets beyond "name the variable". Encrypted values, `op://` references and OS keychains are possible later; today lshed is deliberately no better than dotfiles here.
|
|
665
|
-
"변수 이름만 적는" 것 이상의 시크릿 처리. 암호화된 값, `op://` 참조, OS 키체인은 나중 일이고, 지금은 일부러 dotfiles보다 나을 것이 없게 두었습니다.
|
|
666
541
|
- Project-scope MCP servers (`.mcp.json`, `~/.claude.json` `projects.*`) and project-scope plugins. They belong to the project.
|
|
667
|
-
프로젝트 범위 MCP 서버(`.mcp.json`, `~/.claude.json`의 `projects.*`)와 프로젝트 범위 플러그인. 프로젝트의 것입니다.
|
|
668
542
|
- For the other agents, only skills, the instructions file and MCP servers travel. Their own settings files, rules folders and plugins stay where they are.
|
|
669
|
-
다른 에이전트는 스킬, 지침 파일, MCP 서버만 옮깁니다. 각 도구의 설정 파일, 규칙 폴더, 플러그인은 그대로 둡니다.
|
|
670
543
|
|
|
671
|
-
## Troubleshooting
|
|
544
|
+
## Troubleshooting
|
|
672
545
|
|
|
673
|
-
- **"창고 위치를 모릅니다"** — pass `--shed <dir>` or set `LSHED_HOME`. After one successful `restore`, lshed remembers it.
|
|
674
|
-
`--shed <dir>`를 주거나 `LSHED_HOME`을 설정하세요. `restore`가 한 번 성공하면 기억합니다.
|
|
546
|
+
- **"창고 위치를 모릅니다"** (shed location unknown) — pass `--shed <dir>` or set `LSHED_HOME`. After one successful `restore`, lshed remembers it.
|
|
675
547
|
- **restore replaced my `CLAUDE.md`** — it is in `~/.claude/lshed/backups/<timestamp>/CLAUDE.md`. Move its content into a fragment in the shed and add that fragment to your profile.
|
|
676
|
-
**restore가 내 `CLAUDE.md`를 바꿨다** — `~/.claude/lshed/backups/<시각>/CLAUDE.md`에 있습니다. 내용을 창고의 조각으로 옮기고 그 조각을 프로필에 넣으세요.
|
|
677
548
|
- **I edited a skill locally and want to keep it** — `lshed diff` to see, `lshed save <id>` to push it into the shed, then `lshed sync`.
|
|
678
|
-
**로컬에서 고친 스킬을 지키고 싶다** — `lshed diff`로 보고, `lshed save <id>`로 창고에 넣고, `lshed sync`.
|
|
679
549
|
- **`status` says a package differs from the lock** — something updated the clone or plugin behind lshed's back (Claude Code auto-updates plugins). `lshed update` records the new version.
|
|
680
|
-
**`status`가 패키지가 lock과 다르다고 한다** — 무언가가 lshed 몰래 clone이나 플러그인을 갱신했습니다(Claude Code는 플러그인을 자동 갱신합니다). `lshed update`가 새 버전을 기록합니다.
|
|
681
550
|
- **`status` keeps listing the same new things** — they are installer aliases or scratch. Add them to `exclude:` in `lshed.yaml`.
|
|
682
|
-
**`status`가 같은 새 항목을 계속 보여 준다** — 설치기 별칭이거나 임시 파일입니다. `lshed.yaml`의 `exclude:`에 넣으세요.
|
|
683
551
|
- **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.
|
|
684
|
-
**restore가 MCP 변수가 없다고 한다** — 셸 프로필에서 export하고 Claude Code를 다시 시작하세요. `~/.claude.json`의 자리표시자는 맞게 들어간 것이고, Claude Code가 시작할 때 채웁니다.
|
|
685
552
|
- **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.
|
|
686
|
-
**restore가 훅 경로를 엉뚱하게 썼다** — 창고는 홈 경로를 `${HOME}/…`로 담습니다. 이 기기에서 다른 곳을 가리켜야 하면 창고의 JSON을 `${HOME}`이나 다른 변수로 고치고 다시 `restore`하세요.
|
|
687
553
|
- **`--link` copied a file on Windows** — single-file links need Developer Mode. Turn it on and `restore` again, or keep the copy and use `save` for that file.
|
|
688
|
-
**Windows에서 `--link`가 파일을 복사했다** — 파일 하나짜리 링크는 개발자 모드가 필요합니다. 켜고 다시 `restore`하거나, 복사본을 두고 그 파일은 `save`로 다루세요.
|
|
689
554
|
- **sync stopped on a conflict** — `cd <shed> && git pull --rebase`, resolve, `git rebase --continue`, then `lshed sync` again.
|
|
690
|
-
**sync가 충돌로 멈췄다** — `cd <창고> && git pull --rebase`, 해결, `git rebase --continue`, 그리고 다시 `lshed sync`.
|
|
691
555
|
|
|
692
|
-
## License
|
|
556
|
+
## License
|
|
693
557
|
|
|
694
558
|
MIT
|