lazyclaude-ai 0.1.0 → 0.1.2
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/README.md +65 -65
- package/README_ko-KR.md +178 -0
- package/RELEASE_CHECKLIST.md +41 -13
- package/bin/lazyclaude-ai.js +201 -18
- package/docs/migration.md +12 -4
- package/package.json +2 -1
- package/plugins/lazyclaude/.claude-plugin/plugin.json +1 -1
- package/scripts/qa-portable-install.sh +44 -1
package/README.md
CHANGED
|
@@ -5,80 +5,86 @@ simple local activation, prompt-triggered ultrawork discipline,
|
|
|
5
5
|
planner/executor/reviewer agents, lifecycle hooks, MCP scaffolding, and
|
|
6
6
|
LSP-backed code checks.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
marketplace, or
|
|
12
|
-
|
|
8
|
+
The current public package is `lazyclaude-ai@0.1.1`, and this checkout prepares
|
|
9
|
+
the next `0.1.2` patch for personal install convenience. The repo can remain
|
|
10
|
+
private and quiet; publishing to npm here does not imply public repo promotion,
|
|
11
|
+
marketplace publication, or advertisement. Future package releases still require
|
|
12
|
+
explicit user approval.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Quick Install
|
|
15
15
|
|
|
16
|
-
Use
|
|
16
|
+
Use the npm package from any machine:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npx lazyclaude-ai install
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
npx --yes lazyclaude-ai install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Validate the installed plugin:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx --yes lazyclaude-ai doctor
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
user directory and prints the exact launch command:
|
|
28
|
+
Launch Claude Code normally:
|
|
27
29
|
|
|
28
30
|
```bash
|
|
29
|
-
claude
|
|
31
|
+
claude
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
Alternative entrypoints:
|
|
33
35
|
|
|
34
36
|
```bash
|
|
35
|
-
lazyclaude
|
|
37
|
+
bunx lazyclaude-ai install
|
|
38
|
+
npm install -g lazyclaude-ai
|
|
39
|
+
lazyclaude install
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
If you need the explicit `npm exec` form, use a fresh prefix so npm does not
|
|
43
|
+
confuse the registry package with a same-name source checkout:
|
|
39
44
|
|
|
40
45
|
```bash
|
|
41
|
-
|
|
42
|
-
lazyclaude run
|
|
46
|
+
npm exec --prefix "$(mktemp -d)" --yes --package lazyclaude-ai -- lazyclaude install
|
|
43
47
|
```
|
|
44
48
|
|
|
45
49
|
Remove only LazyClaude-managed install state:
|
|
46
50
|
|
|
47
51
|
```bash
|
|
48
|
-
lazyclaude uninstall
|
|
52
|
+
npx --yes lazyclaude-ai uninstall
|
|
49
53
|
```
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
The installer registers `lazyclaude@lazyclaude-ai` in Claude Code's user plugin
|
|
56
|
+
registry under `~/.claude/plugins` and enables it in `settings.json`
|
|
57
|
+
`enabledPlugins`, so you do not need to type a long `--plugin-dir` command every
|
|
58
|
+
time. Set `CLAUDE_CONFIG_DIR=/some/path` and `LAZYCLAUDE_HOME=/some/path` only
|
|
59
|
+
for isolated tests.
|
|
53
60
|
|
|
54
|
-
## Local
|
|
61
|
+
## Local Development
|
|
55
62
|
|
|
56
|
-
Use the plugin directly from this checkout while it
|
|
63
|
+
Use the plugin directly from this checkout while editing it:
|
|
57
64
|
|
|
58
65
|
```bash
|
|
59
66
|
claude --plugin-dir ./plugins/lazyclaude
|
|
60
67
|
```
|
|
61
68
|
|
|
62
|
-
If you already have an OMC/omc Claude plugin installed, do not co-load it with
|
|
63
|
-
LazyClaude during this local MVP test. LazyClaude intentionally avoids a root
|
|
64
|
-
Claude marketplace skeleton now, so it can be loaded directly by
|
|
65
|
-
path without competing with an existing OMC marketplace or plugin namespace.
|
|
66
|
-
If your user-level Claude config still enables OMC, Claude may create a local
|
|
67
|
-
`.omc/` state directory during smoke tests; LazyClaude ignores and excludes that
|
|
68
|
-
directory from git and npm package surfaces.
|
|
69
|
-
|
|
70
69
|
Inside Claude Code, reload local plugin metadata after edits:
|
|
71
70
|
|
|
72
71
|
```text
|
|
73
72
|
/reload-plugins
|
|
74
73
|
```
|
|
75
74
|
|
|
75
|
+
If an OMC/omc Claude plugin is installed, do not co-load it with LazyClaude
|
|
76
|
+
while testing this MVP. Local checkout tests use `--plugin-dir`, and npm installs
|
|
77
|
+
create only a LazyClaude-managed local marketplace under the user's LazyClaude
|
|
78
|
+
home. The checkout does not ship a root Claude marketplace skeleton. If
|
|
79
|
+
user-level OMC creates a local `.omc/` state directory during smoke tests,
|
|
80
|
+
LazyClaude ignores and excludes that directory from git and npm package surfaces.
|
|
81
|
+
|
|
76
82
|
## ULW Usage
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
LazyClaude is available in normal Claude Code sessions after install:
|
|
79
85
|
|
|
80
86
|
```bash
|
|
81
|
-
claude
|
|
87
|
+
claude
|
|
82
88
|
```
|
|
83
89
|
|
|
84
90
|
Then type one of these prompts in Claude Code:
|
|
@@ -92,13 +98,13 @@ $start-work plans/lazyclaude-retrofit.md
|
|
|
92
98
|
```
|
|
93
99
|
|
|
94
100
|
Expected behavior: LazyClaude's prompt hook adds `ULTRAWORK MODE ENABLED`
|
|
95
|
-
context, then the matching skill
|
|
96
|
-
test-first work,
|
|
97
|
-
|
|
101
|
+
context, then the matching skill and agent instructions steer Claude toward
|
|
102
|
+
test-first work, manual QA evidence, cleanup receipts, and explicit approval
|
|
103
|
+
before future release steps.
|
|
98
104
|
|
|
99
105
|
Plain `ulw` is hook context activation, so it may not show a separate Skill
|
|
100
106
|
tool invocation in Claude Code history. Use visible namespaced commands when
|
|
101
|
-
you want explicit LazyClaude skill
|
|
107
|
+
you want explicit LazyClaude skill or command activation:
|
|
102
108
|
|
|
103
109
|
```text
|
|
104
110
|
/lazyclaude:ulw-loop <what you want executed with evidence>
|
|
@@ -106,6 +112,17 @@ you want explicit LazyClaude skill/command activation:
|
|
|
106
112
|
/lazyclaude:start-work plans/lazyclaude-retrofit.md
|
|
107
113
|
```
|
|
108
114
|
|
|
115
|
+
## Commands
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx --yes lazyclaude-ai install
|
|
119
|
+
npx --yes lazyclaude-ai doctor
|
|
120
|
+
npx --yes lazyclaude-ai path
|
|
121
|
+
npx --yes lazyclaude-ai run -- --help
|
|
122
|
+
npx --yes lazyclaude-ai update
|
|
123
|
+
npx --yes lazyclaude-ai uninstall
|
|
124
|
+
```
|
|
125
|
+
|
|
109
126
|
The package alias can be inspected without installing anything globally:
|
|
110
127
|
|
|
111
128
|
```bash
|
|
@@ -113,12 +130,13 @@ node bin/lazyclaude-ai.js --dry-run install
|
|
|
113
130
|
node bin/lazyclaude-ai.js --dry-run doctor
|
|
114
131
|
```
|
|
115
132
|
|
|
116
|
-
##
|
|
133
|
+
## Verification
|
|
117
134
|
|
|
118
135
|
```bash
|
|
119
136
|
npm test
|
|
120
137
|
npm run validate:plugin
|
|
121
138
|
npm run qa:tmux
|
|
139
|
+
npm run qa:portable
|
|
122
140
|
npm run pack:dry-run
|
|
123
141
|
```
|
|
124
142
|
|
|
@@ -126,26 +144,6 @@ npm run pack:dry-run
|
|
|
126
144
|
available on the machine, the smoke harness records a controlled skip with the
|
|
127
145
|
version probe evidence instead of failing mysteriously.
|
|
128
146
|
|
|
129
|
-
## Rollback And Uninstall
|
|
130
|
-
|
|
131
|
-
For npm-installed LazyClaude, run:
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
lazyclaude uninstall
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Because the local MVP can also be loaded with
|
|
138
|
-
`claude --plugin-dir ./plugins/lazyclaude`, checkout rollback is local and
|
|
139
|
-
reversible:
|
|
140
|
-
|
|
141
|
-
1. Stop the Claude Code session that loaded the plugin.
|
|
142
|
-
2. Start Claude Code without the `--plugin-dir` flag.
|
|
143
|
-
3. Remove any temporary plugin-dir reference you added for testing.
|
|
144
|
-
4. Run `/reload-plugins` in any remaining Claude Code session that should stop
|
|
145
|
-
seeing LazyClaude.
|
|
146
|
-
|
|
147
|
-
No global install step is required for this MVP.
|
|
148
|
-
|
|
149
147
|
## Safety Model
|
|
150
148
|
|
|
151
149
|
LazyClaude is intentionally local-first:
|
|
@@ -154,16 +152,17 @@ LazyClaude is intentionally local-first:
|
|
|
154
152
|
- Hooks do not execute user prompt text.
|
|
155
153
|
- The ultrawork prompt hook returns constant guidance and does not echo prompt
|
|
156
154
|
text back into the context.
|
|
157
|
-
- Any `.omc
|
|
158
|
-
included in the LazyClaude package.
|
|
155
|
+
- Any `.omc/`, `.omo/`, or `evidence/` local state is ignored and is not
|
|
156
|
+
included in the LazyClaude npm package.
|
|
159
157
|
- The planner agent is read-only and has no edit tools.
|
|
160
158
|
- MCP and LSP helpers are local stdio commands.
|
|
161
|
-
-
|
|
162
|
-
explicit user approval.
|
|
159
|
+
- Future publication, remote marketplace mutation, and package release all
|
|
160
|
+
require explicit user approval.
|
|
163
161
|
|
|
164
162
|
## MVP Scope
|
|
165
163
|
|
|
166
164
|
- Package/bin: `lazyclaude-ai`
|
|
165
|
+
- Friendly bin alias: `lazyclaude`
|
|
167
166
|
- Plugin namespace: `lazyclaude`
|
|
168
167
|
- Claude Code platform: `claude-code`
|
|
169
168
|
- Skills: ultrawork planning, ultrawork loop, start-work, rules, LSP,
|
|
@@ -172,5 +171,6 @@ LazyClaude is intentionally local-first:
|
|
|
172
171
|
- Hooks: session-start, user-prompt-submit, post-tool-use, post-compact
|
|
173
172
|
- Config: local MCP server and TypeScript-family LSP doctor
|
|
174
173
|
|
|
175
|
-
See `
|
|
176
|
-
the Codex-to-Claude migration
|
|
174
|
+
See `README_ko-KR.md` for Korean setup notes, `REFERENCE.md` for the pinned
|
|
175
|
+
LazyCodex source, and `docs/migration.md` for the Codex-to-Claude migration
|
|
176
|
+
table.
|
package/README_ko-KR.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# LazyClaude
|
|
2
|
+
|
|
3
|
+
LazyClaude는 LazyCodex 스타일의 작업 흐름을 Claude Code에 맞게 옮긴
|
|
4
|
+
개인용 배포판입니다. 간단한 프롬프트 트리거, ultrawork 규칙, 플래너와
|
|
5
|
+
실행자 에이전트, hooks, MCP scaffold, LSP doctor를 Claude Code 플러그인
|
|
6
|
+
형태로 제공합니다.
|
|
7
|
+
|
|
8
|
+
현재 공개 npm 패키지는 `lazyclaude-ai@0.1.1`이고, 이 checkout은 다음
|
|
9
|
+
`0.1.2` 패치 배포 후보를 준비합니다. 목적은 다른 PC에서도 빠르게
|
|
10
|
+
설치하기 위한 개인용 배포물입니다. 저장소는 비공개 저장소로 유지할 수
|
|
11
|
+
있고, npm 배포가 곧 홍보, 공개 저장소 운영, Claude marketplace 등록을
|
|
12
|
+
의미하지는 않습니다. 새 버전 배포는 항상 별도의 명시적 승인 후에
|
|
13
|
+
진행합니다.
|
|
14
|
+
|
|
15
|
+
## 빠른 설치
|
|
16
|
+
|
|
17
|
+
새 환경에서는 아래 한 줄을 사용합니다.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx --yes lazyclaude-ai install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
설치 상태를 확인합니다.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx --yes lazyclaude-ai doctor
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
설치 후에는 일반적인 `claude` 명령으로 Claude Code를 실행합니다.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
대체 진입점은 다음과 같습니다.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bunx lazyclaude-ai install
|
|
39
|
+
npm install -g lazyclaude-ai
|
|
40
|
+
lazyclaude install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
현재 checkout처럼 package 이름이 같은 폴더 안에서 `npm exec`를 직접 쓸
|
|
44
|
+
때는 fresh prefix를 주는 편이 안전합니다.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm exec --prefix "$(mktemp -d)" --yes --package lazyclaude-ai -- lazyclaude install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
LazyClaude가 관리한 설치 상태만 제거하려면 다음을 사용합니다.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx --yes lazyclaude-ai uninstall
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
installer는 `lazyclaude@lazyclaude-ai`를 Claude Code user plugin registry
|
|
57
|
+
아래의 `~/.claude/plugins`에 등록하고 `settings.json`의 `enabledPlugins`와
|
|
58
|
+
LazyClaude 전용 local marketplace 항목, `known_marketplaces.json`을 함께
|
|
59
|
+
기록합니다. 따라서 매번 긴 `--plugin-dir` 명령을 입력할 필요가 없습니다.
|
|
60
|
+
격리 테스트가 필요할 때만
|
|
61
|
+
`CLAUDE_CONFIG_DIR=/some/path`와 `LAZYCLAUDE_HOME=/some/path`를 지정하세요.
|
|
62
|
+
|
|
63
|
+
## 로컬 개발
|
|
64
|
+
|
|
65
|
+
이 저장소에서 바로 플러그인을 테스트할 때는 다음처럼 실행합니다.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
claude --plugin-dir ./plugins/lazyclaude
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Claude Code 안에서 플러그인 메타데이터를 다시 읽고 싶으면 다음 명령을
|
|
72
|
+
사용합니다.
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
/reload-plugins
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
기존 OMC/omc Claude 플러그인이 설치되어 있다면 MVP 테스트 중에는
|
|
79
|
+
LazyClaude와 함께 co-load하지 않는 것을 권장합니다. 로컬 checkout 테스트는
|
|
80
|
+
`--plugin-dir`로 직접 로드하고, npm 설치는 사용자 홈 아래에 LazyClaude가
|
|
81
|
+
관리하는 local marketplace만 만듭니다. root Claude marketplace skeleton은
|
|
82
|
+
저장소 checkout에 배포하지 않습니다. user-level OMC가 검증 중 `.omc/`
|
|
83
|
+
상태 디렉터리를 만들더라도 LazyClaude는 이를 git 및 npm package surface에서
|
|
84
|
+
제외합니다.
|
|
85
|
+
|
|
86
|
+
## ULW 사용법
|
|
87
|
+
|
|
88
|
+
설치 후에는 일반적인 `claude` 명령에서 LazyClaude를 사용할 수 있습니다.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
claude
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
그다음 Claude Code에 아래 프롬프트 중 하나를 입력합니다.
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
ulw
|
|
98
|
+
ultrawork
|
|
99
|
+
$ulw-plan <계획이 필요한 작업>
|
|
100
|
+
$ulw-loop <증거 기반으로 실행할 작업>
|
|
101
|
+
$start-work plans/lazyclaude-retrofit.md
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
기대 동작은 LazyClaude prompt hook이 `ULTRAWORK MODE ENABLED` 컨텍스트를
|
|
105
|
+
추가하고, skill 및 agent 지시문이 Claude를 test-first 작업, 실제 수동 QA
|
|
106
|
+
증거, cleanup receipt, 향후 release step의 명시적 승인 쪽으로 유도하는
|
|
107
|
+
것입니다.
|
|
108
|
+
|
|
109
|
+
단순히 `ulw`라고 입력하는 것은 hook context activation이므로 Claude Code
|
|
110
|
+
history에 별도 Skill tool invocation으로 보이지 않을 수 있습니다. 명시적인
|
|
111
|
+
LazyClaude command activation을 보고 싶다면 namespaced command를 사용합니다.
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
/lazyclaude:ulw-loop <증거 기반으로 실행할 작업>
|
|
115
|
+
/lazyclaude:ulw-plan <계획이 필요한 작업>
|
|
116
|
+
/lazyclaude:start-work plans/lazyclaude-retrofit.md
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 명령어
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx --yes lazyclaude-ai install
|
|
123
|
+
npx --yes lazyclaude-ai doctor
|
|
124
|
+
npx --yes lazyclaude-ai path
|
|
125
|
+
npx --yes lazyclaude-ai run -- --help
|
|
126
|
+
npx --yes lazyclaude-ai update
|
|
127
|
+
npx --yes lazyclaude-ai uninstall
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
전역 설치 없이 checkout 내부 bin을 확인할 수도 있습니다.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
node bin/lazyclaude-ai.js --dry-run install
|
|
134
|
+
node bin/lazyclaude-ai.js --dry-run doctor
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 검증
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm test
|
|
141
|
+
npm run validate:plugin
|
|
142
|
+
npm run qa:tmux
|
|
143
|
+
npm run qa:portable
|
|
144
|
+
npm run pack:dry-run
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`validate:plugin`과 `qa:tmux`는 로컬 검증용입니다. Claude Code가 없는
|
|
148
|
+
환경에서는 smoke harness가 모호한 실패 대신 controlled skip과 version
|
|
149
|
+
probe evidence를 남깁니다.
|
|
150
|
+
|
|
151
|
+
## 안전 모델
|
|
152
|
+
|
|
153
|
+
- Hook은 Claude Code event JSON을 stdin으로 읽고 제한된 JSON context를
|
|
154
|
+
반환합니다.
|
|
155
|
+
- Hook은 사용자 프롬프트 텍스트를 실행하지 않습니다.
|
|
156
|
+
- ultrawork prompt hook은 고정된 guidance를 반환하며 prompt text를 다시
|
|
157
|
+
echo하지 않습니다.
|
|
158
|
+
- `.omc/`, `.omo/`, `evidence/` 같은 로컬 상태는 ignore되며 npm 패키지에
|
|
159
|
+
포함되지 않습니다.
|
|
160
|
+
- planner agent는 read-only이며 edit tool을 갖지 않습니다.
|
|
161
|
+
- MCP와 LSP helper는 local stdio command입니다.
|
|
162
|
+
- 향후 publish, remote marketplace 변경, 새 package release는 모두 명시적
|
|
163
|
+
승인 후 진행합니다.
|
|
164
|
+
|
|
165
|
+
## 범위
|
|
166
|
+
|
|
167
|
+
- Package/bin: `lazyclaude-ai`
|
|
168
|
+
- Friendly bin alias: `lazyclaude`
|
|
169
|
+
- Plugin namespace: `lazyclaude`
|
|
170
|
+
- Claude Code platform: `claude-code`
|
|
171
|
+
- Skills: ultrawork planning, ultrawork loop, start-work, rules, LSP,
|
|
172
|
+
programming, review-work
|
|
173
|
+
- Agents: planner, executor, verifier, reviewer, librarian, QA runner
|
|
174
|
+
- Hooks: session-start, user-prompt-submit, post-tool-use, post-compact
|
|
175
|
+
- Config: local MCP server and TypeScript-family LSP doctor
|
|
176
|
+
|
|
177
|
+
영문 설명은 `README.md`, LazyCodex 출처 고정 정보는 `REFERENCE.md`,
|
|
178
|
+
Codex-to-Claude 변환 표는 `docs/migration.md`를 참고하세요.
|
package/RELEASE_CHECKLIST.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# LazyClaude Release Checklist
|
|
2
2
|
|
|
3
|
-
Status: quiet public npm package
|
|
4
|
-
user approval.
|
|
3
|
+
Status: quiet public npm package `lazyclaude-ai@0.1.1` is published for
|
|
4
|
+
personal install convenience after explicit user approval. This checkout is
|
|
5
|
+
prepared as the next `0.1.2` patch candidate until the user explicitly approves
|
|
6
|
+
another publish.
|
|
5
7
|
|
|
6
|
-
DO NOT publish LazyClaude, run `npm publish`, push release
|
|
7
|
-
remote Claude Code marketplace entry without explicit user
|
|
8
|
+
DO NOT publish a new version of LazyClaude, run `npm publish`, push release
|
|
9
|
+
tags, or add a remote Claude Code marketplace entry without explicit user
|
|
10
|
+
approval.
|
|
8
11
|
|
|
9
12
|
This release path is for personal install convenience, not advertisement,
|
|
10
13
|
public repo promotion, or a public launch campaign.
|
|
@@ -14,6 +17,7 @@ public repo promotion, or a public launch campaign.
|
|
|
14
17
|
- `npm test`
|
|
15
18
|
- `npm run validate:plugin`
|
|
16
19
|
- `npm run qa:tmux`
|
|
20
|
+
- `npm run qa:portable`
|
|
17
21
|
- `npm run pack:dry-run`
|
|
18
22
|
|
|
19
23
|
## Local / Private Checkout Use
|
|
@@ -27,10 +31,24 @@ Use this track when testing from the current checkout:
|
|
|
27
31
|
|
|
28
32
|
No npm publication is required for this track.
|
|
29
33
|
|
|
30
|
-
##
|
|
34
|
+
## Fresh Machine NPM Install Use
|
|
31
35
|
|
|
32
|
-
Use this track
|
|
33
|
-
|
|
36
|
+
Use this track after an approved npm package version exists:
|
|
37
|
+
|
|
38
|
+
1. Run `npx --yes lazyclaude-ai install`.
|
|
39
|
+
2. Run `npx --yes lazyclaude-ai doctor`.
|
|
40
|
+
3. Start Claude Code with the normal `claude` command.
|
|
41
|
+
|
|
42
|
+
The installer should register `lazyclaude@lazyclaude-ai` in Claude Code's user
|
|
43
|
+
plugin registry, `settings.json` `enabledPlugins`, and the LazyClaude-managed
|
|
44
|
+
local marketplace metadata needed by `claude plugin details`. Do not ask users
|
|
45
|
+
to type a generated command that shells out to `npx --yes lazyclaude-ai path` for
|
|
46
|
+
normal npm installs.
|
|
47
|
+
|
|
48
|
+
## Quiet Public NPM Package Release Or Update
|
|
49
|
+
|
|
50
|
+
Use this track only when the user explicitly approves making a new package
|
|
51
|
+
version installable through `npm`, `npx`, and `bunx`.
|
|
34
52
|
|
|
35
53
|
1. Confirm the target package name and version with the user.
|
|
36
54
|
2. Review `REFERENCE.md` and `docs/migration.md` for accurate attribution.
|
|
@@ -72,15 +90,25 @@ rollback action with user approval:
|
|
|
72
90
|
|
|
73
91
|
## Marketplace Boundary
|
|
74
92
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
Local checkout testing still uses `claude --plugin-dir ./plugins/lazyclaude`.
|
|
94
|
+
NPM installs should use the global Claude user plugin registry and normal
|
|
95
|
+
`claude` launches. A root Claude marketplace file is intentionally not shipped
|
|
96
|
+
because users may already have an OMC/omc marketplace or plugin installed. The
|
|
97
|
+
npm installer creates a LazyClaude-managed local marketplace under
|
|
98
|
+
`LAZYCLAUDE_HOME` and registers only `lazyclaude-ai` in Claude's user marketplace
|
|
99
|
+
metadata so skills and hooks appear in `claude plugin details`. Do not add a
|
|
100
|
+
remote marketplace entry without explicit user approval. If user-level OMC
|
|
101
|
+
creates `.omc/` state during validation, keep it ignored and confirm it is
|
|
102
|
+
absent from the package dry-run.
|
|
81
103
|
|
|
82
104
|
## Rollback
|
|
83
105
|
|
|
84
106
|
If a local test load causes problems, stop Claude Code, restart without
|
|
85
107
|
`claude --plugin-dir ./plugins/lazyclaude`, and run `/reload-plugins` in any
|
|
86
108
|
remaining session that should drop the local plugin metadata.
|
|
109
|
+
|
|
110
|
+
If an npm-installed LazyClaude copy causes problems, run:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx --yes lazyclaude-ai uninstall
|
|
114
|
+
```
|
package/bin/lazyclaude-ai.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
readFileSync,
|
|
9
9
|
rmSync,
|
|
10
10
|
symlinkSync,
|
|
11
|
+
writeFileSync,
|
|
11
12
|
} from "node:fs";
|
|
12
13
|
import { homedir } from "node:os";
|
|
13
14
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -20,11 +21,11 @@ const version = packageJson.version;
|
|
|
20
21
|
const usage = `Usage: lazyclaude-ai [--dry-run] <install|doctor|path|run|update|uninstall> [...args]
|
|
21
22
|
|
|
22
23
|
Commands:
|
|
23
|
-
install
|
|
24
|
+
install Register the packaged LazyClaude plugin in Claude Code.
|
|
24
25
|
doctor Validate the installed LazyClaude plugin path.
|
|
25
26
|
path Print the installed Claude plugin path.
|
|
26
|
-
run -- ... Run Claude Code
|
|
27
|
-
update Reinstall this package version and refresh the
|
|
27
|
+
run -- ... Run Claude Code after the global plugin install.
|
|
28
|
+
update Reinstall this package version and refresh the Claude plugin registry.
|
|
28
29
|
uninstall Remove LazyClaude-managed install state.
|
|
29
30
|
`;
|
|
30
31
|
|
|
@@ -36,10 +37,19 @@ const parseArgs = (argv) => {
|
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
const lazyHome = () => resolve(process.env.LAZYCLAUDE_HOME ?? join(homedir(), ".lazyclaude"));
|
|
40
|
+
const claudeHome = () => resolve(process.env.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_HOME ?? join(homedir(), ".claude"));
|
|
39
41
|
const versionRoot = (home = lazyHome()) => join(home, "lazyclaude-ai", version);
|
|
40
42
|
const currentRoot = (home = lazyHome()) => join(home, "current");
|
|
41
43
|
const pluginPathForRoot = (installRoot) => join(installRoot, "plugins", "lazyclaude");
|
|
42
|
-
const
|
|
44
|
+
const claudePluginRoot = (home = claudeHome()) => join(home, "plugins", "cache", "lazyclaude-ai", "lazyclaude", version);
|
|
45
|
+
const installedPluginsPath = (home = claudeHome()) => join(home, "plugins", "installed_plugins.json");
|
|
46
|
+
const knownMarketplacesPath = (home = claudeHome()) => join(home, "plugins", "known_marketplaces.json");
|
|
47
|
+
const claudeSettingsPath = (home = claudeHome()) => join(home, "settings.json");
|
|
48
|
+
const pluginKey = "lazyclaude@lazyclaude-ai";
|
|
49
|
+
const marketplaceName = "lazyclaude-ai";
|
|
50
|
+
const intendedPluginPath = (home = claudeHome()) => claudePluginRoot(home);
|
|
51
|
+
const marketplaceRoot = (home = lazyHome()) => join(home, "marketplaces", marketplaceName);
|
|
52
|
+
const marketplacePluginPath = (home = lazyHome()) => join(marketplaceRoot(home), "plugins", "lazyclaude");
|
|
43
53
|
const sourcePluginPath = () => join(root, "plugins", "lazyclaude");
|
|
44
54
|
|
|
45
55
|
const printUsage = () => {
|
|
@@ -51,9 +61,156 @@ const fail = (message, status = 1) => {
|
|
|
51
61
|
process.exit(status);
|
|
52
62
|
};
|
|
53
63
|
|
|
54
|
-
const currentExists = (home =
|
|
64
|
+
const currentExists = (home = claudeHome()) => existsSync(claudePluginRoot(home));
|
|
55
65
|
|
|
56
|
-
const
|
|
66
|
+
const readInstalledPlugins = (home = claudeHome()) => {
|
|
67
|
+
const registryPath = installedPluginsPath(home);
|
|
68
|
+
if (!existsSync(registryPath)) return { version: 2, plugins: {} };
|
|
69
|
+
const parsed = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
70
|
+
return {
|
|
71
|
+
...parsed,
|
|
72
|
+
version: parsed.version ?? 2,
|
|
73
|
+
plugins: parsed.plugins ?? {},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const writeInstalledPlugins = (registry, home = claudeHome()) => {
|
|
78
|
+
const registryPath = installedPluginsPath(home);
|
|
79
|
+
mkdirSync(dirname(registryPath), { recursive: true });
|
|
80
|
+
writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const readClaudeSettings = (home = claudeHome()) => {
|
|
84
|
+
const settingsPath = claudeSettingsPath(home);
|
|
85
|
+
if (!existsSync(settingsPath)) return {};
|
|
86
|
+
return JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const writeClaudeSettings = (settings, home = claudeHome()) => {
|
|
90
|
+
const settingsPath = claudeSettingsPath(home);
|
|
91
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
92
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const readKnownMarketplaces = (home = claudeHome()) => {
|
|
96
|
+
const path = knownMarketplacesPath(home);
|
|
97
|
+
if (!existsSync(path)) return {};
|
|
98
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const writeKnownMarketplaces = (knownMarketplaces, home = claudeHome()) => {
|
|
102
|
+
const path = knownMarketplacesPath(home);
|
|
103
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
104
|
+
writeFileSync(path, `${JSON.stringify(knownMarketplaces, null, 2)}\n`);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const marketplaceSource = (marketplacePath) => ({
|
|
108
|
+
source: "directory",
|
|
109
|
+
path: marketplacePath,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const writeLocalMarketplace = (legacyHome = lazyHome()) => {
|
|
113
|
+
const rootPath = marketplaceRoot(legacyHome);
|
|
114
|
+
const targetPlugin = marketplacePluginPath(legacyHome);
|
|
115
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
116
|
+
mkdirSync(dirname(targetPlugin), { recursive: true });
|
|
117
|
+
cpSync(sourcePluginPath(), targetPlugin, { recursive: true });
|
|
118
|
+
const marketplaceManifestPath = join(rootPath, ".claude-plugin", "marketplace.json");
|
|
119
|
+
mkdirSync(dirname(marketplaceManifestPath), { recursive: true });
|
|
120
|
+
writeFileSync(
|
|
121
|
+
marketplaceManifestPath,
|
|
122
|
+
`${JSON.stringify(
|
|
123
|
+
{
|
|
124
|
+
$schema: "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
125
|
+
name: marketplaceName,
|
|
126
|
+
description: "Local LazyClaude npm-installed marketplace.",
|
|
127
|
+
owner: {
|
|
128
|
+
name: "LazyClaude contributors",
|
|
129
|
+
},
|
|
130
|
+
plugins: [
|
|
131
|
+
{
|
|
132
|
+
name: "lazyclaude",
|
|
133
|
+
description: packageJson.description,
|
|
134
|
+
author: {
|
|
135
|
+
name: "LazyClaude contributors",
|
|
136
|
+
},
|
|
137
|
+
source: "./plugins/lazyclaude",
|
|
138
|
+
category: "development",
|
|
139
|
+
homepage: "https://github.com/code-yeongyu/lazycodex",
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
null,
|
|
144
|
+
2,
|
|
145
|
+
)}\n`,
|
|
146
|
+
);
|
|
147
|
+
return rootPath;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const registerMarketplace = (marketplacePath, home = claudeHome()) => {
|
|
151
|
+
const settings = readClaudeSettings(home);
|
|
152
|
+
settings.enabledPlugins = settings.enabledPlugins ?? {};
|
|
153
|
+
settings.enabledPlugins[pluginKey] = true;
|
|
154
|
+
settings.extraKnownMarketplaces = settings.extraKnownMarketplaces ?? {};
|
|
155
|
+
settings.extraKnownMarketplaces[marketplaceName] = {
|
|
156
|
+
source: marketplaceSource(marketplacePath),
|
|
157
|
+
};
|
|
158
|
+
writeClaudeSettings(settings, home);
|
|
159
|
+
|
|
160
|
+
const knownMarketplaces = readKnownMarketplaces(home);
|
|
161
|
+
knownMarketplaces[marketplaceName] = {
|
|
162
|
+
source: marketplaceSource(marketplacePath),
|
|
163
|
+
installLocation: marketplacePath,
|
|
164
|
+
lastUpdated: new Date().toISOString(),
|
|
165
|
+
};
|
|
166
|
+
writeKnownMarketplaces(knownMarketplaces, home);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const unregisterMarketplace = (home = claudeHome()) => {
|
|
170
|
+
const settingsPath = claudeSettingsPath(home);
|
|
171
|
+
if (existsSync(settingsPath)) {
|
|
172
|
+
const settings = readClaudeSettings(home);
|
|
173
|
+
if (settings.enabledPlugins) delete settings.enabledPlugins[pluginKey];
|
|
174
|
+
if (settings.extraKnownMarketplaces) delete settings.extraKnownMarketplaces[marketplaceName];
|
|
175
|
+
writeClaudeSettings(settings, home);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const knownPath = knownMarketplacesPath(home);
|
|
179
|
+
if (existsSync(knownPath)) {
|
|
180
|
+
const knownMarketplaces = readKnownMarketplaces(home);
|
|
181
|
+
delete knownMarketplaces[marketplaceName];
|
|
182
|
+
writeKnownMarketplaces(knownMarketplaces, home);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const registerClaudePlugin = (installRoot, marketplacePath, home = claudeHome()) => {
|
|
187
|
+
const registry = readInstalledPlugins(home);
|
|
188
|
+
const now = new Date().toISOString();
|
|
189
|
+
const existing = registry.plugins[pluginKey]?.[0] ?? {};
|
|
190
|
+
registry.plugins[pluginKey] = [
|
|
191
|
+
{
|
|
192
|
+
scope: "user",
|
|
193
|
+
installPath: installRoot,
|
|
194
|
+
version,
|
|
195
|
+
installedAt: existing.installedAt ?? now,
|
|
196
|
+
lastUpdated: now,
|
|
197
|
+
installedBy: "lazyclaude-ai",
|
|
198
|
+
enabled: true,
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
writeInstalledPlugins(registry, home);
|
|
202
|
+
registerMarketplace(marketplacePath, home);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const unregisterClaudePlugin = (home = claudeHome()) => {
|
|
206
|
+
const registryPath = installedPluginsPath(home);
|
|
207
|
+
if (!existsSync(registryPath)) return;
|
|
208
|
+
const registry = readInstalledPlugins(home);
|
|
209
|
+
delete registry.plugins[pluginKey];
|
|
210
|
+
writeInstalledPlugins(registry, home);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const requireInstalledPluginPath = (home = claudeHome()) => {
|
|
57
214
|
if (!currentExists(home)) {
|
|
58
215
|
fail("LazyClaude is not installed. Run `lazyclaude install` first.");
|
|
59
216
|
}
|
|
@@ -76,16 +233,18 @@ const resetCurrentPointer = (home, target) => {
|
|
|
76
233
|
};
|
|
77
234
|
|
|
78
235
|
const install = ({ dryRun }) => {
|
|
79
|
-
const home =
|
|
80
|
-
const
|
|
81
|
-
const targetPlugin =
|
|
236
|
+
const home = claudeHome();
|
|
237
|
+
const legacyHome = lazyHome();
|
|
238
|
+
const targetPlugin = claudePluginRoot(home);
|
|
82
239
|
const sourcePlugin = sourcePluginPath();
|
|
240
|
+
const targetMarketplace = marketplaceRoot(legacyHome);
|
|
83
241
|
|
|
84
242
|
if (dryRun) {
|
|
85
243
|
process.stdout.write(`DRY_RUN: install LazyClaude ${version}\n`);
|
|
86
244
|
process.stdout.write(`Would copy: ${sourcePlugin} -> ${targetPlugin}\n`);
|
|
87
|
-
process.stdout.write(`Would
|
|
88
|
-
process.stdout.write(`
|
|
245
|
+
process.stdout.write(`Would create local marketplace: ${targetMarketplace}\n`);
|
|
246
|
+
process.stdout.write(`Would register Claude plugin: ${pluginKey}\n`);
|
|
247
|
+
process.stdout.write("Launch with: claude\n");
|
|
89
248
|
return;
|
|
90
249
|
}
|
|
91
250
|
|
|
@@ -93,14 +252,23 @@ const install = ({ dryRun }) => {
|
|
|
93
252
|
fail(`Packaged LazyClaude plugin payload is missing: ${sourcePlugin}`);
|
|
94
253
|
}
|
|
95
254
|
|
|
96
|
-
rmSync(
|
|
255
|
+
rmSync(targetPlugin, { recursive: true, force: true });
|
|
97
256
|
mkdirSync(dirname(targetPlugin), { recursive: true });
|
|
98
257
|
cpSync(sourcePlugin, targetPlugin, { recursive: true });
|
|
99
|
-
|
|
258
|
+
const localMarketplace = writeLocalMarketplace(legacyHome);
|
|
259
|
+
registerClaudePlugin(targetPlugin, localMarketplace, home);
|
|
260
|
+
|
|
261
|
+
const legacyRoot = versionRoot(legacyHome);
|
|
262
|
+
rmSync(legacyRoot, { recursive: true, force: true });
|
|
263
|
+
mkdirSync(dirname(pluginPathForRoot(legacyRoot)), { recursive: true });
|
|
264
|
+
cpSync(sourcePlugin, pluginPathForRoot(legacyRoot), { recursive: true });
|
|
265
|
+
resetCurrentPointer(legacyHome, legacyRoot);
|
|
100
266
|
|
|
101
267
|
process.stdout.write(`INSTALL_PASS: LazyClaude ${version} installed\n`);
|
|
268
|
+
process.stdout.write(`Claude plugin: ${pluginKey}\n`);
|
|
269
|
+
process.stdout.write(`Marketplace: ${localMarketplace}\n`);
|
|
102
270
|
process.stdout.write(`Plugin path: ${intendedPluginPath(home)}\n`);
|
|
103
|
-
process.stdout.write(
|
|
271
|
+
process.stdout.write("Launch with: claude\n");
|
|
104
272
|
};
|
|
105
273
|
|
|
106
274
|
const doctor = ({ dryRun }) => {
|
|
@@ -109,10 +277,13 @@ const doctor = ({ dryRun }) => {
|
|
|
109
277
|
process.stdout.write("DRY_RUN: doctor LazyClaude install\n");
|
|
110
278
|
process.stdout.write(`Would check plugin files under: ${pluginPath}\n`);
|
|
111
279
|
process.stdout.write(`Would run: claude plugin validate ${pluginPath}\n`);
|
|
280
|
+
process.stdout.write(`Would check Claude plugin registry: ${pluginKey}\n`);
|
|
112
281
|
return;
|
|
113
282
|
}
|
|
114
283
|
|
|
115
284
|
const pluginPath = requireInstalledPluginPath();
|
|
285
|
+
const registryEntry = readInstalledPlugins().plugins[pluginKey]?.[0];
|
|
286
|
+
if (!registryEntry) fail(`LazyClaude is missing from Claude plugin registry: ${pluginKey}`);
|
|
116
287
|
const requiredFiles = [
|
|
117
288
|
".claude-plugin/plugin.json",
|
|
118
289
|
".mcp.json",
|
|
@@ -141,10 +312,17 @@ const doctor = ({ dryRun }) => {
|
|
|
141
312
|
fail("Claude plugin validation failed.");
|
|
142
313
|
}
|
|
143
314
|
process.stdout.write("CLAUDE_PLUGIN_VALIDATE_PASS\n");
|
|
315
|
+
const details = spawnSync("claude", ["plugin", "details", pluginKey], { encoding: "utf8" });
|
|
316
|
+
if (details.status !== 0) {
|
|
317
|
+
if (details.stdout) process.stderr.write(details.stdout);
|
|
318
|
+
if (details.stderr) process.stderr.write(details.stderr);
|
|
319
|
+
fail("Claude plugin details failed.");
|
|
320
|
+
}
|
|
321
|
+
process.stdout.write("CLAUDE_PLUGIN_DETAILS_PASS\n");
|
|
144
322
|
}
|
|
145
323
|
|
|
146
324
|
process.stdout.write(`Plugin path: ${pluginPath}\n`);
|
|
147
|
-
process.stdout.write(
|
|
325
|
+
process.stdout.write("Launch with: claude\n");
|
|
148
326
|
process.stdout.write("DOCTOR_PASS\n");
|
|
149
327
|
};
|
|
150
328
|
|
|
@@ -155,8 +333,8 @@ const printPath = () => {
|
|
|
155
333
|
const runClaude = ({ dryRun, rest }) => {
|
|
156
334
|
const separatorIndex = rest.indexOf("--");
|
|
157
335
|
const claudeArgs = separatorIndex === -1 ? rest : rest.slice(separatorIndex + 1);
|
|
158
|
-
|
|
159
|
-
const command = ["claude",
|
|
336
|
+
if (!dryRun) requireInstalledPluginPath();
|
|
337
|
+
const command = ["claude", ...claudeArgs];
|
|
160
338
|
|
|
161
339
|
if (dryRun) {
|
|
162
340
|
process.stdout.write(command.join(" "));
|
|
@@ -171,12 +349,17 @@ const runClaude = ({ dryRun, rest }) => {
|
|
|
171
349
|
|
|
172
350
|
const uninstall = ({ dryRun }) => {
|
|
173
351
|
const home = lazyHome();
|
|
352
|
+
const pluginCacheRoot = join(claudeHome(), "plugins", "cache", "lazyclaude-ai");
|
|
174
353
|
if (dryRun) {
|
|
175
|
-
process.stdout.write(`DRY_RUN: remove ${join(home, "lazyclaude-ai")}
|
|
354
|
+
process.stdout.write(`DRY_RUN: remove ${join(home, "lazyclaude-ai")}, ${currentRoot(home)}, ${marketplaceRoot(home)}, ${pluginCacheRoot}, ${pluginKey}, and its marketplace/settings entries\n`);
|
|
176
355
|
return;
|
|
177
356
|
}
|
|
178
357
|
rmSync(currentRoot(home), { recursive: true, force: true });
|
|
179
358
|
rmSync(join(home, "lazyclaude-ai"), { recursive: true, force: true });
|
|
359
|
+
rmSync(marketplaceRoot(home), { recursive: true, force: true });
|
|
360
|
+
unregisterClaudePlugin();
|
|
361
|
+
unregisterMarketplace();
|
|
362
|
+
rmSync(pluginCacheRoot, { recursive: true, force: true });
|
|
180
363
|
process.stdout.write("UNINSTALL_PASS\n");
|
|
181
364
|
};
|
|
182
365
|
|
package/docs/migration.md
CHANGED
|
@@ -26,14 +26,22 @@ claude --plugin-dir ./plugins/lazyclaude
|
|
|
26
26
|
For fresh machines, the quiet npm distribution path is:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
npx lazyclaude-ai install
|
|
29
|
+
npx --yes lazyclaude-ai install
|
|
30
30
|
bunx lazyclaude-ai install
|
|
31
31
|
lazyclaude doctor
|
|
32
|
-
lazyclaude run
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
Launch Claude Code normally after install:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
claude
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The installer writes the user plugin cache, `plugins/installed_plugins.json`,
|
|
41
|
+
the `settings.json` `enabledPlugins` entry for `lazyclaude@lazyclaude-ai`, and
|
|
42
|
+
a LazyClaude-managed local marketplace entry in `known_marketplaces.json`.
|
|
43
|
+
This keeps installation convenient without requiring public repo promotion,
|
|
44
|
+
manual `--plugin-dir` launch commands, or a remote Claude marketplace entry.
|
|
37
45
|
|
|
38
46
|
If OMC/omc is already installed in Claude Code, keep it disabled or start a
|
|
39
47
|
separate Claude Code session without OMC while testing LazyClaude. This repo no
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazyclaude-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Claude Code-native LazyCodex-style workflow distribution.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"plugins",
|
|
14
14
|
"scripts",
|
|
15
15
|
"README.md",
|
|
16
|
+
"README_ko-KR.md",
|
|
16
17
|
"REFERENCE.md",
|
|
17
18
|
"RELEASE_CHECKLIST.md",
|
|
18
19
|
"LICENSE"
|
|
@@ -4,6 +4,7 @@ set -u
|
|
|
4
4
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
5
|
EVIDENCE="$ROOT/evidence/portable-qa-install.txt"
|
|
6
6
|
TMP_HOME=""
|
|
7
|
+
TMP_CLAUDE_HOME=""
|
|
7
8
|
|
|
8
9
|
mkdir -p "$ROOT/evidence"
|
|
9
10
|
: > "$EVIDENCE"
|
|
@@ -12,6 +13,9 @@ cleanup() {
|
|
|
12
13
|
if [ -n "$TMP_HOME" ]; then
|
|
13
14
|
rm -rf "$TMP_HOME"
|
|
14
15
|
fi
|
|
16
|
+
if [ -n "$TMP_CLAUDE_HOME" ]; then
|
|
17
|
+
rm -rf "$TMP_CLAUDE_HOME"
|
|
18
|
+
fi
|
|
15
19
|
}
|
|
16
20
|
trap cleanup EXIT INT TERM
|
|
17
21
|
|
|
@@ -22,7 +26,9 @@ trap cleanup EXIT INT TERM
|
|
|
22
26
|
} >> "$EVIDENCE"
|
|
23
27
|
|
|
24
28
|
TMP_HOME="$(mktemp -d "${TMPDIR:-/tmp/}lazyclaude-portable-qa.XXXXXX")"
|
|
29
|
+
TMP_CLAUDE_HOME="$(mktemp -d "${TMPDIR:-/tmp/}lazyclaude-claude-qa.XXXXXX")"
|
|
25
30
|
export LAZYCLAUDE_HOME="$TMP_HOME"
|
|
31
|
+
export CLAUDE_CONFIG_DIR="$TMP_CLAUDE_HOME"
|
|
26
32
|
|
|
27
33
|
run_step() {
|
|
28
34
|
local label="$1"
|
|
@@ -55,11 +61,45 @@ if [ ! -f "$PLUGIN_PATH/.claude-plugin/plugin.json" ]; then
|
|
|
55
61
|
echo "INSTALL_FAIL"
|
|
56
62
|
exit 1
|
|
57
63
|
fi
|
|
64
|
+
if [ ! -f "$TMP_CLAUDE_HOME/plugins/installed_plugins.json" ]; then
|
|
65
|
+
echo "INSTALL_FAIL: Claude plugin registry missing" >> "$EVIDENCE"
|
|
66
|
+
echo "INSTALL_FAIL"
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
if [ ! -f "$TMP_CLAUDE_HOME/settings.json" ]; then
|
|
70
|
+
echo "INSTALL_FAIL: Claude settings missing" >> "$EVIDENCE"
|
|
71
|
+
echo "INSTALL_FAIL"
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
if [ ! -f "$TMP_CLAUDE_HOME/plugins/known_marketplaces.json" ]; then
|
|
75
|
+
echo "INSTALL_FAIL: Claude known marketplaces missing" >> "$EVIDENCE"
|
|
76
|
+
echo "INSTALL_FAIL"
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
if [ ! -f "$TMP_HOME/marketplaces/lazyclaude-ai/.claude-plugin/marketplace.json" ]; then
|
|
80
|
+
echo "INSTALL_FAIL: LazyClaude local marketplace missing" >> "$EVIDENCE"
|
|
81
|
+
echo "INSTALL_FAIL"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
node -e 'const fs = require("fs"); const p = process.argv[1]; const registry = JSON.parse(fs.readFileSync(`${p}/plugins/installed_plugins.json`, "utf8")); const entry = registry.plugins["lazyclaude@lazyclaude-ai"]?.[0]; if (!entry || entry.scope !== "user" || !entry.installPath.includes("/plugins/cache/lazyclaude-ai/lazyclaude/")) process.exit(1);' "$TMP_CLAUDE_HOME"
|
|
85
|
+
node -e 'const fs = require("fs"); const p = process.argv[1]; const h = process.argv[2]; const settings = JSON.parse(fs.readFileSync(`${p}/settings.json`, "utf8")); const market = settings.extraKnownMarketplaces?.["lazyclaude-ai"]; if (settings.enabledPlugins?.["lazyclaude@lazyclaude-ai"] !== true || market?.source?.source !== "directory" || market.source.path !== `${h}/marketplaces/lazyclaude-ai`) process.exit(1);' "$TMP_CLAUDE_HOME" "$TMP_HOME"
|
|
86
|
+
node -e 'const fs = require("fs"); const p = process.argv[1]; const h = process.argv[2]; const known = JSON.parse(fs.readFileSync(`${p}/plugins/known_marketplaces.json`, "utf8")); const market = known["lazyclaude-ai"]; if (market?.source?.source !== "directory" || market.installLocation !== `${h}/marketplaces/lazyclaude-ai`) process.exit(1);' "$TMP_CLAUDE_HOME" "$TMP_HOME"
|
|
58
87
|
echo "INSTALL_PASS" >> "$EVIDENCE"
|
|
59
88
|
|
|
60
89
|
run_step DOCTOR node "$ROOT/bin/lazyclaude-ai.js" doctor
|
|
61
90
|
echo "DOCTOR_PASS" >> "$EVIDENCE"
|
|
62
91
|
|
|
92
|
+
if command -v claude >/dev/null 2>&1; then
|
|
93
|
+
run_step CLAUDE_PLUGIN_DETAILS claude plugin details lazyclaude@lazyclaude-ai
|
|
94
|
+
if ! grep -q "Skills (7)" "$EVIDENCE" || ! grep -q "ulw-loop" "$EVIDENCE" || ! grep -q "ulw-plan" "$EVIDENCE" || ! grep -q "Hooks (4)" "$EVIDENCE"; then
|
|
95
|
+
echo "CLAUDE_PLUGIN_DETAILS_FAIL: expected LazyClaude skills/hooks inventory" >> "$EVIDENCE"
|
|
96
|
+
echo "CLAUDE_PLUGIN_DETAILS_FAIL"
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
else
|
|
100
|
+
echo "CLAUDE_PLUGIN_DETAILS_SKIP: claude executable not found" >> "$EVIDENCE"
|
|
101
|
+
fi
|
|
102
|
+
|
|
63
103
|
run_step DRY_RUN_UNINSTALL node "$ROOT/bin/lazyclaude-ai.js" --dry-run uninstall
|
|
64
104
|
if [ ! -d "$PLUGIN_PATH" ]; then
|
|
65
105
|
echo "DRY_RUN_UNINSTALL_FAIL: plugin removed during dry-run" >> "$EVIDENCE"
|
|
@@ -67,14 +107,17 @@ if [ ! -d "$PLUGIN_PATH" ]; then
|
|
|
67
107
|
fi
|
|
68
108
|
|
|
69
109
|
run_step UNINSTALL node "$ROOT/bin/lazyclaude-ai.js" uninstall
|
|
70
|
-
if [ -e "$TMP_HOME/current" ] || [ -e "$TMP_HOME/lazyclaude-ai" ]; then
|
|
110
|
+
if [ -e "$TMP_HOME/current" ] || [ -e "$TMP_HOME/lazyclaude-ai" ] || [ -e "$TMP_CLAUDE_HOME/plugins/cache/lazyclaude-ai" ]; then
|
|
71
111
|
echo "UNINSTALL_FAIL: managed state remains" >> "$EVIDENCE"
|
|
72
112
|
echo "UNINSTALL_FAIL"
|
|
73
113
|
exit 1
|
|
74
114
|
fi
|
|
115
|
+
node -e 'const fs = require("fs"); const p = process.argv[1]; const settings = JSON.parse(fs.readFileSync(`${p}/settings.json`, "utf8")); if (settings.enabledPlugins?.["lazyclaude@lazyclaude-ai"] !== undefined) process.exit(1);' "$TMP_CLAUDE_HOME"
|
|
116
|
+
node -e 'const fs = require("fs"); const p = process.argv[1]; const settings = JSON.parse(fs.readFileSync(`${p}/settings.json`, "utf8")); const known = JSON.parse(fs.readFileSync(`${p}/plugins/known_marketplaces.json`, "utf8")); if (settings.extraKnownMarketplaces?.["lazyclaude-ai"] !== undefined || known["lazyclaude-ai"] !== undefined) process.exit(1);' "$TMP_CLAUDE_HOME"
|
|
75
117
|
echo "UNINSTALL_PASS" >> "$EVIDENCE"
|
|
76
118
|
|
|
77
119
|
cleanup
|
|
78
120
|
TMP_HOME=""
|
|
121
|
+
TMP_CLAUDE_HOME=""
|
|
79
122
|
echo "CLEANUP: removed portable QA temp home" >> "$EVIDENCE"
|
|
80
123
|
echo "PORTABLE_QA_PASS"
|