@wipcomputer/wip-ldm-os 0.2.13 → 0.3.1
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/SKILL.md +1 -1
- package/bin/ldm.js +242 -0
- package/dist/bridge/chunk-KWGJCDGS.js +424 -0
- package/dist/bridge/cli.d.ts +1 -0
- package/dist/bridge/cli.js +215 -0
- package/dist/bridge/core.d.ts +74 -0
- package/dist/bridge/core.js +40 -0
- package/dist/bridge/mcp-server.d.ts +2 -0
- package/dist/bridge/mcp-server.js +284 -0
- package/docs/TECHNICAL.md +290 -0
- package/docs/acp-compatibility.md +30 -0
- package/docs/optional-skills.md +77 -0
- package/docs/recall.md +29 -0
- package/docs/shared-workspace.md +37 -0
- package/docs/system-pulse.md +26 -0
- package/docs/universal-installer.md +84 -0
- package/lib/messages.mjs +195 -0
- package/lib/sessions.mjs +145 -0
- package/lib/updates.mjs +173 -0
- package/package.json +9 -2
- package/src/boot/boot-hook.mjs +36 -1
- package/src/bridge/cli.ts +245 -0
- package/src/bridge/core.ts +622 -0
- package/src/bridge/mcp-server.ts +371 -0
- package/src/bridge/package.json +18 -0
- package/src/bridge/tsconfig.json +19 -0
- package/src/cron/update-check.mjs +28 -0
- package/src/hooks/stop-hook.mjs +24 -0
package/lib/updates.mjs
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/updates.mjs
|
|
3
|
+
* NPM update checker with cached manifest.
|
|
4
|
+
* Reads the extension registry, checks npm for newer versions,
|
|
5
|
+
* and writes results to ~/.ldm/state/available-updates.json.
|
|
6
|
+
* Zero external dependencies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
|
|
13
|
+
const HOME = process.env.HOME || '';
|
|
14
|
+
const LDM_ROOT = join(HOME, '.ldm');
|
|
15
|
+
const LDM_EXTENSIONS = join(LDM_ROOT, 'extensions');
|
|
16
|
+
const STATE_DIR = join(LDM_ROOT, 'state');
|
|
17
|
+
const UPDATES_PATH = join(STATE_DIR, 'available-updates.json');
|
|
18
|
+
const REGISTRY_PATH = join(LDM_EXTENSIONS, 'registry.json');
|
|
19
|
+
|
|
20
|
+
// ── Helpers ──
|
|
21
|
+
|
|
22
|
+
function readJSON(path) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeJSON(path, data) {
|
|
31
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
32
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Semver comparison ──
|
|
36
|
+
// Copied from deploy.mjs to keep this module self-contained.
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Compare two semver strings. Returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
40
|
+
*/
|
|
41
|
+
export function compareSemver(a, b) {
|
|
42
|
+
if (!a || !b) return 0;
|
|
43
|
+
const pa = a.split('.').map(Number);
|
|
44
|
+
const pb = b.split('.').map(Number);
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
const na = pa[i] || 0;
|
|
47
|
+
const nb = pb[i] || 0;
|
|
48
|
+
if (na > nb) return 1;
|
|
49
|
+
if (na < nb) return -1;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Catalog lookup ──
|
|
55
|
+
|
|
56
|
+
let _catalog = null;
|
|
57
|
+
|
|
58
|
+
function loadCatalog() {
|
|
59
|
+
if (_catalog) return _catalog;
|
|
60
|
+
try {
|
|
61
|
+
// Try the installed location first, then the repo-relative location
|
|
62
|
+
const paths = [
|
|
63
|
+
join(LDM_EXTENSIONS, 'wip-ldm-os', 'catalog.json'),
|
|
64
|
+
join(LDM_ROOT, 'catalog.json'),
|
|
65
|
+
];
|
|
66
|
+
for (const p of paths) {
|
|
67
|
+
const data = readJSON(p);
|
|
68
|
+
if (data?.components) {
|
|
69
|
+
_catalog = data;
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
return { components: [] };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the npm package name for a registry entry.
|
|
79
|
+
* Checks the catalog for an npm field, or infers from the name.
|
|
80
|
+
* @param {string} name - Extension name from registry
|
|
81
|
+
* @param {Object} info - Registry entry info
|
|
82
|
+
* @returns {string|null} npm package name or null
|
|
83
|
+
*/
|
|
84
|
+
export function resolveNpmName(name, info) {
|
|
85
|
+
// Check catalog for npm field
|
|
86
|
+
const catalog = loadCatalog();
|
|
87
|
+
for (const c of catalog.components || []) {
|
|
88
|
+
// Match by ID or by registryMatches
|
|
89
|
+
if (c.id === name || (c.registryMatches || []).includes(name)) {
|
|
90
|
+
if (c.npm) return c.npm;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if the registry entry itself has a packageName
|
|
95
|
+
if (info?.packageName) return info.packageName;
|
|
96
|
+
|
|
97
|
+
// Check deployed package.json for npm name
|
|
98
|
+
if (info?.ldmPath) {
|
|
99
|
+
const pkg = readJSON(join(info.ldmPath, 'package.json'));
|
|
100
|
+
if (pkg?.name) return pkg.name;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Update checking ──
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check for available updates by querying npm registry.
|
|
110
|
+
* Reads ~/.ldm/extensions/registry.json, checks each entry against npm,
|
|
111
|
+
* and writes results to ~/.ldm/state/available-updates.json.
|
|
112
|
+
* @returns {{ checkedAt: string, checked: number, updatesAvailable: number, updates: Array }}
|
|
113
|
+
*/
|
|
114
|
+
export function checkForUpdates() {
|
|
115
|
+
const registry = readJSON(REGISTRY_PATH);
|
|
116
|
+
if (!registry?.extensions) {
|
|
117
|
+
return { checkedAt: new Date().toISOString(), checked: 0, updatesAvailable: 0, updates: [] };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const updates = [];
|
|
121
|
+
let checked = 0;
|
|
122
|
+
|
|
123
|
+
for (const [name, info] of Object.entries(registry.extensions)) {
|
|
124
|
+
const npmName = resolveNpmName(name, info);
|
|
125
|
+
if (!npmName) continue;
|
|
126
|
+
|
|
127
|
+
const currentVersion = info.version;
|
|
128
|
+
if (!currentVersion || currentVersion === 'unknown' || currentVersion === '?') continue;
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const result = execSync(`npm view ${npmName} version 2>/dev/null`, {
|
|
132
|
+
encoding: 'utf8',
|
|
133
|
+
timeout: 10000,
|
|
134
|
+
}).trim();
|
|
135
|
+
|
|
136
|
+
checked++;
|
|
137
|
+
|
|
138
|
+
if (result && compareSemver(result, currentVersion) > 0) {
|
|
139
|
+
updates.push({
|
|
140
|
+
name,
|
|
141
|
+
packageName: npmName,
|
|
142
|
+
currentVersion,
|
|
143
|
+
latestVersion: result,
|
|
144
|
+
checkedAt: new Date().toISOString(),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// Skip on failure (network error, package not found, timeout, etc.)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const manifest = {
|
|
153
|
+
checkedAt: new Date().toISOString(),
|
|
154
|
+
checked,
|
|
155
|
+
updatesAvailable: updates.length,
|
|
156
|
+
updates,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Write to cache
|
|
160
|
+
try {
|
|
161
|
+
writeJSON(UPDATES_PATH, manifest);
|
|
162
|
+
} catch {}
|
|
163
|
+
|
|
164
|
+
return manifest;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Read the cached update manifest without re-checking npm.
|
|
169
|
+
* @returns {Object|null} The cached manifest or null if not found
|
|
170
|
+
*/
|
|
171
|
+
export function readUpdateManifest() {
|
|
172
|
+
return readJSON(UPDATES_PATH);
|
|
173
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/wip-ldm-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
|
|
6
6
|
"main": "src/boot/boot-hook.mjs",
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
"ldm": "bin/ldm.js",
|
|
9
9
|
"wip-ldm-os": "bin/ldm.js",
|
|
10
10
|
"ldm-scaffold": "bin/scaffold.sh",
|
|
11
|
-
"ldm-boot-install": "src/boot/install-cli.js"
|
|
11
|
+
"ldm-boot-install": "src/boot/install-cli.js",
|
|
12
|
+
"lesa": "dist/bridge/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build:bridge": "cd src/bridge && npm install && npx tsup core.ts mcp-server.ts cli.ts --format esm --dts --clean --outDir ../../dist/bridge",
|
|
16
|
+
"build": "npm run build:bridge"
|
|
12
17
|
},
|
|
13
18
|
"claudeCode": {
|
|
14
19
|
"hook": {
|
|
@@ -22,7 +27,9 @@
|
|
|
22
27
|
"src/",
|
|
23
28
|
"lib/",
|
|
24
29
|
"bin/",
|
|
30
|
+
"dist/bridge/",
|
|
25
31
|
"templates/",
|
|
32
|
+
"docs/",
|
|
26
33
|
"catalog.json",
|
|
27
34
|
"SKILL.md"
|
|
28
35
|
],
|
package/src/boot/boot-hook.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Follows guard.mjs pattern: stdin JSON in, stdout JSON out, exit 0 always.
|
|
6
6
|
|
|
7
7
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
8
|
-
import { join, dirname, resolve } from 'node:path';
|
|
8
|
+
import { join, dirname, resolve, basename } from 'node:path';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
@@ -212,6 +212,41 @@ async function main() {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// ── Register session (fire-and-forget) ──
|
|
216
|
+
try {
|
|
217
|
+
const { registerSession } = await import('../../lib/sessions.mjs');
|
|
218
|
+
const name = process.env.CLAUDE_SESSION_NAME || basename(input?.cwd || process.cwd()) || `session-${process.pid}`;
|
|
219
|
+
registerSession({
|
|
220
|
+
name,
|
|
221
|
+
agentId: config?.agentId || 'unknown',
|
|
222
|
+
pid: process.ppid || process.pid,
|
|
223
|
+
meta: { cwd: input?.cwd },
|
|
224
|
+
});
|
|
225
|
+
} catch {}
|
|
226
|
+
|
|
227
|
+
// ── Check pending messages ──
|
|
228
|
+
try {
|
|
229
|
+
const { readMessages } = await import('../../lib/messages.mjs');
|
|
230
|
+
const sessionName = process.env.CLAUDE_SESSION_NAME || basename(input?.cwd || process.cwd()) || 'unknown';
|
|
231
|
+
const pending = readMessages(sessionName, { markRead: false });
|
|
232
|
+
if (pending.length > 0) {
|
|
233
|
+
const msgLines = pending.map(m => ` [${m.type}] ${m.from}: ${m.body}`).join('\n');
|
|
234
|
+
sections.push(`== Pending Messages (${pending.length}) ==\n${msgLines}`);
|
|
235
|
+
}
|
|
236
|
+
} catch {}
|
|
237
|
+
|
|
238
|
+
// ── Check for updates ──
|
|
239
|
+
try {
|
|
240
|
+
const { readUpdateManifest } = await import('../../lib/updates.mjs');
|
|
241
|
+
const manifest = readUpdateManifest();
|
|
242
|
+
if (manifest?.updatesAvailable > 0) {
|
|
243
|
+
const updateLines = manifest.updates
|
|
244
|
+
.map(u => ` ${u.name}: ${u.currentVersion} -> ${u.latestVersion}`)
|
|
245
|
+
.join('\n');
|
|
246
|
+
sections.push(`== Updates Available (${manifest.updatesAvailable}) ==\n${updateLines}\nRun: ldm install`);
|
|
247
|
+
}
|
|
248
|
+
} catch {}
|
|
249
|
+
|
|
215
250
|
const elapsed = Date.now() - startTime;
|
|
216
251
|
const footer = `== Boot complete. Loaded ${loaded.length}/9 files in ${elapsed}ms. ==`;
|
|
217
252
|
if (skipped.length > 0) {
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lesa-bridge/cli.ts: CLI interface.
|
|
3
|
+
// lesa send "message", lesa inbox, lesa search "query", lesa read <file>
|
|
4
|
+
|
|
5
|
+
import { existsSync, statSync } from "node:fs";
|
|
6
|
+
import {
|
|
7
|
+
resolveConfig,
|
|
8
|
+
resolveGatewayConfig,
|
|
9
|
+
resolveApiKey,
|
|
10
|
+
sendMessage,
|
|
11
|
+
searchConversations,
|
|
12
|
+
searchWorkspace,
|
|
13
|
+
readWorkspaceFile,
|
|
14
|
+
discoverSkills,
|
|
15
|
+
} from "./core.js";
|
|
16
|
+
|
|
17
|
+
const config = resolveConfig();
|
|
18
|
+
|
|
19
|
+
function usage(): void {
|
|
20
|
+
console.log(`lesa-bridge: Claude Code CLI ↔ OpenClaw TUI agent bridge
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
lesa send <message> Send a message to the OpenClaw agent
|
|
24
|
+
lesa search <query> Semantic search over conversation history
|
|
25
|
+
lesa memory <query> Keyword search across workspace files
|
|
26
|
+
lesa read <path> Read a workspace file (relative to workspace/)
|
|
27
|
+
lesa status Show bridge configuration
|
|
28
|
+
lesa diagnose Check gateway, inbox, DB, skills health
|
|
29
|
+
lesa help Show this help
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
lesa send "What are you working on?"
|
|
33
|
+
lesa search "API key resolution"
|
|
34
|
+
lesa memory "compaction"
|
|
35
|
+
lesa read MEMORY.md
|
|
36
|
+
lesa read memory/2026-02-10.md`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main(): Promise<void> {
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
const command = args[0];
|
|
42
|
+
|
|
43
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
44
|
+
usage();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const arg = args.slice(1).join(" ");
|
|
49
|
+
|
|
50
|
+
switch (command) {
|
|
51
|
+
case "send": {
|
|
52
|
+
if (!arg) {
|
|
53
|
+
console.error("Error: message required. Usage: lesa send <message>");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const reply = await sendMessage(config.openclawDir, arg);
|
|
58
|
+
console.log(reply);
|
|
59
|
+
} catch (err: any) {
|
|
60
|
+
console.error(`Error: ${err.message}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case "search": {
|
|
67
|
+
if (!arg) {
|
|
68
|
+
console.error("Error: query required. Usage: lesa search <query>");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const results = await searchConversations(config, arg);
|
|
73
|
+
if (results.length === 0) {
|
|
74
|
+
console.log("No results found.");
|
|
75
|
+
} else {
|
|
76
|
+
const icon: Record<string, string> = { fresh: "🟢", recent: "🟡", aging: "🟠", stale: "🔴" };
|
|
77
|
+
for (const [i, r] of results.entries()) {
|
|
78
|
+
const sim = r.similarity !== undefined ? ` (${(r.similarity * 100).toFixed(1)}%)` : "";
|
|
79
|
+
const fresh = r.freshness ? ` ${icon[r.freshness]} ${r.freshness}` : "";
|
|
80
|
+
console.log(`[${i + 1}]${sim}${fresh} ${r.sessionKey} ${r.date}`);
|
|
81
|
+
console.log(r.text);
|
|
82
|
+
if (i < results.length - 1) console.log("\n---\n");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err: any) {
|
|
86
|
+
console.error(`Error: ${err.message}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case "memory": {
|
|
93
|
+
if (!arg) {
|
|
94
|
+
console.error("Error: query required. Usage: lesa memory <query>");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const results = searchWorkspace(config.workspaceDir, arg);
|
|
99
|
+
if (results.length === 0) {
|
|
100
|
+
console.log(`No workspace files matched "${arg}".`);
|
|
101
|
+
} else {
|
|
102
|
+
for (const r of results) {
|
|
103
|
+
console.log(`### ${r.path}`);
|
|
104
|
+
for (const excerpt of r.excerpts) {
|
|
105
|
+
console.log(` ${excerpt.replace(/\n/g, "\n ")}`);
|
|
106
|
+
}
|
|
107
|
+
console.log();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
console.error(`Error: ${err.message}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
case "read": {
|
|
118
|
+
if (!arg) {
|
|
119
|
+
console.error("Error: path required. Usage: lesa read <path>");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const result = readWorkspaceFile(config.workspaceDir, arg);
|
|
124
|
+
console.log(result.content);
|
|
125
|
+
} catch (err: any) {
|
|
126
|
+
console.error(err.message);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
case "status": {
|
|
133
|
+
console.log(`lesa-bridge status`);
|
|
134
|
+
console.log(` OpenClaw dir: ${config.openclawDir}`);
|
|
135
|
+
console.log(` Workspace: ${config.workspaceDir}`);
|
|
136
|
+
console.log(` Database: ${config.dbPath}`);
|
|
137
|
+
console.log(` Inbox port: ${config.inboxPort}`);
|
|
138
|
+
console.log(` Embedding: ${config.embeddingModel} (${config.embeddingDimensions}d)`);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case "diagnose": {
|
|
143
|
+
console.log("lesa-bridge diagnose\n");
|
|
144
|
+
let issues = 0;
|
|
145
|
+
|
|
146
|
+
// 1. OpenClaw dir
|
|
147
|
+
if (existsSync(config.openclawDir)) {
|
|
148
|
+
console.log(` ✓ OpenClaw dir exists: ${config.openclawDir}`);
|
|
149
|
+
} else {
|
|
150
|
+
console.log(` ✗ OpenClaw dir missing: ${config.openclawDir}`);
|
|
151
|
+
issues++;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 2. Gateway config + connectivity
|
|
155
|
+
try {
|
|
156
|
+
const gw = resolveGatewayConfig(config.openclawDir);
|
|
157
|
+
console.log(` ✓ Gateway config found (port ${gw.port}, token present)`);
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const resp = await fetch(`http://127.0.0.1:${gw.port}/health`, { signal: AbortSignal.timeout(3000) });
|
|
161
|
+
if (resp.ok) {
|
|
162
|
+
console.log(` ✓ Gateway responding on port ${gw.port}`);
|
|
163
|
+
} else {
|
|
164
|
+
console.log(` ✗ Gateway returned ${resp.status}`);
|
|
165
|
+
issues++;
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
console.log(` ✗ Gateway not reachable on port ${gw.port}`);
|
|
169
|
+
issues++;
|
|
170
|
+
}
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
console.log(` ✗ Gateway config: ${err.message}`);
|
|
173
|
+
issues++;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 3. Inbox endpoint
|
|
177
|
+
try {
|
|
178
|
+
const resp = await fetch(`http://127.0.0.1:${config.inboxPort}/status`, { signal: AbortSignal.timeout(3000) });
|
|
179
|
+
const data = await resp.json() as { ok: boolean; pending: number };
|
|
180
|
+
if (data.ok) {
|
|
181
|
+
console.log(` ✓ Inbox endpoint responding (${data.pending} pending)`);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(` ✗ Inbox endpoint returned unexpected response`);
|
|
184
|
+
issues++;
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
console.log(` - Inbox not running (normal if MCP server isn't started)`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 4. Embeddings DB
|
|
191
|
+
if (existsSync(config.dbPath)) {
|
|
192
|
+
const stats = statSync(config.dbPath);
|
|
193
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
|
|
194
|
+
console.log(` ✓ Embeddings DB exists (${sizeMB} MB)`);
|
|
195
|
+
} else {
|
|
196
|
+
console.log(` ✗ Embeddings DB missing: ${config.dbPath}`);
|
|
197
|
+
issues++;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 5. API key
|
|
201
|
+
const apiKey = resolveApiKey(config.openclawDir);
|
|
202
|
+
if (apiKey) {
|
|
203
|
+
console.log(` ✓ OpenAI API key found (semantic search enabled)`);
|
|
204
|
+
} else {
|
|
205
|
+
console.log(` - No OpenAI API key (text search fallback)`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 6. Skills
|
|
209
|
+
try {
|
|
210
|
+
const skills = discoverSkills(config.openclawDir);
|
|
211
|
+
const executable = skills.filter(s => s.hasScripts).length;
|
|
212
|
+
console.log(` ✓ Skills discovered: ${skills.length} total, ${executable} executable`);
|
|
213
|
+
} catch (err: any) {
|
|
214
|
+
console.log(` ✗ Skill discovery failed: ${err.message}`);
|
|
215
|
+
issues++;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 7. Workspace
|
|
219
|
+
if (existsSync(config.workspaceDir)) {
|
|
220
|
+
console.log(` ✓ Workspace dir exists`);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(` ✗ Workspace dir missing: ${config.workspaceDir}`);
|
|
223
|
+
issues++;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log();
|
|
227
|
+
if (issues === 0) {
|
|
228
|
+
console.log(" All checks passed.");
|
|
229
|
+
} else {
|
|
230
|
+
console.log(` ${issues} issue(s) found.`);
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
default:
|
|
236
|
+
console.error(`Unknown command: ${command}`);
|
|
237
|
+
usage();
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
main().catch((err) => {
|
|
243
|
+
console.error(`Fatal: ${err.message}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
});
|