llm-wiki-kit 0.2.4 → 0.2.6
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 +17 -1
- package/docs/manual.md +31 -1
- package/docs/operations.md +51 -2
- package/docs/troubleshooting.md +20 -1
- package/package.json +1 -1
- package/src/cli.js +3 -2
- package/src/doctor.js +23 -7
- package/src/fs-utils.js +11 -0
- package/src/install.js +72 -53
- package/src/platform.js +178 -0
- package/src/update-notice.js +2 -0
- package/src/update.js +4 -1
package/README.md
CHANGED
|
@@ -16,6 +16,20 @@ llm-wiki doctor --workspace /apps
|
|
|
16
16
|
|
|
17
17
|
Restart Claude Code and Codex sessions after installation.
|
|
18
18
|
|
|
19
|
+
### Native Windows
|
|
20
|
+
|
|
21
|
+
Native Windows is supported through the npm-generated `llm-wiki.cmd` shim. Use PowerShell or Windows Terminal with Node.js 20+:
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
npm install -g llm-wiki-kit@latest
|
|
25
|
+
llm-wiki install --workspace C:\path\to\project --profile standard
|
|
26
|
+
llm-wiki doctor --workspace C:\path\to\project
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
On Windows, `llm-wiki install` does not create a Unix-style `~/.local/bin` symlink. It verifies the npm shim on `PATH`, installs Codex hooks with `commandWindows`, and writes Claude Code hooks with a Windows-safe `node.exe <bin>` command. Restart Codex and Claude Code after installation.
|
|
30
|
+
|
|
31
|
+
Use WSL2 instead when your repository and tooling already live in Linux. Native Windows support is for Windows-hosted projects and the native Codex/Claude Code surfaces.
|
|
32
|
+
|
|
19
33
|
The default install mode is npm global install. On servers where the global npm prefix is root-owned, use sudo:
|
|
20
34
|
|
|
21
35
|
```bash
|
|
@@ -121,9 +135,11 @@ Installed npm runtimes also perform a cached update notice check from hooks whil
|
|
|
121
135
|
|
|
122
136
|
For `llm-wiki-kit` code releases, source tests are not enough. After code changes, publish the package, install the newly published version, and verify the installed CLI and hooks with `version`, `status`, `doctor`, `update`, `lint`, and hook smoke checks before calling the release complete.
|
|
123
137
|
|
|
138
|
+
Native Windows changes require a real Windows smoke before release. The release gate is: install the packed candidate on a Windows host, run `llm-wiki install`, `llm-wiki status`, and `llm-wiki doctor` against a temporary Windows project, inspect `%USERPROFILE%\.codex\hooks.json` and `%USERPROFILE%\.claude\settings.json`, then repeat the minimal smoke after `npm install -g llm-wiki-kit@latest` once published. Simulated unit tests are not enough for the Windows support claim.
|
|
139
|
+
|
|
124
140
|
After a plain `npm install -g llm-wiki-kit@latest`, existing hooks keep working when they already point at the global npm package path. The next `SessionStart`/`InstructionsLoaded` hook automatically reapplies safe managed template updates for the active project root. Clearly generated older `llm-wiki/AGENTS.md` and procedure files are refreshed even when old state is missing; user-edited files are not overwritten and are surfaced to the active agent as cleanup work. If hooks point at a source checkout or stale shim, run `llm-wiki post-update --workspace <project>` or `llm-wiki install --workspace <project>` once to reconnect them.
|
|
125
141
|
|
|
126
|
-
`llm-wiki install` no longer creates a user-local `~/.local/bin/llm-wiki` shim when an npm/nvm global `llm-wiki` command already resolves to the current runtime. If an older kit-managed local shim is shadowing that npm command, install backs it up and removes it.
|
|
142
|
+
On Linux/macOS, `llm-wiki install` no longer creates a user-local `~/.local/bin/llm-wiki` shim when an npm/nvm global `llm-wiki` command already resolves to the current runtime. If an older kit-managed local shim is shadowing that npm command, install backs it up and removes it. On Windows, the npm-generated `llm-wiki.cmd` shim is the supported command entrypoint and no local symlink is created.
|
|
127
143
|
|
|
128
144
|
On PCs that use nvm or user-local npm, prefer the non-sudo global install and make sure the `llm-wiki` command resolves to that npm package:
|
|
129
145
|
|
package/docs/manual.md
CHANGED
|
@@ -120,6 +120,14 @@ llm-wiki install --workspace /path/to/project --profile standard
|
|
|
120
120
|
llm-wiki install --workspace /path/to/project --profile standard --no-project
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
Native Windows에서는 PowerShell/Windows Terminal에서 npm shim을 사용한다.
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
npm install -g llm-wiki-kit@latest
|
|
127
|
+
llm-wiki install --workspace C:\path\to\project --profile standard
|
|
128
|
+
llm-wiki doctor --workspace C:\path\to\project
|
|
129
|
+
```
|
|
130
|
+
|
|
123
131
|
`standard` profile은 기본 profile이다.
|
|
124
132
|
|
|
125
133
|
- context injection
|
|
@@ -130,7 +138,7 @@ llm-wiki install --workspace /path/to/project --profile standard --no-project
|
|
|
130
138
|
|
|
131
139
|
`--no-project`는 hook/bin 설치만 하고 현재 workspace bootstrap은 하지 않을 때 사용한다.
|
|
132
140
|
|
|
133
|
-
global npm package 설치 자체는 환경에 따라 `npm install -g` 또는 `sudo npm install -g`로 수행한다. hook 설정 갱신은 보통 일반 사용자 home 아래 파일을 수정하므로 `llm-wiki install`은 해당 user로 실행한다.
|
|
141
|
+
global npm package 설치 자체는 환경에 따라 `npm install -g` 또는 `sudo npm install -g`로 수행한다. hook 설정 갱신은 보통 일반 사용자 home 아래 파일을 수정하므로 `llm-wiki install`은 해당 user로 실행한다. Windows에서는 npm이 만든 `llm-wiki.cmd`를 표준 command shim으로 사용하며 Unix-style `~/.local/bin` symlink를 만들지 않는다. Codex hook에는 Windows 실행용 `commandWindows`가 함께 기록된다.
|
|
134
142
|
|
|
135
143
|
### `llm-wiki update`
|
|
136
144
|
|
|
@@ -416,6 +424,18 @@ node --test
|
|
|
416
424
|
npm pack --dry-run
|
|
417
425
|
```
|
|
418
426
|
|
|
427
|
+
Native Windows 관련 변경은 publish 전 실제 Windows 설치 검증이 필수다. 후보 tarball을 Windows host에 설치하고 임시 project에서 다음을 확인한다.
|
|
428
|
+
|
|
429
|
+
```powershell
|
|
430
|
+
npm install -g .\llm-wiki-kit-<version>.tgz
|
|
431
|
+
llm-wiki version
|
|
432
|
+
llm-wiki install --workspace C:\Temp\llm-wiki-kit-smoke --profile standard
|
|
433
|
+
llm-wiki status --workspace C:\Temp\llm-wiki-kit-smoke
|
|
434
|
+
llm-wiki doctor --workspace C:\Temp\llm-wiki-kit-smoke
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
검증에는 `%USERPROFILE%\.codex\hooks.json`의 `commandWindows`, `%USERPROFILE%\.claude\settings.json`의 Windows-safe `node.exe` command, project-local `llm-wiki/` 생성, sample hook roundtrip이 포함된다. 원격 자동화는 WinRM을 사용하되 credential은 환경변수나 secret store로만 전달하고 wiki/log에 저장하지 않는다.
|
|
438
|
+
|
|
419
439
|
publish 후 검증:
|
|
420
440
|
|
|
421
441
|
```bash
|
|
@@ -431,6 +451,8 @@ llm-wiki update --workspace /apps --current-only
|
|
|
431
451
|
|
|
432
452
|
`llm-wiki-kit` 코드 변경 릴리스는 publish와 새 published version 설치 후 검증까지 끝나야 완료로 본다. root-owned global npm prefix에서는 package install에 `sudo npm install -g`가 필요할 수 있고, user home hook 갱신은 일반 user로 `llm-wiki install` 또는 `post-update`를 실행한다.
|
|
433
453
|
|
|
454
|
+
Windows 지원 변경이 포함된 릴리스는 publish 후에도 같은 Windows host에서 `npm install -g llm-wiki-kit@latest` 후 최소 `version/status/doctor` smoke를 반복한다.
|
|
455
|
+
|
|
434
456
|
## Troubleshooting Shortcuts
|
|
435
457
|
|
|
436
458
|
hook이 안 돈다면:
|
|
@@ -449,6 +471,14 @@ npm root -g
|
|
|
449
471
|
node "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" version
|
|
450
472
|
```
|
|
451
473
|
|
|
474
|
+
Windows에서는 `where llm-wiki`와 npm prefix를 먼저 확인한다.
|
|
475
|
+
|
|
476
|
+
```powershell
|
|
477
|
+
where llm-wiki
|
|
478
|
+
npm root -g
|
|
479
|
+
node "$(npm root -g)\llm-wiki-kit\bin\llm-wiki.js" version
|
|
480
|
+
```
|
|
481
|
+
|
|
452
482
|
local shim이 npm global command를 shadowing하면:
|
|
453
483
|
|
|
454
484
|
```bash
|
package/docs/operations.md
CHANGED
|
@@ -24,6 +24,14 @@ npm install -g llm-wiki-kit
|
|
|
24
24
|
llm-wiki install --workspace /apps --profile standard
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
Native Windows:
|
|
28
|
+
|
|
29
|
+
```powershell
|
|
30
|
+
npm install -g llm-wiki-kit@latest
|
|
31
|
+
llm-wiki install --workspace C:\path\to\project --profile standard
|
|
32
|
+
llm-wiki doctor --workspace C:\path\to\project
|
|
33
|
+
```
|
|
34
|
+
|
|
27
35
|
The default install mode is npm global install. On servers where the global npm prefix is root-owned, use sudo:
|
|
28
36
|
|
|
29
37
|
```bash
|
|
@@ -42,8 +50,9 @@ Avoid mixing root-owned and user-local installs unless you intentionally choose
|
|
|
42
50
|
The installer:
|
|
43
51
|
|
|
44
52
|
- uses an npm/nvm global `llm-wiki` command when it already resolves to the current runtime
|
|
45
|
-
- removes an older kit-managed `~/.local/bin/llm-wiki` shim when it shadows that npm/nvm command
|
|
46
|
-
- creates a user-local shim only when no `PATH` command points at the current runtime, such as source checkout development or user-local fallback installs
|
|
53
|
+
- on Linux/macOS, removes an older kit-managed `~/.local/bin/llm-wiki` shim when it shadows that npm/nvm command
|
|
54
|
+
- on Linux/macOS, creates a user-local shim only when no `PATH` command points at the current runtime, such as source checkout development or user-local fallback installs
|
|
55
|
+
- on Windows, relies on the npm-generated `llm-wiki.cmd` shim and does not create a Unix-style local symlink
|
|
47
56
|
- backs up existing Codex/Claude settings before editing
|
|
48
57
|
- merges hook entries without removing existing hooks
|
|
49
58
|
- bootstraps the workspace `llm-wiki/`
|
|
@@ -58,6 +67,13 @@ npm install
|
|
|
58
67
|
./install.sh --workspace /apps --profile standard
|
|
59
68
|
```
|
|
60
69
|
|
|
70
|
+
On Windows source checkouts, run the CLI through Node instead of `install.sh`:
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
npm install
|
|
74
|
+
node bin\llm-wiki.js install --workspace C:\path\to\project --profile standard
|
|
75
|
+
```
|
|
76
|
+
|
|
61
77
|
Pre-publish server smoke tests can use a local tarball:
|
|
62
78
|
|
|
63
79
|
```bash
|
|
@@ -211,6 +227,25 @@ llm-wiki version
|
|
|
211
227
|
|
|
212
228
|
Do not delete the whole `~/.local/bin` directory. It may contain unrelated user tools.
|
|
213
229
|
|
|
230
|
+
## Updating Native Windows Installs
|
|
231
|
+
|
|
232
|
+
Windows uses npm's command shim. If `llm-wiki` is missing or old after install, inspect the Windows command resolution:
|
|
233
|
+
|
|
234
|
+
```powershell
|
|
235
|
+
where llm-wiki
|
|
236
|
+
npm root -g
|
|
237
|
+
node "$(npm root -g)\llm-wiki-kit\bin\llm-wiki.js" version
|
|
238
|
+
llm-wiki status --workspace C:\path\to\project
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
If `where llm-wiki` does not show the npm shim, reinstall the package in the active Node installation:
|
|
242
|
+
|
|
243
|
+
```powershell
|
|
244
|
+
npm uninstall -g llm-wiki-kit
|
|
245
|
+
npm install -g llm-wiki-kit@latest --registry=https://registry.npmjs.org/ --prefer-online
|
|
246
|
+
llm-wiki install --workspace C:\path\to\project --profile standard
|
|
247
|
+
```
|
|
248
|
+
|
|
214
249
|
Use package-name-only uninstall syntax:
|
|
215
250
|
|
|
216
251
|
```bash
|
|
@@ -242,6 +277,18 @@ npm pack --dry-run
|
|
|
242
277
|
|
|
243
278
|
Check that the tarball includes `bin/`, `src/`, `docs/`, `examples/`, `README.md`, `LICENSE`, `install.sh`, and `package.json`, but does not include project-local `llm-wiki/` contents.
|
|
244
279
|
|
|
280
|
+
For Native Windows changes, a real Windows smoke is mandatory before publishing. Install the candidate tarball on a Windows host and verify:
|
|
281
|
+
|
|
282
|
+
```powershell
|
|
283
|
+
npm install -g .\llm-wiki-kit-<version>.tgz
|
|
284
|
+
llm-wiki version
|
|
285
|
+
llm-wiki install --workspace C:\Temp\llm-wiki-kit-smoke --profile standard
|
|
286
|
+
llm-wiki status --workspace C:\Temp\llm-wiki-kit-smoke
|
|
287
|
+
llm-wiki doctor --workspace C:\Temp\llm-wiki-kit-smoke
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Inspect `%USERPROFILE%\.codex\hooks.json` for `commandWindows`, `%USERPROFILE%\.claude\settings.json` for a Windows-safe `node.exe` hook command, and the temporary project for the generated `llm-wiki/` tree. If this is automated from Linux, use WinRM and keep credentials only in environment variables or a secret store.
|
|
291
|
+
|
|
245
292
|
After publishing:
|
|
246
293
|
|
|
247
294
|
```bash
|
|
@@ -253,6 +300,8 @@ llm-wiki doctor --workspace /apps
|
|
|
253
300
|
llm-wiki update --check --workspace /apps
|
|
254
301
|
```
|
|
255
302
|
|
|
303
|
+
For Native Windows changes, repeat a minimal post-publish smoke on the same Windows host after `npm install -g llm-wiki-kit@latest`.
|
|
304
|
+
|
|
256
305
|
## Uninstall
|
|
257
306
|
|
|
258
307
|
```bash
|
package/docs/troubleshooting.md
CHANGED
|
@@ -123,6 +123,15 @@ npm root -g
|
|
|
123
123
|
node "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" version
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
+
On Native Windows, use PowerShell:
|
|
127
|
+
|
|
128
|
+
```powershell
|
|
129
|
+
where llm-wiki
|
|
130
|
+
npm ls -g llm-wiki-kit --depth=0
|
|
131
|
+
npm root -g
|
|
132
|
+
node "$(npm root -g)\llm-wiki-kit\bin\llm-wiki.js" version
|
|
133
|
+
```
|
|
134
|
+
|
|
126
135
|
If the direct `node "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" version` command prints the latest version but plain `llm-wiki` is old, reconnect through the installer:
|
|
127
136
|
|
|
128
137
|
```bash
|
|
@@ -135,7 +144,7 @@ After a manual `sudo npm install -g llm-wiki-kit@latest`, a normal-user `llm-wik
|
|
|
135
144
|
|
|
136
145
|
If `readlink -f "$(command -v llm-wiki)"` points at a repository checkout such as `/mnt/d/dev_proj/llm-wiki-kit/bin/llm-wiki.js`, `npm install -g` is not updating that checkout. Either switch the shim to the npm package as above, or update the checkout itself with `git pull` and run it intentionally as source.
|
|
137
146
|
|
|
138
|
-
Running `llm-wiki post-update --workspace /path/to/project` or `llm-wiki install --workspace /path/to/project --profile standard` also reconnects hook entries to the current runtime. `install` uses an npm/nvm global command when it already resolves to the current runtime, removes an older kit-managed local shim when it shadows that command, and creates a local shim only when no `PATH` command points at the current runtime.
|
|
147
|
+
Running `llm-wiki post-update --workspace /path/to/project` or `llm-wiki install --workspace /path/to/project --profile standard` also reconnects hook entries to the current runtime. On Linux/macOS, `install` uses an npm/nvm global command when it already resolves to the current runtime, removes an older kit-managed local shim when it shadows that command, and creates a local shim only when no `PATH` command points at the current runtime. On Windows, `install` relies on npm's `llm-wiki.cmd` shim and does not create a Unix-style symlink.
|
|
139
148
|
|
|
140
149
|
If `which -a llm-wiki` shows both `~/.local/bin/llm-wiki` and an nvm path such as `~/.nvm/versions/node/v20.20.2/bin/llm-wiki`, the `~/.local/bin` entry usually wins because it appears earlier on `PATH`. Let install handle managed shims first:
|
|
141
150
|
|
|
@@ -159,6 +168,16 @@ llm-wiki version
|
|
|
159
168
|
|
|
160
169
|
Use `npm uninstall -g llm-wiki-kit` with the package name only. `npm uninstall -g llm-wiki-kit@latest` is not the correct uninstall syntax and does not remove source checkouts or manually-created command shims.
|
|
161
170
|
|
|
171
|
+
If `where llm-wiki` is empty on Windows after npm install, the active Node npm prefix is not on `PATH` or npm did not create the shim. Reinstall with the active Node installation, then restart the terminal:
|
|
172
|
+
|
|
173
|
+
```powershell
|
|
174
|
+
npm uninstall -g llm-wiki-kit
|
|
175
|
+
npm install -g llm-wiki-kit@latest --registry=https://registry.npmjs.org/ --prefer-online
|
|
176
|
+
where llm-wiki
|
|
177
|
+
llm-wiki install --workspace C:\path\to\project --profile standard
|
|
178
|
+
llm-wiki doctor --workspace C:\path\to\project
|
|
179
|
+
```
|
|
180
|
+
|
|
162
181
|
## npm install -g Fails With EACCES
|
|
163
182
|
|
|
164
183
|
If npm tries to write under `/usr` and fails with `EACCES`, use sudo when the server policy allows system-wide global packages:
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -131,7 +131,7 @@ Usage:
|
|
|
131
131
|
`- workspace: ${value.workspace}`,
|
|
132
132
|
`- runtime bin: ${value.binPath}`,
|
|
133
133
|
`- command: ${value.commandPath || 'not found on PATH'}`,
|
|
134
|
-
`-
|
|
134
|
+
`- command shim: ${value.localBin || value.commandPath || 'not found'} (${value.localBinAction})`,
|
|
135
135
|
`- changed hooks: ${value.changed.length ? value.changed.join(', ') : 'none (already installed)'}`,
|
|
136
136
|
'Restart Codex/Claude Code sessions so new hooks are loaded.',
|
|
137
137
|
].join('\n'));
|
|
@@ -281,11 +281,12 @@ function formatStatus(value) {
|
|
|
281
281
|
return [
|
|
282
282
|
'llm-wiki-kit status',
|
|
283
283
|
`- version: ${value.runtimeVersion}`,
|
|
284
|
+
`- platform: ${value.platform || process.platform}`,
|
|
284
285
|
`- runtime: ${value.runtimeVersion} (${value.installSource})`,
|
|
285
286
|
`- bin: ${value.binPath}`,
|
|
286
287
|
`- command: ${value.commandPath || 'not found'}`,
|
|
287
288
|
`- command matches runtime: ${value.commandMatchesRuntime ? 'yes' : 'no'}`,
|
|
288
|
-
`-
|
|
289
|
+
`- command shim: ${value.localBin?.path || 'unknown'} (${value.localBin?.exists ? (value.localBin.managed ? 'managed' : 'unmanaged') : 'absent'}${value.localBin?.matchesRuntime ? ', current' : ''})`,
|
|
289
290
|
`- hooks current: ${value.hooksCurrent ? 'yes' : 'no'}`,
|
|
290
291
|
`- codex hook: ${value.codexInstalled ? 'current' : 'missing/outdated'}`,
|
|
291
292
|
`- claude hook: ${value.claudeInstalled ? 'current' : 'missing/outdated'}`,
|
package/src/doctor.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from 'child_process';
|
|
2
|
-
import { join } from 'path';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
3
|
import { mkdtemp } from 'fs/promises';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
5
6
|
import { exists, kitDataDir } from './fs-utils.js';
|
|
6
7
|
import { status } from './install.js';
|
|
8
|
+
import { isWindows } from './platform.js';
|
|
7
9
|
|
|
8
10
|
function nodeMajor() {
|
|
9
11
|
return Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
@@ -28,8 +30,8 @@ export async function runDoctor(options = {}) {
|
|
|
28
30
|
`version=${stat.claudeVersion || 'unknown'}; unsupported=${(stat.claudeUnsupportedKitEvents || []).join(', ') || 'none'}`
|
|
29
31
|
);
|
|
30
32
|
add('project-templates', 'project templates current', stat.project.managedFilesCurrent, stat.project.statePath);
|
|
31
|
-
add('codex-command', 'codex command available',
|
|
32
|
-
add('claude-command', 'claude command available',
|
|
33
|
+
add('codex-command', 'codex command available', commandVersionAvailable('codex'), 'codex --version');
|
|
34
|
+
add('claude-command', 'claude command available', commandVersionAvailable('claude'), 'claude --version');
|
|
33
35
|
add('state-writable', 'state directory writable', await canWrite(join(kitDataDir(), '.doctor')), kitDataDir());
|
|
34
36
|
add('docs', 'docs present', await docsPresent(), 'README.md and docs/');
|
|
35
37
|
add('sample-hook', 'sample hook roundtrip', await sampleHookRoundtrip(stat.binPath), 'UserPromptSubmit fixture');
|
|
@@ -38,8 +40,17 @@ export async function runDoctor(options = {}) {
|
|
|
38
40
|
return { ok: allOk, checks, workspace: stat.workspace, status: stat };
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
function commandVersionAvailable(command) {
|
|
44
|
+
const result = spawnSync(command, ['--version'], {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
shell: isWindows(),
|
|
47
|
+
timeout: 10000,
|
|
48
|
+
});
|
|
49
|
+
return result.status === 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
async function docsPresent() {
|
|
42
|
-
const root =
|
|
53
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
43
54
|
return (await exists(join(root, 'README.md'))) &&
|
|
44
55
|
(await exists(join(root, 'docs', 'concepts.md'))) &&
|
|
45
56
|
(await exists(join(root, 'docs', 'security.md')));
|
|
@@ -105,17 +116,22 @@ function failed(result, id) {
|
|
|
105
116
|
|
|
106
117
|
function doctorRemediation(result) {
|
|
107
118
|
const workspace = result.workspace || process.cwd();
|
|
119
|
+
const windows = isWindows({ platform: result.status?.platform });
|
|
108
120
|
const suggestions = [];
|
|
109
121
|
if (failed(result, 'install-source')) {
|
|
110
122
|
suggestions.push(`normal install: npm install -g llm-wiki-kit@latest && llm-wiki install --workspace ${workspace} --profile standard`);
|
|
111
|
-
suggestions.push(
|
|
123
|
+
suggestions.push(windows
|
|
124
|
+
? `source checkout development: node bin\\llm-wiki.js install --workspace ${workspace} --profile standard`
|
|
125
|
+
: `source checkout development: ./install.sh --workspace ${workspace} --profile standard`);
|
|
112
126
|
}
|
|
113
127
|
if (failed(result, 'command-path')) {
|
|
114
128
|
const local = result.status?.localBin;
|
|
115
|
-
if (result.status?.installSource === 'npm' && local?.exists && local.managed) {
|
|
129
|
+
if (!windows && result.status?.installSource === 'npm' && local?.exists && local.managed) {
|
|
116
130
|
suggestions.push(`remove stale managed local shim if it still shadows npm: rm -f ${local.path} && hash -r`);
|
|
117
131
|
}
|
|
118
|
-
suggestions.push(
|
|
132
|
+
suggestions.push(windows
|
|
133
|
+
? `reinstall npm shim and hooks: npm install -g llm-wiki-kit@latest && llm-wiki install --workspace ${workspace} --profile standard`
|
|
134
|
+
: `reconnect command and hooks: llm-wiki install --workspace ${workspace} --profile standard`);
|
|
119
135
|
}
|
|
120
136
|
if (failed(result, 'codex-hook') || failed(result, 'claude-hook') || failed(result, 'claude-settings-compatible')) {
|
|
121
137
|
suggestions.push(`install hooks: llm-wiki install --workspace ${workspace} --profile standard, then restart Codex/Claude Code sessions`);
|
package/src/fs-utils.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from 'fs/promises';
|
|
15
15
|
import { basename, dirname, join, parse, resolve } from 'path';
|
|
16
16
|
import { PROJECT_MARKERS } from './constants.js';
|
|
17
|
+
import { cacheHomeRelative, dataHomeRelative, isWindows } from './platform.js';
|
|
17
18
|
import { normalizeForStorage } from './redaction.js';
|
|
18
19
|
|
|
19
20
|
export function homeDir() {
|
|
@@ -21,13 +22,23 @@ export function homeDir() {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function dataHome() {
|
|
25
|
+
if (isWindows() && process.env.LOCALAPPDATA) return process.env.LOCALAPPDATA;
|
|
24
26
|
return process.env.XDG_DATA_HOME || join(homeDir(), '.local', 'share');
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export function cacheHome() {
|
|
30
|
+
if (isWindows() && process.env.LOCALAPPDATA) return join(process.env.LOCALAPPDATA, 'llm-wiki-kit', 'cache');
|
|
28
31
|
return process.env.XDG_CACHE_HOME || join(homeDir(), '.cache');
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
export function defaultDataHome(options = {}) {
|
|
35
|
+
return join(homeDir(), dataHomeRelative(options));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function defaultCacheHome(options = {}) {
|
|
39
|
+
return join(homeDir(), cacheHomeRelative(options));
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
export function kitDataDir() {
|
|
32
43
|
return join(dataHome(), 'llm-wiki-kit');
|
|
33
44
|
}
|
package/src/install.js
CHANGED
|
@@ -1,50 +1,26 @@
|
|
|
1
|
-
import { realpathSync } from 'fs';
|
|
2
1
|
import { chmod, lstat, readlink, unlink } from 'fs/promises';
|
|
3
|
-
import { spawnSync } from 'child_process';
|
|
4
2
|
import { join, resolve } from 'path';
|
|
5
3
|
import { CODEX_EVENTS, KIT_NAME } from './constants.js';
|
|
6
4
|
import { detectClaudeVersion, supportedClaudeEvents } from './claude-compat.js';
|
|
7
5
|
import { backupFile, exists, homeDir, readJson, safeSymlink, writeJson } from './fs-utils.js';
|
|
8
6
|
import { maintenanceSummary } from './maintenance.js';
|
|
7
|
+
import {
|
|
8
|
+
commandForNodeScript,
|
|
9
|
+
commandMatchesRuntime,
|
|
10
|
+
commandPaths as findCommandPaths,
|
|
11
|
+
inspectCommandPath,
|
|
12
|
+
isWindows,
|
|
13
|
+
realpathOrOriginal,
|
|
14
|
+
samePath,
|
|
15
|
+
sameResolvedPath,
|
|
16
|
+
} from './platform.js';
|
|
9
17
|
import { inspectProjectState } from './project-state.js';
|
|
10
18
|
import { bootstrapProject } from './project.js';
|
|
11
19
|
import { recordProject } from './projects.js';
|
|
12
20
|
import { binPath, detectInstallSource, packageRoot, runtimeVersion } from './version.js';
|
|
13
21
|
|
|
14
|
-
function
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function hookCommand(provider, eventName) {
|
|
19
|
-
return `${shellQuote(process.execPath)} ${shellQuote(binPath)} hook ${provider} ${eventName}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function llmWikiCommandPaths() {
|
|
23
|
-
const result = spawnSync('sh', ['-lc', 'which -a llm-wiki 2>/dev/null || true'], {
|
|
24
|
-
encoding: 'utf8',
|
|
25
|
-
});
|
|
26
|
-
if (result.error) return [];
|
|
27
|
-
return [...new Set(result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean))];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function realpathOrOriginal(path) {
|
|
31
|
-
if (!path) return null;
|
|
32
|
-
try {
|
|
33
|
-
return realpathSync(path);
|
|
34
|
-
} catch {
|
|
35
|
-
return path;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function sameResolvedPath(left, right) {
|
|
40
|
-
const resolvedLeft = realpathOrOriginal(left);
|
|
41
|
-
const resolvedRight = realpathOrOriginal(right);
|
|
42
|
-
return Boolean(resolvedLeft && resolvedRight && resolvedLeft === resolvedRight);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function samePath(left, right) {
|
|
46
|
-
if (!left || !right) return false;
|
|
47
|
-
return resolve(left) === resolve(right);
|
|
22
|
+
export function hookCommand(provider, eventName, options = {}) {
|
|
23
|
+
return commandForNodeScript(binPath, ['hook', provider, eventName], options);
|
|
48
24
|
}
|
|
49
25
|
|
|
50
26
|
function isKitPath(path) {
|
|
@@ -52,8 +28,12 @@ function isKitPath(path) {
|
|
|
52
28
|
}
|
|
53
29
|
|
|
54
30
|
function isKitHookEntry(entry) {
|
|
55
|
-
const serialized = JSON.stringify(entry || {});
|
|
56
|
-
return serialized.includes(KIT_NAME) || serialized.includes(binPath) || serialized.includes('/llm-wiki-kit/');
|
|
31
|
+
const serialized = normalizeHookPathText(JSON.stringify(entry || {}));
|
|
32
|
+
return serialized.includes(KIT_NAME) || serialized.includes(normalizeHookPathText(binPath)) || serialized.includes('/llm-wiki-kit/');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeHookPathText(value) {
|
|
36
|
+
return String(value || '').replace(/\\\\/g, '/').replace(/\\/g, '/');
|
|
57
37
|
}
|
|
58
38
|
|
|
59
39
|
async function inspectLocalBin(localBinPath) {
|
|
@@ -85,7 +65,7 @@ async function inspectLocalBin(localBinPath) {
|
|
|
85
65
|
}
|
|
86
66
|
|
|
87
67
|
async function reconcileLocalBin(localBinPath) {
|
|
88
|
-
const commandPathsBefore =
|
|
68
|
+
const commandPathsBefore = await findCommandPaths('llm-wiki');
|
|
89
69
|
const localBefore = await inspectLocalBin(localBinPath);
|
|
90
70
|
const alternateRuntimeCommand = commandPathsBefore.some((path) => (
|
|
91
71
|
!samePath(path, localBinPath) && sameResolvedPath(path, binPath)
|
|
@@ -114,7 +94,7 @@ async function reconcileLocalBin(localBinPath) {
|
|
|
114
94
|
await safeSymlink(binPath, localBinPath);
|
|
115
95
|
}
|
|
116
96
|
|
|
117
|
-
const commandPathsAfter =
|
|
97
|
+
const commandPathsAfter = await findCommandPaths('llm-wiki');
|
|
118
98
|
const localAfter = await inspectLocalBin(localBinPath);
|
|
119
99
|
return {
|
|
120
100
|
...localAfter,
|
|
@@ -126,6 +106,23 @@ async function reconcileLocalBin(localBinPath) {
|
|
|
126
106
|
};
|
|
127
107
|
}
|
|
128
108
|
|
|
109
|
+
async function reconcileWindowsCommand() {
|
|
110
|
+
const commandPathsBefore = await findCommandPaths('llm-wiki', { platform: 'win32' });
|
|
111
|
+
const commandPath = commandPathsBefore[0] || null;
|
|
112
|
+
const command = await inspectCommandPath(commandPath, binPath, {
|
|
113
|
+
platform: 'win32',
|
|
114
|
+
installSource: detectInstallSource(),
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
...command,
|
|
118
|
+
action: command.matchesRuntime ? 'skipped-windows-npm-shim-available' : 'skipped-windows-no-local-shim',
|
|
119
|
+
alternateRuntimeCommand: command.matchesRuntime,
|
|
120
|
+
commandPathsBefore,
|
|
121
|
+
commandPathsAfter: commandPathsBefore,
|
|
122
|
+
commandPath,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
129
126
|
function addHook(hooks, eventName, command, options = {}) {
|
|
130
127
|
hooks[eventName] = Array.isArray(hooks[eventName]) ? hooks[eventName] : [];
|
|
131
128
|
const already = hooks[eventName].some((entry) => (
|
|
@@ -142,6 +139,7 @@ function addHook(hooks, eventName, command, options = {}) {
|
|
|
142
139
|
},
|
|
143
140
|
],
|
|
144
141
|
};
|
|
142
|
+
if (options.commandWindows) entry.hooks[0].commandWindows = options.commandWindows;
|
|
145
143
|
if (options.matcher) entry.matcher = options.matcher;
|
|
146
144
|
hooks[eventName].push(entry);
|
|
147
145
|
return true;
|
|
@@ -188,12 +186,23 @@ function unsupportedKitClaudeEvents(hooks, supportedEvents) {
|
|
|
188
186
|
.sort();
|
|
189
187
|
}
|
|
190
188
|
|
|
189
|
+
function hookTextIncludes(text, value) {
|
|
190
|
+
return normalizeHookPathText(text).includes(normalizeHookPathText(value));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function hookEventIncludesBin(entries) {
|
|
194
|
+
return hookTextIncludes(JSON.stringify(entries || {}), binPath);
|
|
195
|
+
}
|
|
196
|
+
|
|
191
197
|
export async function install(options = {}) {
|
|
192
198
|
const workspace = resolve(options.workspace || process.cwd());
|
|
193
199
|
await chmod(binPath, 0o755).catch(() => {});
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
200
|
+
const platform = options.platform || process.platform;
|
|
201
|
+
const localBin = isWindows({ platform }) ? null : join(homeDir(), '.local', 'bin');
|
|
202
|
+
const localBinPath = localBin ? join(localBin, 'llm-wiki') : null;
|
|
203
|
+
const localBinResult = isWindows({ platform })
|
|
204
|
+
? await reconcileWindowsCommand()
|
|
205
|
+
: await reconcileLocalBin(localBinPath);
|
|
197
206
|
if (!options.noProject) {
|
|
198
207
|
await bootstrapProject(workspace, { profile: options.profile || 'standard', recordState: true });
|
|
199
208
|
await recordProject(workspace, 'install');
|
|
@@ -213,7 +222,9 @@ export async function install(options = {}) {
|
|
|
213
222
|
}
|
|
214
223
|
for (const eventName of CODEX_EVENTS) {
|
|
215
224
|
const matcher = eventName === 'SessionStart' ? 'startup|resume|clear' : undefined;
|
|
216
|
-
|
|
225
|
+
const command = hookCommand('codex', eventName, { platform });
|
|
226
|
+
const commandWindows = isWindows({ platform }) ? hookCommand('codex', eventName, { platform: 'win32' }) : undefined;
|
|
227
|
+
if (addHook(codex.hooks, eventName, command, { matcher, commandWindows })) {
|
|
217
228
|
codexChanged = true;
|
|
218
229
|
changed.push(`codex:${eventName}`);
|
|
219
230
|
}
|
|
@@ -241,7 +252,7 @@ export async function install(options = {}) {
|
|
|
241
252
|
}
|
|
242
253
|
for (const eventName of claudeEvents) {
|
|
243
254
|
const matcher = eventName === 'SessionStart' ? 'startup|resume|clear' : undefined;
|
|
244
|
-
if (addHook(claude.hooks, eventName, hookCommand('claude', eventName), { matcher })) {
|
|
255
|
+
if (addHook(claude.hooks, eventName, hookCommand('claude', eventName, { platform }), { matcher })) {
|
|
245
256
|
claudeChanged = true;
|
|
246
257
|
changed.push(`claude:${eventName}`);
|
|
247
258
|
}
|
|
@@ -294,31 +305,39 @@ export async function uninstall(options = {}) {
|
|
|
294
305
|
|
|
295
306
|
export async function status(options = {}) {
|
|
296
307
|
const workspace = resolve(options.workspace || process.cwd());
|
|
308
|
+
const platform = options.platform || process.platform;
|
|
297
309
|
const codexHooksPath = join(homeDir(), '.codex', 'hooks.json');
|
|
298
310
|
const claudeSettingsPath = join(homeDir(), '.claude', 'settings.json');
|
|
299
311
|
const codex = await readJson(codexHooksPath, {});
|
|
300
312
|
const claude = await readJson(claudeSettingsPath, {});
|
|
301
313
|
const claudeDetection = detectClaudeVersion();
|
|
302
314
|
const claudeEvents = supportedClaudeEvents(claudeDetection);
|
|
303
|
-
const claudeMissingEvents = claudeEvents.filter((eventName) => !
|
|
315
|
+
const claudeMissingEvents = claudeEvents.filter((eventName) => !hookEventIncludesBin(claude.hooks?.[eventName] || []));
|
|
304
316
|
const claudeUnsupportedKitEvents = unsupportedKitClaudeEvents(claude.hooks || {}, claudeEvents);
|
|
305
|
-
const codexInstalled =
|
|
317
|
+
const codexInstalled = hookEventIncludesBin(codex.hooks || {});
|
|
306
318
|
const claudeInstalled = claudeMissingEvents.length === 0 && claudeUnsupportedKitEvents.length === 0;
|
|
307
|
-
const
|
|
308
|
-
const commandPath =
|
|
319
|
+
const discoveredCommandPaths = await findCommandPaths('llm-wiki', { platform });
|
|
320
|
+
const commandPath = discoveredCommandPaths[0] || null;
|
|
309
321
|
const resolvedCommandPath = realpathOrOriginal(commandPath);
|
|
310
322
|
const resolvedBinPath = realpathOrOriginal(binPath);
|
|
311
|
-
const localBinPath = join(homeDir(), '.local', 'bin', 'llm-wiki');
|
|
312
|
-
const localBin =
|
|
323
|
+
const localBinPath = isWindows({ platform }) ? null : join(homeDir(), '.local', 'bin', 'llm-wiki');
|
|
324
|
+
const localBin = localBinPath
|
|
325
|
+
? await inspectLocalBin(localBinPath)
|
|
326
|
+
: await inspectCommandPath(commandPath, binPath, { platform, installSource: detectInstallSource() });
|
|
327
|
+
const commandMatches = await commandMatchesRuntime(commandPath, binPath, {
|
|
328
|
+
platform,
|
|
329
|
+
installSource: detectInstallSource(),
|
|
330
|
+
});
|
|
313
331
|
return {
|
|
314
332
|
workspace,
|
|
333
|
+
platform,
|
|
315
334
|
runtimeVersion: runtimeVersion(),
|
|
316
335
|
packageRoot,
|
|
317
336
|
installSource: detectInstallSource(),
|
|
318
337
|
binPath,
|
|
319
338
|
commandPath,
|
|
320
|
-
commandPaths,
|
|
321
|
-
commandMatchesRuntime: Boolean(resolvedCommandPath && resolvedBinPath && resolvedCommandPath === resolvedBinPath),
|
|
339
|
+
commandPaths: discoveredCommandPaths,
|
|
340
|
+
commandMatchesRuntime: Boolean(commandMatches || (resolvedCommandPath && resolvedBinPath && resolvedCommandPath === resolvedBinPath)),
|
|
322
341
|
localBin,
|
|
323
342
|
codexInstalled,
|
|
324
343
|
claudeInstalled,
|
package/src/platform.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { realpathSync } from 'fs';
|
|
2
|
+
import { access, lstat, readFile } from 'fs/promises';
|
|
3
|
+
import { constants as fsConstants } from 'fs';
|
|
4
|
+
import { delimiter, dirname, extname, join, resolve } from 'path';
|
|
5
|
+
import { packageName, packageRoot } from './version.js';
|
|
6
|
+
|
|
7
|
+
export function runtimePlatform(options = {}) {
|
|
8
|
+
return options.platform || process.platform;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isWindows(options = {}) {
|
|
12
|
+
return runtimePlatform(options) === 'win32';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function pathDelimiter(options = {}) {
|
|
16
|
+
return isWindows(options) ? ';' : delimiter;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function pathEnvValue(env = process.env, options = {}) {
|
|
20
|
+
if (!isWindows(options)) return env.PATH || '';
|
|
21
|
+
const key = Object.keys(env).find((name) => name.toLowerCase() === 'path');
|
|
22
|
+
return key ? env[key] || '' : env.PATH || '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function dataHomeRelative(options = {}) {
|
|
26
|
+
return isWindows(options)
|
|
27
|
+
? join('AppData', 'Local')
|
|
28
|
+
: join('.local', 'share');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function cacheHomeRelative(options = {}) {
|
|
32
|
+
return isWindows(options)
|
|
33
|
+
? join('AppData', 'Local', 'llm-wiki-kit', 'cache')
|
|
34
|
+
: '.cache';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function commandQuote(value, options = {}) {
|
|
38
|
+
return isWindows(options)
|
|
39
|
+
? `"${String(value).replace(/"/g, '""')}"`
|
|
40
|
+
: `"${String(value).replace(/(["\\$`])/g, '\\$1')}"`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function commandForNodeScript(scriptPath, args = [], options = {}) {
|
|
44
|
+
if (!isWindows(options)) {
|
|
45
|
+
return [commandQuote(process.execPath, options), commandQuote(scriptPath, options), ...args].join(' ');
|
|
46
|
+
}
|
|
47
|
+
return [process.execPath, scriptPath, ...args]
|
|
48
|
+
.map((part) => commandQuote(part, options))
|
|
49
|
+
.join(' ');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function realpathOrOriginal(path) {
|
|
53
|
+
if (!path) return null;
|
|
54
|
+
try {
|
|
55
|
+
return realpathSync(path);
|
|
56
|
+
} catch {
|
|
57
|
+
return path;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function sameResolvedPath(left, right) {
|
|
62
|
+
const resolvedLeft = realpathOrOriginal(left);
|
|
63
|
+
const resolvedRight = realpathOrOriginal(right);
|
|
64
|
+
return Boolean(resolvedLeft && resolvedRight && resolvedLeft === resolvedRight);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function samePath(left, right) {
|
|
68
|
+
if (!left || !right) return false;
|
|
69
|
+
return resolve(left) === resolve(right);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function unique(values) {
|
|
73
|
+
return [...new Set(values.filter(Boolean))];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function windowsExtensions(env = process.env) {
|
|
77
|
+
return unique((env.PATHEXT || '.COM;.EXE;.BAT;.CMD;.PS1')
|
|
78
|
+
.split(';')
|
|
79
|
+
.map((part) => part.trim())
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.flatMap((part) => [part, part.toLowerCase(), part.toUpperCase()]));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function commandCandidates(dir, command, options = {}) {
|
|
85
|
+
const direct = join(dir, command);
|
|
86
|
+
if (!isWindows(options) || extname(command)) return [direct];
|
|
87
|
+
return [direct, ...windowsExtensions(options.env).map((ext) => join(dir, `${command}${ext}`))];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function commandFileExists(path, options = {}) {
|
|
91
|
+
try {
|
|
92
|
+
await access(path, isWindows(options) ? fsConstants.F_OK : fsConstants.X_OK);
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function commandPaths(command, options = {}) {
|
|
100
|
+
const env = options.env || process.env;
|
|
101
|
+
const pathValue = pathEnvValue(env, options);
|
|
102
|
+
const paths = [];
|
|
103
|
+
for (const dir of pathValue.split(pathDelimiter(options)).filter(Boolean)) {
|
|
104
|
+
for (const candidate of commandCandidates(dir, command, { ...options, env })) {
|
|
105
|
+
if (await commandFileExists(candidate, options)) paths.push(candidate);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return unique(paths);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizePathText(value) {
|
|
112
|
+
return String(value || '').replace(/\\/g, '/').toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function commandShimReferencesRuntime(commandPath, binPath, options = {}) {
|
|
116
|
+
if (!isWindows(options) || !commandPath) return false;
|
|
117
|
+
const extension = extname(commandPath).toLowerCase();
|
|
118
|
+
if (!['', '.cmd', '.bat', '.ps1'].includes(extension)) return false;
|
|
119
|
+
let content = '';
|
|
120
|
+
try {
|
|
121
|
+
content = await readFile(commandPath, 'utf8');
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const normalized = normalizePathText(content);
|
|
126
|
+
const normalizedBin = normalizePathText(binPath);
|
|
127
|
+
if (normalized.includes(normalizedBin)) return true;
|
|
128
|
+
if (options.installSource === 'npm') {
|
|
129
|
+
const commandDirPackageRoot = join(dirname(commandPath), 'node_modules', packageName());
|
|
130
|
+
return sameResolvedPath(commandDirPackageRoot, packageRoot) &&
|
|
131
|
+
normalized.includes(`node_modules/${packageName()}/bin/llm-wiki.js`);
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function commandMatchesRuntime(commandPath, binPath, options = {}) {
|
|
137
|
+
if (!commandPath || !binPath) return false;
|
|
138
|
+
if (sameResolvedPath(commandPath, binPath)) return true;
|
|
139
|
+
return commandShimReferencesRuntime(commandPath, binPath, options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function inspectCommandPath(commandPath, binPath, options = {}) {
|
|
143
|
+
if (!commandPath) {
|
|
144
|
+
return {
|
|
145
|
+
path: null,
|
|
146
|
+
exists: false,
|
|
147
|
+
symlink: false,
|
|
148
|
+
target: null,
|
|
149
|
+
resolved: null,
|
|
150
|
+
managed: false,
|
|
151
|
+
matchesRuntime: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const stat = await lstat(commandPath);
|
|
156
|
+
const resolved = realpathOrOriginal(commandPath);
|
|
157
|
+
const matchesRuntime = await commandMatchesRuntime(commandPath, binPath, options);
|
|
158
|
+
return {
|
|
159
|
+
path: commandPath,
|
|
160
|
+
exists: true,
|
|
161
|
+
symlink: stat.isSymbolicLink(),
|
|
162
|
+
target: null,
|
|
163
|
+
resolved,
|
|
164
|
+
managed: matchesRuntime,
|
|
165
|
+
matchesRuntime,
|
|
166
|
+
};
|
|
167
|
+
} catch {
|
|
168
|
+
return {
|
|
169
|
+
path: commandPath,
|
|
170
|
+
exists: false,
|
|
171
|
+
symlink: false,
|
|
172
|
+
target: null,
|
|
173
|
+
resolved: null,
|
|
174
|
+
managed: false,
|
|
175
|
+
matchesRuntime: false,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
package/src/update-notice.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'child_process';
|
|
2
2
|
import { join, resolve } from 'path';
|
|
3
3
|
import { cacheHome, readJson, writeJson } from './fs-utils.js';
|
|
4
|
+
import { isWindows } from './platform.js';
|
|
4
5
|
import { commandForProject } from './projects.js';
|
|
5
6
|
import { compareVersions, parseRegistryVersion } from './update.js';
|
|
6
7
|
import { detectInstallSource, packageName, runtimeVersion } from './version.js';
|
|
@@ -62,6 +63,7 @@ function trimDetail(value) {
|
|
|
62
63
|
function checkRegistry(target) {
|
|
63
64
|
const result = spawnSync(npmCommand(), ['view', `${packageName()}@${target}`, 'version'], {
|
|
64
65
|
encoding: 'utf8',
|
|
66
|
+
shell: isWindows(),
|
|
65
67
|
timeout: timeoutMs(),
|
|
66
68
|
});
|
|
67
69
|
if (result.status !== 0 || result.error) {
|
package/src/update.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join, resolve } from 'path';
|
|
|
3
3
|
import { exists } from './fs-utils.js';
|
|
4
4
|
import { appendWikiLog } from './project.js';
|
|
5
5
|
import { install } from './install.js';
|
|
6
|
+
import { isWindows } from './platform.js';
|
|
6
7
|
import { applyProjectTemplateUpdate, inspectProjectState } from './project-state.js';
|
|
7
8
|
import { knownProjectRoots, recordProject } from './projects.js';
|
|
8
9
|
import { binPath, detectInstallSource, packageName, runtimeVersion } from './version.js';
|
|
@@ -24,7 +25,8 @@ async function runCommand(command, args, options = {}) {
|
|
|
24
25
|
const killGraceMs = options.killGraceMs || 2000;
|
|
25
26
|
const label = options.label || commandLine(command, args);
|
|
26
27
|
const startedAt = Date.now();
|
|
27
|
-
const
|
|
28
|
+
const windows = isWindows(options);
|
|
29
|
+
const detached = !windows;
|
|
28
30
|
let stdout = '';
|
|
29
31
|
let stderr = '';
|
|
30
32
|
let settled = false;
|
|
@@ -67,6 +69,7 @@ async function runCommand(command, args, options = {}) {
|
|
|
67
69
|
child = spawn(command, args, {
|
|
68
70
|
detached,
|
|
69
71
|
env: options.env || process.env,
|
|
72
|
+
shell: windows,
|
|
70
73
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
74
|
});
|
|
72
75
|
} catch (error) {
|