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 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
- LazyClaude is intended as a quiet personal distribution. It can be prepared for
9
- public npm installation convenience without advertising, public repo promotion,
10
- or Claude marketplace publication. Do not run `npm publish`, mutate a
11
- marketplace, or promote this as publicly available without explicit user
12
- approval. Until that approval is given, this package is not published.
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
- ## Fresh PC Install
14
+ ## Quick Install
15
15
 
16
- Use one of these npm-compatible entrypoints on a new machine:
16
+ Use the npm package from any machine:
17
17
 
18
18
  ```bash
19
- npx lazyclaude-ai install
20
- bunx lazyclaude-ai install
21
- npm install -g lazyclaude-ai
22
- lazyclaude install
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
- The installer copies the packaged Claude Code plugin into a LazyClaude-managed
26
- user directory and prints the exact launch command:
28
+ Launch Claude Code normally:
27
29
 
28
30
  ```bash
29
- claude --plugin-dir ~/.lazyclaude/current/plugins/lazyclaude
31
+ claude
30
32
  ```
31
33
 
32
- Validate the install:
34
+ Alternative entrypoints:
33
35
 
34
36
  ```bash
35
- lazyclaude doctor
37
+ bunx lazyclaude-ai install
38
+ npm install -g lazyclaude-ai
39
+ lazyclaude install
36
40
  ```
37
41
 
38
- Launch Claude Code through the installed plugin:
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
- lazyclaude run -- --help
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
- Set `LAZYCLAUDE_HOME=/some/path` to install into a custom directory for testing
52
- or isolated machines.
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 Install
61
+ ## Local Development
55
62
 
56
- Use the plugin directly from this checkout while it is under development:
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
- Start Claude Code from this repo:
84
+ LazyClaude is available in normal Claude Code sessions after install:
79
85
 
80
86
  ```bash
81
- claude --plugin-dir ./plugins/lazyclaude
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/agent instructions steer Claude toward
96
- test-first work, real manual QA evidence, cleanup receipts, and no publish step
97
- without approval.
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/command activation:
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
- ## Local Development
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/` directory created by a user-level OMC plugin is ignored and is not
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
- - Publication, remote marketplace mutation, and package release all require
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 `REFERENCE.md` for the pinned LazyCodex source. See `docs/migration.md` for
176
- the Codex-to-Claude migration table.
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.
@@ -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`를 참고하세요.
@@ -1,10 +1,13 @@
1
1
  # LazyClaude Release Checklist
2
2
 
3
- Status: quiet public npm package candidate; currently unpublished until explicit
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 tags, or add a
7
- remote Claude Code marketplace entry without explicit user approval.
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
- ## Quiet Public NPM Package Release
34
+ ## Fresh Machine NPM Install Use
31
35
 
32
- Use this track only when the user explicitly approves making the package
33
- installable through `npm`, `npx`, and `bunx`.
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
- LazyClaude is tested through `claude --plugin-dir ./plugins/lazyclaude`. A root
76
- Claude marketplace file is intentionally not shipped because users may already
77
- have an OMC/omc marketplace or plugin installed. Do not add a local or remote
78
- marketplace entry without explicit user approval. If user-level OMC creates
79
- `.omc/` state during validation, keep it ignored and confirm it is absent from
80
- the package dry-run.
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
+ ```
@@ -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 Copy the packaged LazyClaude plugin into LAZYCLAUDE_HOME.
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 with the installed plugin path.
27
- update Reinstall this package version and refresh the current pointer.
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 intendedPluginPath = (home = lazyHome()) => pluginPathForRoot(currentRoot(home));
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 = lazyHome()) => existsSync(currentRoot(home));
64
+ const currentExists = (home = claudeHome()) => existsSync(claudePluginRoot(home));
55
65
 
56
- const requireInstalledPluginPath = (home = lazyHome()) => {
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 = lazyHome();
80
- const targetRoot = versionRoot(home);
81
- const targetPlugin = pluginPathForRoot(targetRoot);
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 point current: ${currentRoot(home)} -> ${targetRoot}\n`);
88
- process.stdout.write(`Launch with: claude --plugin-dir ${intendedPluginPath(home)}\n`);
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(targetRoot, { recursive: true, force: true });
255
+ rmSync(targetPlugin, { recursive: true, force: true });
97
256
  mkdirSync(dirname(targetPlugin), { recursive: true });
98
257
  cpSync(sourcePlugin, targetPlugin, { recursive: true });
99
- resetCurrentPointer(home, targetRoot);
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(`Launch with: claude --plugin-dir ${intendedPluginPath(home)}\n`);
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(`Launch with: claude --plugin-dir ${pluginPath}\n`);
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
- const pluginPath = dryRun ? intendedPluginPath() : requireInstalledPluginPath();
159
- const command = ["claude", "--plugin-dir", pluginPath, ...claudeArgs];
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")} and ${currentRoot(home)}\n`);
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
- This keeps installation convenient without requiring public repo promotion or a
36
- Claude marketplace entry.
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.0",
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"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lazyclaude",
3
3
  "description": "Claude Code-native LazyCodex-style workflow plugin.",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "author": {
6
6
  "name": "LazyClaude contributors"
7
7
  },
@@ -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"