@wipcomputer/wip-ldm-os 0.4.82-alpha.1 → 0.4.84

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.
@@ -18,23 +18,24 @@ Every tool is a sensor, an actuator, or both. Every tool should be accessible th
18
18
  - Guard a file from edits (wip-file-guard)
19
19
  - Generate a video (wip-grok generate_video)
20
20
 
21
- ## The Seven Interfaces
21
+ ## The Eight Interfaces
22
22
 
23
- Agents don't all speak the same language. Some run shell commands. Some import modules. Some talk MCP. Some read markdown instructions.
23
+ Agents don't all speak the same language. Some run shell commands. Some import modules. Some talk MCP over stdio. Some talk MCP over HTTPS. Some read markdown instructions.
24
24
 
25
25
  So every tool should expose multiple interfaces into the same core logic:
26
26
 
27
- | Interface | What | Who uses it |
28
- |-----------|------|-------------|
29
- | **CLI** | Shell command | Humans, any agent with bash |
30
- | **Module** | ES import | Other tools, scripts |
31
- | **MCP Server** | JSON-RPC over stdio | Claude Code, Cursor, any MCP client |
32
- | **OpenClaw Plugin** | Lifecycle hooks + tools | OpenClaw agents |
33
- | **Skill** | Markdown instructions (SKILL.md) | Any agent that reads files |
34
- | **Claude Code Hook** | PreToolUse/Stop events | Claude Code |
35
- | **Claude Code Plugin** | Distributable package (skills, agents, hooks, MCP, LSP) | Claude Code marketplace |
27
+ | # | Interface | What | Who uses it |
28
+ |---|-----------|------|-------------|
29
+ | 1 | **CLI** | Shell command | Humans, any agent with bash |
30
+ | 2 | **Module** | ES import | Other tools, scripts |
31
+ | 3 | **MCP Server (local stdio)** | JSON-RPC over stdio | Claude Code, Cursor, OpenClaw |
32
+ | 4 | **Remote MCP** | JSON-RPC over HTTPS (SSE / streamable HTTP) | Claude Desktop, web, mobile |
33
+ | 5 | **OpenClaw Plugin** | Lifecycle hooks + tools | OpenClaw agents |
34
+ | 6 | **Skill** | Markdown instructions (SKILL.md) | Any agent that reads files |
35
+ | 7 | **Claude Code Hook** | PreToolUse/Stop events | Claude Code |
36
+ | 8 | **Claude Code Plugin** | Distributable package (skills, agents, hooks, MCP, LSP) | Claude Code marketplace |
36
37
 
37
- Not every tool needs all seven. Build what makes sense. But the more interfaces you expose, the more agents can use your tool.
38
+ Not every tool needs all eight. Build what makes sense. But the more interfaces you expose, the more agents can use your tool. Local and Remote MCP are sibling transports of the same protocol; they sit next to each other so the relationship is obvious.
38
39
 
39
40
  ### 1. CLI
40
41
 
@@ -75,15 +76,15 @@ An importable ES module. The programmatic interface. Other tools compose with it
75
76
  }
76
77
  ```
77
78
 
78
- ### 3. MCP Server
79
+ ### 3. MCP Server (local stdio)
79
80
 
80
- A JSON-RPC server implementing the Model Context Protocol. Any MCP-compatible agent can use it.
81
+ A JSON-RPC server implementing the Model Context Protocol over stdio. Spawned as a child process by the agent. For the HTTP/SSE sibling, see [#4 Remote MCP](#4-remote-mcp).
81
82
 
82
83
  **Convention:** `mcp-server.mjs` (or `.js`, `.ts`) at the repo root. Uses `@modelcontextprotocol/sdk`.
83
84
 
84
85
  **Detection:** One of `mcp-server.mjs`, `mcp-server.js`, `mcp-server.ts`, `dist/mcp-server.js` exists.
85
86
 
86
- **Install:** Add to `.mcp.json`:
87
+ **Install:** Add to `.mcp.json` with `command` + `args`:
87
88
 
88
89
  ```json
89
90
  {
@@ -94,7 +95,46 @@ A JSON-RPC server implementing the Model Context Protocol. Any MCP-compatible ag
94
95
  }
95
96
  ```
96
97
 
97
- ### 4. OpenClaw Plugin
98
+ ### 4. Remote MCP
99
+
100
+ The HTTP/SSE (or streamable HTTP) sibling of #3. Hosted at an HTTPS endpoint, not spawned locally. The transport that lights up Claude Desktop connectors, web, and mobile clients.
101
+
102
+ **Contract:** Remote MCP endpoint is **declared by package/catalog metadata** and **registered by `ldm install`**. No filesystem-sniffing fallback.
103
+
104
+ **Convention:** `mcp.remote` field in `package.json`:
105
+
106
+ ```json
107
+ {
108
+ "mcp": {
109
+ "remote": {
110
+ "url": "https://example.com/mcp",
111
+ "transport": "streamable-http",
112
+ "auth": "oauth"
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ `url` may be a placeholder (`"https://__DEPLOYED_URL__"`) when the repo ships the server code and the catalog supplies the URL at install time.
119
+
120
+ **Detection:** `package.json.mcp.remote.url` is a string.
121
+
122
+ **Install:** Add to `.mcp.json` as a remote entry, plus print a one-line Claude Desktop hint:
123
+
124
+ ```json
125
+ {
126
+ "tool-name": {
127
+ "url": "https://example.com/mcp",
128
+ "transport": "streamable-http"
129
+ }
130
+ }
131
+ ```
132
+
133
+ **Auth:** `none` writes the URL as-is. `shared-secret` prompts the user once and stores out-of-band. `oauth` is gated behind a follow-up ticket; first cut prints a TODO.
134
+
135
+ **Implementation status:** detection + install action are tracked in `ai/product/bugs/installer/` (see [Remote MCP detection](../../ai/product/bugs/installer/2026-04-28--cc-mini--installer-remote-mcp-detection.md) and [Remote MCP install](../../ai/product/bugs/installer/2026-04-28--cc-mini--installer-remote-mcp-install.md)). The spec is canonical now; the detector and installer catch up next.
136
+
137
+ ### 5. OpenClaw Plugin
98
138
 
99
139
  A plugin for OpenClaw agents. Lifecycle hooks, tool registration, settings.
100
140
 
@@ -104,12 +144,14 @@ A plugin for OpenClaw agents. Lifecycle hooks, tool registration, settings.
104
144
 
105
145
  **Install:** Copy to `~/.openclaw/extensions/<name>/`, run `npm install --omit=dev`.
106
146
 
107
- ### 5. Skill (SKILL.md)
147
+ ### 6. Skill (SKILL.md)
108
148
 
109
149
  A markdown file that teaches agents when and how to use the tool. The instruction interface. Follows the [Agent Skills Spec](https://agentskills.io/specification).
110
150
 
111
151
  **Convention:** `SKILL.md` at the repo root. Optional `references/` directory for context files.
112
152
 
153
+ **Platform variants:** Codex CLI reads `AGENTS.md` with the same role and content shape. Treat as the Codex-flavored filename for this same interface, not a separate one.
154
+
113
155
  **Detection:** `SKILL.md` exists.
114
156
 
115
157
  **Install:** `ldm install` deploys `SKILL.md` to `~/.openclaw/skills/<name>/`. If `references/` exists, it is deployed alongside and also to `settings/docs/skills/<name>/` in the workspace (so all agents can read them).
@@ -132,7 +174,7 @@ metadata:
132
174
  ---
133
175
  ```
134
176
 
135
- ### 6. Claude Code Hook
177
+ ### 7. Claude Code Hook
136
178
 
137
179
  A hook that runs during Claude Code's tool lifecycle (PreToolUse, Stop, etc.).
138
180
 
@@ -157,7 +199,7 @@ A hook that runs during Claude Code's tool lifecycle (PreToolUse, Stop, etc.).
157
199
  }
158
200
  ```
159
201
 
160
- ### 7. Claude Code Plugin
202
+ ### 8. Claude Code Plugin
161
203
 
162
204
  A distributable plugin for Claude Code. Bundles skills, agents, hooks, MCP servers, and LSP servers into one installable package. Shareable via marketplaces.
163
205
 
@@ -188,6 +230,14 @@ your-plugin/
188
230
  }
189
231
  ```
190
232
 
233
+ ### Out of scope by design
234
+
235
+ Disposable, agent-generated artifacts (custom dashboards, ephemeral scripts, one-off automations) are out of scope for this spec. They are products of an agent, not Universal Interface products. The eight interfaces describe what the agent has to *compose with*. Worked example: [SPEC.md ... Worked example (compact sketch)](SPEC.md#worked-example-compact-sketch).
236
+
237
+ ### Future considerations
238
+
239
+ *LSP as a standalone interface (#9).* LSP servers are currently surfaced via Claude Code Plugin bundles (#8) ... `.lsp.json` is part of the plugin shape. If a product ships a standalone LSP server outside a CC Plugin, we will add it as a numbered interface. Not added today.
240
+
191
241
  ## How to Build It
192
242
 
193
243
  The architecture is simple. Four files:
@@ -206,30 +256,33 @@ This means one codebase, one set of tests, multiple interfaces.
206
256
 
207
257
  ## Install Prompt Template
208
258
 
209
- Every product gets an install prompt. Paste it into any AI. The AI reads the spec, explains it, checks what's installed, and walks you through a dry run.
259
+ Every product gets an install prompt. Paste it into any AI. The AI reads the install spec, explains the product, checks what's installed, and walks you through a dry run before touching anything.
260
+
261
+ The install spec URL convention, behavior contract, track flags, and `agent.txt` distinction are defined in [SPEC.md ... Install Spec](SPEC.md#install-spec). This is the canonical paste-into-an-AI form:
210
262
 
211
263
  ```
212
- Read wip.computer/install/{URL}
264
+ Read https://wip.computer/install/<slug>.txt
213
265
 
214
- Then explain:
215
- 1. What is {name of product}?
266
+ Check if <product name> is already installed. If it is, run
267
+ `ldm install --dry-run <slug>` and show me what I have and what's new.
268
+
269
+ If not, walk me through setup and explain:
270
+ 1. What is <product name>?
216
271
  2. What does it install on my system?
217
272
  3. What changes for us? (this AI)
218
273
  4. What changes across all my AIs?
219
274
 
220
- Check if {name of product} is already installed.
221
-
222
- If it is, show me what I have and what's new.
223
-
224
275
  Then ask:
225
276
  - Do you have questions?
226
277
  - Want to see a dry run?
227
278
 
228
- If I say yes, run: {product-init} init --dry-run
279
+ If I say yes, run: ldm install --dry-run <slug>
229
280
 
230
281
  Show me exactly what will change. Don't install anything until I say "install".
231
282
  ```
232
283
 
284
+ Tracks: append `--alpha` or `--beta` to install a prerelease (e.g. `ldm install --beta <slug>`). The same install spec URL covers all tracks.
285
+
233
286
  ## The `ai/` Folder
234
287
 
235
288
  Every repo should have an `ai/` folder. This is where agents and humans collaborate on the project ... plans, todos, dev updates, research notes, conversations.
@@ -308,7 +361,8 @@ ldm install # update all
308
361
  |---------|-----------|---------------|
309
362
  | `package.json` with `bin` | CLI | `npm install -g` |
310
363
  | `main` or `exports` in `package.json` | Module | Reports import path |
311
- | `mcp-server.mjs` | MCP | Prints `.mcp.json` config |
364
+ | `mcp-server.mjs` | MCP (local stdio) | Adds `command` + `args` entry to `.mcp.json` |
365
+ | `mcp.remote.url` in `package.json` | Remote MCP | Adds `url` + `transport` entry to `.mcp.json`; prints Claude Desktop hint. **Implementation in flight ([ticket](../../ai/product/bugs/installer/2026-04-28--cc-mini--installer-remote-mcp-detection.md)).** |
312
366
  | `openclaw.plugin.json` | OpenClaw | Copies to `~/.openclaw/extensions/` |
313
367
  | `SKILL.md` | Skill | Reports path |
314
368
  | `guard.mjs` or `claudeCode.hook` | CC Hook | Adds to `~/.claude/settings.json` |
@@ -323,6 +377,7 @@ ldm install # update all
323
377
  | [wip-file-guard](https://github.com/wipcomputer/wip-ai-devops-toolbox/tree/main/tools/wip-file-guard) | Actuator | CLI + OpenClaw + CC Hook | Protect files from AI edits |
324
378
  | [wip-healthcheck](https://github.com/wipcomputer/wip-healthcheck) | Sensor | CLI + Module | System health monitoring |
325
379
  | [wip-markdown-viewer](https://github.com/wipcomputer/wip-markdown-viewer) | Actuator | CLI + Module | Live markdown viewer |
380
+ | [wip-codex-remote-control](https://wip.computer/install/wip-codex-remote-control.txt) | Sensor + Actuator | CLI + Module + MCP + Skill + Install Spec | Pair phone with Codex; AI-led install via published install spec. Worked example of the install-spec URL pattern. |
326
381
 
327
382
  ## Supported Tools
328
383
 
@@ -0,0 +1,257 @@
1
+ // lib/bin-manifest.mjs — bin ownership manifest aggregator + heal helpers
2
+ //
3
+ // Implements the design at
4
+ // ai/product/plans-prds/current/2026-04-28--cc-mini--ldm-bin-ownership-manifest-design.md
5
+ //
6
+ // Two declarers contribute entries:
7
+ // - LDM CLI: package.json `wipLdmOs.binFiles`
8
+ // - Extensions: ~/.ldm/extensions/<name>/openclaw.plugin.json `binFiles`
9
+ //
10
+ // Aggregation produces { entries, conflicts }. Conflicts are hard
11
+ // failures: callers MUST check `conflicts.length === 0` before any
12
+ // write. heal() never runs if conflicts exist.
13
+
14
+ import { existsSync, readFileSync, statSync, mkdirSync, copyFileSync, chmodSync } from 'node:fs';
15
+ import { join, dirname, basename } from 'node:path';
16
+
17
+ /**
18
+ * @typedef {Object} BinDeclaration
19
+ * @property {string} name - basename written to <binDir>/<name>
20
+ * @property {string} source - relative to declarer's installed root
21
+ * @property {boolean} [executable] - default true; chmod 0755 after copy
22
+ * @property {string} [purpose] - free-form, surfaces in verbose doctor
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} BinEntry
27
+ * @property {string} name
28
+ * @property {string} destPath - resolved absolute path under binDir
29
+ * @property {string} sourcePath - resolved absolute path
30
+ * @property {boolean} executable
31
+ * @property {string} declarer - 'wip-ldm-os' or extension name
32
+ * @property {string} [purpose]
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} Conflict
37
+ * @property {string} name
38
+ * @property {{declarer: string, sourcePath: string}[]} declarers
39
+ */
40
+
41
+ /**
42
+ * Validate a single declaration shape. Returns array of error strings (empty = ok).
43
+ * @param {BinDeclaration} decl
44
+ * @returns {string[]}
45
+ */
46
+ export function validateDeclaration(decl) {
47
+ const errors = [];
48
+ if (!decl || typeof decl !== 'object') {
49
+ errors.push('declaration must be an object');
50
+ return errors;
51
+ }
52
+ if (typeof decl.name !== 'string' || !decl.name) {
53
+ errors.push('"name" must be a non-empty string');
54
+ } else if (decl.name !== basename(decl.name) || decl.name.includes('/') || decl.name.includes('\\')) {
55
+ errors.push(`"name" must be a basename, got "${decl.name}"`);
56
+ } else if (decl.name.includes('..')) {
57
+ errors.push(`"name" must not contain "..", got "${decl.name}"`);
58
+ }
59
+ if (typeof decl.source !== 'string' || !decl.source) {
60
+ errors.push('"source" must be a non-empty string');
61
+ } else if (decl.source.includes('..')) {
62
+ errors.push(`"source" must not contain "..", got "${decl.source}"`);
63
+ }
64
+ if (decl.executable !== undefined && typeof decl.executable !== 'boolean') {
65
+ errors.push('"executable" must be a boolean if provided');
66
+ }
67
+ return errors;
68
+ }
69
+
70
+ /**
71
+ * Validate all declarations from one declarer (e.g. the LDM CLI's own
72
+ * `wipLdmOs.binFiles`, or one extension's `binFiles`). Used by both
73
+ * runtime aggregation and prepublish CI gate.
74
+ *
75
+ * Checks: shape per entry, no internal duplicate `name`, `source` exists
76
+ * on disk under `packageRoot`.
77
+ *
78
+ * @param {string} declarer
79
+ * @param {string} packageRoot - the absolute path to resolve `source` against
80
+ * @param {BinDeclaration[]} decls
81
+ * @returns {{valid: boolean, errors: string[]}}
82
+ */
83
+ export function validateDeclarations(declarer, packageRoot, decls) {
84
+ const errors = [];
85
+ if (!Array.isArray(decls)) {
86
+ return { valid: false, errors: [`${declarer}: binFiles must be an array`] };
87
+ }
88
+ const seen = new Set();
89
+ for (let i = 0; i < decls.length; i++) {
90
+ const d = decls[i];
91
+ const ctx = `${declarer}[${i}]${d?.name ? ` ${d.name}` : ''}`;
92
+ for (const e of validateDeclaration(d)) errors.push(`${ctx}: ${e}`);
93
+ if (d && typeof d.name === 'string') {
94
+ if (seen.has(d.name)) errors.push(`${declarer}: duplicate name within declarer: ${d.name}`);
95
+ seen.add(d.name);
96
+ }
97
+ if (d && typeof d.source === 'string') {
98
+ const src = join(packageRoot, d.source);
99
+ if (!existsSync(src)) errors.push(`${ctx}: source not found at ${src}`);
100
+ }
101
+ }
102
+ return { valid: errors.length === 0, errors };
103
+ }
104
+
105
+ /**
106
+ * Read LDM CLI's own bin declarations from its package.json.
107
+ * @param {string} ldmCliRoot
108
+ * @returns {BinDeclaration[]}
109
+ */
110
+ function readLdmCliDeclarations(ldmCliRoot) {
111
+ const pkgPath = join(ldmCliRoot, 'package.json');
112
+ if (!existsSync(pkgPath)) return [];
113
+ try {
114
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
115
+ return Array.isArray(pkg?.wipLdmOs?.binFiles) ? pkg.wipLdmOs.binFiles : [];
116
+ } catch {
117
+ return [];
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Read one extension's bin declarations from its openclaw.plugin.json.
123
+ * @param {string} extDir - ~/.ldm/extensions/<name>
124
+ * @returns {BinDeclaration[]}
125
+ */
126
+ function readExtensionDeclarations(extDir) {
127
+ const manifestPath = join(extDir, 'openclaw.plugin.json');
128
+ if (!existsSync(manifestPath)) return [];
129
+ try {
130
+ const m = JSON.parse(readFileSync(manifestPath, 'utf-8'));
131
+ return Array.isArray(m?.binFiles) ? m.binFiles : [];
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Aggregate all bin entries across LDM CLI + registered extensions.
139
+ * Returns conflicts for any name claimed by 2+ declarers.
140
+ *
141
+ * IMPORTANT: callers MUST check `conflicts.length === 0` before doing any
142
+ * writes. Conflict means we cannot safely decide who owns the file.
143
+ *
144
+ * @param {Object} opts
145
+ * @param {string} opts.ldmCliRoot - absolute path to LDM CLI package root
146
+ * @param {string} opts.extensionsRoot - absolute path, typically ~/.ldm/extensions
147
+ * @param {string} opts.binDir - absolute path, typically ~/.ldm/bin
148
+ * @param {Object} [opts.registry] - optional ~/.ldm/extensions/registry.json contents
149
+ * @returns {{entries: BinEntry[], conflicts: Conflict[]}}
150
+ */
151
+ export function aggregateBinManifest({ ldmCliRoot, extensionsRoot, binDir, registry } = {}) {
152
+ /** @type {BinEntry[]} */
153
+ const entries = [];
154
+ /** @type {Map<string, {declarer: string, sourcePath: string}[]>} */
155
+ const claims = new Map();
156
+
157
+ function record(declarer, declarerRoot, decls) {
158
+ for (const d of decls) {
159
+ if (!d || typeof d.name !== 'string' || typeof d.source !== 'string') continue;
160
+ const sourcePath = join(declarerRoot, d.source);
161
+ const list = claims.get(d.name) || [];
162
+ list.push({ declarer, sourcePath });
163
+ claims.set(d.name, list);
164
+ entries.push({
165
+ name: d.name,
166
+ destPath: join(binDir, d.name),
167
+ sourcePath,
168
+ executable: d.executable !== false,
169
+ declarer,
170
+ purpose: d.purpose,
171
+ });
172
+ }
173
+ }
174
+
175
+ // 1. LDM CLI
176
+ record('wip-ldm-os', ldmCliRoot, readLdmCliDeclarations(ldmCliRoot));
177
+
178
+ // 2. Registered extensions
179
+ const extNames = registry?.extensions ? Object.keys(registry.extensions) : [];
180
+ for (const name of extNames) {
181
+ const extDir = join(extensionsRoot, name);
182
+ record(name, extDir, readExtensionDeclarations(extDir));
183
+ }
184
+
185
+ /** @type {Conflict[]} */
186
+ const conflicts = [];
187
+ for (const [name, declarers] of claims.entries()) {
188
+ if (declarers.length > 1) conflicts.push({ name, declarers });
189
+ }
190
+
191
+ return { entries, conflicts };
192
+ }
193
+
194
+ /**
195
+ * Verify and (optionally) heal each entry. heal=false is read-only and
196
+ * just classifies. heal=true restores missing/unexecutable files from
197
+ * `sourcePath`. Returns a per-entry result so callers can build their
198
+ * own output.
199
+ *
200
+ * NEVER call this if aggregateBinManifest reported conflicts. The caller
201
+ * must abort instead.
202
+ *
203
+ * @param {BinEntry[]} entries
204
+ * @param {Object} [opts]
205
+ * @param {boolean} [opts.heal] - default false
206
+ * @returns {{
207
+ * ok: BinEntry[],
208
+ * missing: BinEntry[],
209
+ * notExecutable: BinEntry[],
210
+ * sourceMissing: BinEntry[],
211
+ * healed: BinEntry[],
212
+ * failed: {entry: BinEntry, reason: string}[]
213
+ * }}
214
+ */
215
+ export function healBinManifest(entries, opts = {}) {
216
+ const heal = opts.heal === true;
217
+ const ok = [];
218
+ const missing = [];
219
+ const notExecutable = [];
220
+ const sourceMissing = [];
221
+ const healed = [];
222
+ const failed = [];
223
+
224
+ for (const e of entries) {
225
+ const destExists = existsSync(e.destPath);
226
+ const destExecutable = destExists && (statSync(e.destPath).mode & 0o111) !== 0;
227
+ const expectedExec = e.executable !== false;
228
+ const destOk = destExists && (!expectedExec || destExecutable);
229
+
230
+ if (destOk) {
231
+ ok.push(e);
232
+ continue;
233
+ }
234
+
235
+ if (!destExists) missing.push(e);
236
+ else if (expectedExec && !destExecutable) notExecutable.push(e);
237
+
238
+ if (!heal) continue;
239
+
240
+ if (!existsSync(e.sourcePath)) {
241
+ sourceMissing.push(e);
242
+ failed.push({ entry: e, reason: `source missing at ${e.sourcePath}` });
243
+ continue;
244
+ }
245
+
246
+ try {
247
+ mkdirSync(dirname(e.destPath), { recursive: true });
248
+ copyFileSync(e.sourcePath, e.destPath);
249
+ if (expectedExec) chmodSync(e.destPath, 0o755);
250
+ healed.push(e);
251
+ } catch (err) {
252
+ failed.push({ entry: e, reason: err.message });
253
+ }
254
+ }
255
+
256
+ return { ok, missing, notExecutable, sourceMissing, healed, failed };
257
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.82-alpha.1",
3
+ "version": "0.4.84",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -18,9 +18,13 @@
18
18
  "scripts": {
19
19
  "build:bridge": "cd src/bridge && npm install && npx tsup core.ts mcp-server.ts cli.ts openclaw.ts --format esm --dts --clean --outDir ../../dist/bridge",
20
20
  "build": "npm run build:bridge",
21
- "prepublishOnly": "npm run build:bridge",
21
+ "prepublishOnly": "npm run build:bridge && npm run validate:bin-manifest",
22
+ "validate:bin-manifest": "node scripts/validate-bin-manifest.mjs",
22
23
  "test:skill-frontmatter": "node scripts/test-skill-frontmatter.mjs",
23
24
  "test:installer-update-tracks": "node scripts/test-installer-update-tracks.mjs",
25
+ "test:ldm-install-bin-shim": "node scripts/test-ldm-install-preserves-foreign-bin.mjs",
26
+ "test:doctor-cron-target": "node scripts/test-doctor-cron-target.mjs",
27
+ "test:bin-manifest": "node scripts/test-bin-manifest.mjs",
24
28
  "fmt": "npx prettier --write 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'",
25
29
  "fmt:check": "npx prettier --check 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'"
26
30
  },
@@ -32,6 +36,35 @@
32
36
  "timeout": 15
33
37
  }
34
38
  },
39
+ "wipLdmOs": {
40
+ "binFiles": [
41
+ {
42
+ "name": "process-monitor.sh",
43
+ "source": "bin/process-monitor.sh",
44
+ "purpose": "kills zombie processes; cron every 3 min"
45
+ },
46
+ {
47
+ "name": "ldm-backup.sh",
48
+ "source": "scripts/ldm-backup.sh",
49
+ "purpose": "daily backup; LaunchAgent at 03:00"
50
+ },
51
+ {
52
+ "name": "ldm-restore.sh",
53
+ "source": "scripts/ldm-restore.sh",
54
+ "purpose": "restore from backup; operator-invoked"
55
+ },
56
+ {
57
+ "name": "ldm-summary.sh",
58
+ "source": "scripts/ldm-summary.sh",
59
+ "purpose": "generate session/daily summaries"
60
+ },
61
+ {
62
+ "name": "backfill-summaries.sh",
63
+ "source": "scripts/backfill-summaries.sh",
64
+ "purpose": "backfill summary cadences"
65
+ }
66
+ ]
67
+ },
35
68
  "files": [
36
69
  "src/",
37
70
  "lib/",