llm-wiki-kit 0.2.3 → 0.2.5
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 +21 -2
- package/docs/integrations/claude-code.md +3 -1
- package/docs/integrations/codex.md +1 -1
- package/docs/manual.md +41 -2
- package/docs/operations.md +55 -3
- package/docs/troubleshooting.md +49 -1
- package/package.json +1 -1
- package/src/claude-compat.js +83 -0
- package/src/cli.js +34 -6
- package/src/doctor.js +30 -8
- package/src/fs-utils.js +11 -0
- package/src/hook.js +9 -0
- package/src/install.js +122 -56
- package/src/legacy-omx-wiki.js +222 -0
- package/src/platform.js +178 -0
- package/src/projects.js +8 -0
- package/src/update.js +128 -18
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/projects.js
CHANGED
|
@@ -58,12 +58,17 @@ async function looksLikeProjectRoot(root) {
|
|
|
58
58
|
export async function discoverProjectRoots(searchRoot, options = {}) {
|
|
59
59
|
const root = resolve(searchRoot || process.cwd());
|
|
60
60
|
const maxDirs = options.maxDirs || 5000;
|
|
61
|
+
const progressEvery = options.progressEvery || 1000;
|
|
62
|
+
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : null;
|
|
61
63
|
const roots = new Set();
|
|
62
64
|
let seen = 0;
|
|
63
65
|
|
|
64
66
|
async function walk(dir) {
|
|
65
67
|
if (seen >= maxDirs) return;
|
|
66
68
|
seen += 1;
|
|
69
|
+
if (onProgress && seen % progressEvery === 0) {
|
|
70
|
+
onProgress(`scanned ${seen}/${maxDirs} directories while discovering projects`);
|
|
71
|
+
}
|
|
67
72
|
|
|
68
73
|
if (await looksLikeProjectRoot(dir)) {
|
|
69
74
|
roots.add(dir);
|
|
@@ -91,6 +96,9 @@ export async function discoverProjectRoots(searchRoot, options = {}) {
|
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
await walk(root);
|
|
99
|
+
if (onProgress) {
|
|
100
|
+
onProgress(`project discovery scanned ${seen} director${seen === 1 ? 'y' : 'ies'}; found ${roots.size}`);
|
|
101
|
+
}
|
|
94
102
|
return [...roots].sort();
|
|
95
103
|
}
|
|
96
104
|
|
package/src/update.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
2
|
import { join, resolve } from 'path';
|
|
3
3
|
import { exists } from './fs-utils.js';
|
|
4
4
|
import { appendWikiLog } from './project.js';
|
|
@@ -7,18 +7,101 @@ import { applyProjectTemplateUpdate, inspectProjectState } from './project-state
|
|
|
7
7
|
import { knownProjectRoots, recordProject } from './projects.js';
|
|
8
8
|
import { binPath, detectInstallSource, packageName, runtimeVersion } from './version.js';
|
|
9
9
|
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
function commandLine(command, args) {
|
|
11
|
+
return [command, ...args].join(' ');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function elapsedMs(startedAt) {
|
|
15
|
+
return Date.now() - startedAt;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function progress(options, message) {
|
|
19
|
+
if (typeof options.onProgress === 'function') options.onProgress(message);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function runCommand(command, args, options = {}) {
|
|
23
|
+
const timeout = options.timeout || 120000;
|
|
24
|
+
const killGraceMs = options.killGraceMs || 2000;
|
|
25
|
+
const label = options.label || commandLine(command, args);
|
|
26
|
+
const startedAt = Date.now();
|
|
27
|
+
const detached = process.platform !== 'win32';
|
|
28
|
+
let stdout = '';
|
|
29
|
+
let stderr = '';
|
|
30
|
+
let settled = false;
|
|
31
|
+
let timedOut = false;
|
|
32
|
+
let timeoutId = null;
|
|
33
|
+
let killId = null;
|
|
34
|
+
|
|
35
|
+
progress(options, `starting ${label}`);
|
|
36
|
+
|
|
37
|
+
return new Promise((resolveResult) => {
|
|
38
|
+
let child;
|
|
39
|
+
const finish = (result) => {
|
|
40
|
+
if (settled) return;
|
|
41
|
+
settled = true;
|
|
42
|
+
clearTimeout(timeoutId);
|
|
43
|
+
clearTimeout(killId);
|
|
44
|
+
progress(options, `${label} finished in ${elapsedMs(startedAt)}ms`);
|
|
45
|
+
resolveResult({
|
|
46
|
+
...result,
|
|
47
|
+
stdout,
|
|
48
|
+
stderr,
|
|
49
|
+
elapsedMs: elapsedMs(startedAt),
|
|
50
|
+
timedOut,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const killChild = (signal) => {
|
|
54
|
+
if (!child?.pid) return;
|
|
55
|
+
try {
|
|
56
|
+
if (detached) {
|
|
57
|
+
process.kill(-child.pid, signal);
|
|
58
|
+
} else {
|
|
59
|
+
child.kill(signal);
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// The process may have already exited.
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
child = spawn(command, args, {
|
|
68
|
+
detached,
|
|
69
|
+
env: options.env || process.env,
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
finish({ status: null, signal: null, error });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
child.stdout.setEncoding('utf8');
|
|
78
|
+
child.stderr.setEncoding('utf8');
|
|
79
|
+
child.stdout.on('data', (chunk) => {
|
|
80
|
+
stdout += chunk;
|
|
81
|
+
});
|
|
82
|
+
child.stderr.on('data', (chunk) => {
|
|
83
|
+
stderr += chunk;
|
|
84
|
+
});
|
|
85
|
+
child.on('error', (error) => {
|
|
86
|
+
finish({ status: null, signal: null, error });
|
|
87
|
+
});
|
|
88
|
+
child.on('close', (status, signal) => {
|
|
89
|
+
const error = timedOut
|
|
90
|
+
? Object.assign(new Error(`${label} timed out after ${timeout}ms`), { code: 'ETIMEDOUT' })
|
|
91
|
+
: null;
|
|
92
|
+
finish({ status, signal, error });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
timeoutId = setTimeout(() => {
|
|
96
|
+
timedOut = true;
|
|
97
|
+
progress(options, `${label} exceeded ${timeout}ms; terminating`);
|
|
98
|
+
killChild('SIGTERM');
|
|
99
|
+
killId = setTimeout(() => {
|
|
100
|
+
progress(options, `${label} did not exit after SIGTERM; killing`);
|
|
101
|
+
killChild('SIGKILL');
|
|
102
|
+
}, killGraceMs);
|
|
103
|
+
}, timeout);
|
|
15
104
|
});
|
|
16
|
-
return {
|
|
17
|
-
status: result.status,
|
|
18
|
-
stdout: result.stdout || '',
|
|
19
|
-
stderr: result.stderr || '',
|
|
20
|
-
error: result.error || null,
|
|
21
|
-
};
|
|
22
105
|
}
|
|
23
106
|
|
|
24
107
|
function npmCommand() {
|
|
@@ -31,7 +114,11 @@ function binCommand() {
|
|
|
31
114
|
|
|
32
115
|
function assertCommandOk(result, label) {
|
|
33
116
|
if (result.status === 0 && !result.error) return;
|
|
34
|
-
const detail = result.stderr.trim() ||
|
|
117
|
+
const detail = result.stderr.trim() ||
|
|
118
|
+
result.stdout.trim() ||
|
|
119
|
+
result.error?.message ||
|
|
120
|
+
(result.signal ? `signal ${result.signal}` : '') ||
|
|
121
|
+
'unknown error';
|
|
35
122
|
throw new Error(`${label} failed: ${detail}`);
|
|
36
123
|
}
|
|
37
124
|
|
|
@@ -45,7 +132,13 @@ function shouldUpdateAllProjects(options = {}) {
|
|
|
45
132
|
|
|
46
133
|
async function projectRootsForUpdate(workspace, options = {}) {
|
|
47
134
|
if (!shouldUpdateAllProjects(options)) return [workspace];
|
|
48
|
-
|
|
135
|
+
progress(options, `discovering project roots under ${workspace}`);
|
|
136
|
+
const roots = await knownProjectRoots({
|
|
137
|
+
workspace,
|
|
138
|
+
maxDirs: options.maxDirs,
|
|
139
|
+
onProgress: options.onProgress,
|
|
140
|
+
});
|
|
141
|
+
progress(options, `discovered ${roots.length} project root(s)`);
|
|
49
142
|
return roots.length > 0 ? roots : [workspace];
|
|
50
143
|
}
|
|
51
144
|
|
|
@@ -97,7 +190,9 @@ export function compareVersions(a, b) {
|
|
|
97
190
|
export async function checkForUpdate(options = {}) {
|
|
98
191
|
const target = options.to || 'latest';
|
|
99
192
|
const installedVersion = runtimeVersion();
|
|
100
|
-
const result = runCommand(npmCommand(), ['view', `${packageName()}@${target}`, 'version'], {
|
|
193
|
+
const result = await runCommand(npmCommand(), ['view', `${packageName()}@${target}`, 'version'], {
|
|
194
|
+
...options,
|
|
195
|
+
label: 'npm view',
|
|
101
196
|
timeout: options.timeout || 30000,
|
|
102
197
|
});
|
|
103
198
|
assertCommandOk(result, 'npm view');
|
|
@@ -120,9 +215,16 @@ export async function postUpdate(options = {}) {
|
|
|
120
215
|
});
|
|
121
216
|
|
|
122
217
|
if (options.all) {
|
|
123
|
-
|
|
218
|
+
progress(options, `discovering project roots under ${workspace}`);
|
|
219
|
+
const workspaces = await knownProjectRoots({
|
|
220
|
+
workspace,
|
|
221
|
+
maxDirs: options.maxDirs,
|
|
222
|
+
onProgress: options.onProgress,
|
|
223
|
+
});
|
|
224
|
+
progress(options, `post-update will inspect ${workspaces.length} project root(s)`);
|
|
124
225
|
const projects = [];
|
|
125
226
|
for (const projectRoot of workspaces) {
|
|
227
|
+
progress(options, `applying managed templates to ${projectRoot}`);
|
|
126
228
|
const projectResult = await applyTemplatesToProject(projectRoot, options);
|
|
127
229
|
if (!options.noProject && await hasProjectWiki(projectRoot)) {
|
|
128
230
|
await appendWikiLog(projectRoot, `llm-wiki-kit post-update applied runtime ${runtimeVersion()}; changed templates: ${projectResult.changed.length}`);
|
|
@@ -194,7 +296,9 @@ export async function update(options = {}) {
|
|
|
194
296
|
const target = options.to || 'latest';
|
|
195
297
|
const shouldRunNpmInstall = check.updateAvailable;
|
|
196
298
|
const installResult = shouldRunNpmInstall
|
|
197
|
-
? runCommand(npmCommand(), ['install', '-g', `${packageName()}@${target}`], {
|
|
299
|
+
? await runCommand(npmCommand(), ['install', '-g', `${packageName()}@${target}`], {
|
|
300
|
+
...options,
|
|
301
|
+
label: 'npm install -g',
|
|
198
302
|
timeout: options.timeout || 120000,
|
|
199
303
|
})
|
|
200
304
|
: {
|
|
@@ -210,7 +314,13 @@ export async function update(options = {}) {
|
|
|
210
314
|
if (options.noProject) postArgs.push('--no-project');
|
|
211
315
|
if (options.codex === false) postArgs.push('--no-codex');
|
|
212
316
|
if (options.claude === false) postArgs.push('--no-claude');
|
|
213
|
-
const postResult = runCommand(process.execPath, postArgs, {
|
|
317
|
+
const postResult = await runCommand(process.execPath, postArgs, {
|
|
318
|
+
...options,
|
|
319
|
+
env: {
|
|
320
|
+
...process.env,
|
|
321
|
+
LLM_WIKI_KIT_PROGRESS: process.env.LLM_WIKI_KIT_PROGRESS || '1',
|
|
322
|
+
},
|
|
323
|
+
label: 'post-update',
|
|
214
324
|
timeout: options.timeout || 120000,
|
|
215
325
|
});
|
|
216
326
|
assertCommandOk(postResult, 'post-update');
|