@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.
package/README.md CHANGED
@@ -96,6 +96,8 @@ The OS connects your AIs. Add-ons are what they actually use. Each one is a full
96
96
 
97
97
  **OpenClaw**
98
98
  - Open-source agent runtime. Run AI agents 24/7 with identity, memory, and tool access. The existence proof for LDM OS.
99
+ - WIP contributions accepted upstream: `before_message_write` plugin hook (#18197), Codex app-server final chat events (#70815 -> maintainer PR #71293), memory-core seed cache streaming/yield (#73067 -> maintainer PR #73118), and fallback vector top-K streaming (#73069 -> maintainer PR #73100).
100
+ - Submitted / superseded: symlink plugin discovery fix (#45744; bug confirmed, superseded by #69971, not landed as submitted).
99
101
  - [Read more about OpenClaw](https://github.com/openclaw/openclaw)
100
102
 
101
103
  [See all skills](docs/skills/README.md)
package/SKILL.md CHANGED
@@ -9,7 +9,7 @@ license: MIT
9
9
  compatibility: Requires git, npm, node. Node.js 18+.
10
10
  metadata:
11
11
  display-name: "LDM OS"
12
- version: "0.4.81"
12
+ version: "0.4.84"
13
13
  homepage: "https://github.com/wipcomputer/wip-ldm-os"
14
14
  author: "Parker Todd Brooks"
15
15
  category: infrastructure
package/bin/ldm.js CHANGED
@@ -1935,6 +1935,36 @@ async function cmdInstallCatalog() {
1935
1935
  console.log(` + Migrated ${migrated} registry entries to v2 format (source info added)`);
1936
1936
  }
1937
1937
 
1938
+ // Aggregate the bin ownership manifest BEFORE seedLocalCatalog,
1939
+ // deployBridge, deployScripts, and the heal walk run. If two
1940
+ // declarers claim the same file in ~/.ldm/bin/ we cannot safely
1941
+ // decide ownership, so install must abort before catalog.json,
1942
+ // bridge files, or any ~/.ldm/bin/ shim is written. See
1943
+ // ai/product/plans-prds/current/2026-04-28--cc-mini--ldm-bin-ownership-manifest-design.md
1944
+ const { aggregateBinManifest, healBinManifest } = await import('../lib/bin-manifest.mjs');
1945
+ const ldmCliRoot = join(__dirname, '..');
1946
+ const binManifestRegistry = readJSON(REGISTRY_PATH) || { extensions: {} };
1947
+ const manifest = aggregateBinManifest({
1948
+ ldmCliRoot,
1949
+ extensionsRoot: LDM_EXTENSIONS,
1950
+ binDir: join(LDM_ROOT, 'bin'),
1951
+ registry: binManifestRegistry,
1952
+ });
1953
+ if (manifest.conflicts.length > 0) {
1954
+ console.log('');
1955
+ console.log(' x bin manifest conflict ... aborting before seedLocalCatalog/deployBridge/deployScripts run:');
1956
+ for (const c of manifest.conflicts) {
1957
+ console.log(` "${c.name}" claimed by ${c.declarers.length} declarers:`);
1958
+ for (const d of c.declarers) {
1959
+ console.log(` ${d.declarer}: ${d.sourcePath.replace(HOME, '~')}`);
1960
+ }
1961
+ }
1962
+ console.log('');
1963
+ console.log(' Resolve ownership in the package manifests, then re-run.');
1964
+ installLog(`ldm install aborted: ${manifest.conflicts.length} bin manifest conflict(s)`);
1965
+ process.exit(1);
1966
+ }
1967
+
1938
1968
  // Seed local catalog if missing (#262)
1939
1969
  if (seedLocalCatalog()) {
1940
1970
  console.log(` + catalog.json seeded to ~/.ldm/catalog.json`);
@@ -1950,6 +1980,24 @@ async function cmdInstallCatalog() {
1950
1980
  deployDocs();
1951
1981
  deployRules();
1952
1982
 
1983
+ // Manifest-driven self-heal. deployScripts() above wrote LDM CLI's own
1984
+ // *.sh files; this pass covers the rest of the manifest (LDM CLI files
1985
+ // deployed elsewhere, e.g. process-monitor.sh, plus extension-owned
1986
+ // shims like crystal-capture.sh). Read-only entries that already match
1987
+ // the manifest are no-ops.
1988
+ if (manifest.entries.length > 0) {
1989
+ const heal = healBinManifest(manifest.entries, { heal: !DRY_RUN });
1990
+ for (const e of heal.healed) {
1991
+ console.log(` + Restored ${e.name} from ${e.sourcePath.replace(HOME, '~')} (declarer: ${e.declarer})`);
1992
+ }
1993
+ for (const f of heal.failed) {
1994
+ console.log(` ! ${f.entry.name}: ${f.reason} (declarer: ${f.entry.declarer})`);
1995
+ }
1996
+ if (heal.ok.length > 0 && heal.healed.length === 0 && heal.failed.length === 0) {
1997
+ console.log(` + bin manifest: ${heal.ok.length} entr${heal.ok.length === 1 ? 'y' : 'ies'} verified`);
1998
+ }
1999
+ }
2000
+
1953
2001
  // Check backup configuration
1954
2002
  checkBackupHealth();
1955
2003
 
@@ -2947,6 +2995,98 @@ async function cmdDoctor() {
2947
2995
  }
2948
2996
  }
2949
2997
 
2998
+ // 3c. Cron target health (parallel to crystal doctor's checkCaptureShim)
2999
+ //
3000
+ // When an extension wires a shim into ~/.ldm/bin/ via cron, the cron
3001
+ // line is sticky: it keeps firing even if the shim file goes missing
3002
+ // for any reason. crystal doctor catches this for crystal-capture.sh,
3003
+ // but any cron line referencing ~/.ldm/bin/<file> is at the same risk
3004
+ // class. Walk the crontab and surface broken bin-targeted entries
3005
+ // explicitly. Read-only by default; --fix restores known shims from
3006
+ // their extension dist when the canonical source is present on disk.
3007
+ {
3008
+ const ldmBinPrefix = join(LDM_ROOT, 'bin') + '/';
3009
+ let crontab = '';
3010
+ try {
3011
+ crontab = execSync('crontab -l 2>/dev/null', { encoding: 'utf-8' });
3012
+ } catch {}
3013
+ function expandTilde(p) {
3014
+ return p.startsWith('~') ? join(HOME, p.slice(1)) : p;
3015
+ }
3016
+ function extractCronTarget(line) {
3017
+ const trimmed = line.trim();
3018
+ if (!trimmed.startsWith('*')) return null;
3019
+ const tokens = trimmed.split(/\s+/);
3020
+ if (tokens.length < 6) return null;
3021
+ const expanded = expandTilde(tokens[5]);
3022
+ return expanded.startsWith(ldmBinPrefix) ? expanded : null;
3023
+ }
3024
+ const cronTargets = new Map(); // path → ['missing'|'not executable']
3025
+ const seenTargets = new Set(); // every bin-targeted path we found
3026
+ for (const line of crontab.split('\n')) {
3027
+ const expanded = extractCronTarget(line);
3028
+ if (!expanded) continue;
3029
+ seenTargets.add(expanded);
3030
+ if (cronTargets.has(expanded)) continue;
3031
+ const problems = [];
3032
+ if (!existsSync(expanded)) {
3033
+ problems.push('missing');
3034
+ } else if ((statSync(expanded).mode & 0o111) === 0) {
3035
+ problems.push('not executable');
3036
+ }
3037
+ if (problems.length > 0) cronTargets.set(expanded, problems);
3038
+ }
3039
+ if (cronTargets.size > 0) {
3040
+ // Manifest-driven lookup. Aggregates LDM CLI's wipLdmOs.binFiles
3041
+ // and every registered extension's openclaw.plugin.json#binFiles.
3042
+ // If aggregation reports a conflict, the lookup stays empty and
3043
+ // each broken target becomes "owner unknown" until the conflict
3044
+ // is resolved at the package-manifest layer.
3045
+ const { aggregateBinManifest } = await import('../lib/bin-manifest.mjs');
3046
+ const drManifest = aggregateBinManifest({
3047
+ ldmCliRoot: join(__dirname, '..'),
3048
+ extensionsRoot: LDM_EXTENSIONS,
3049
+ binDir: join(LDM_ROOT, 'bin'),
3050
+ registry: readJSON(REGISTRY_PATH) || { extensions: {} },
3051
+ });
3052
+ const knownSources = {};
3053
+ const declarers = {};
3054
+ if (drManifest.conflicts.length === 0) {
3055
+ for (const e of drManifest.entries) {
3056
+ knownSources[e.name] = e.sourcePath;
3057
+ declarers[e.name] = e.declarer;
3058
+ }
3059
+ } else {
3060
+ console.log(` ! bin manifest conflict: ${drManifest.conflicts.length}; restore disabled until resolved`);
3061
+ }
3062
+ let healed = 0;
3063
+ for (const [path, problems] of cronTargets) {
3064
+ const detail = problems.join(', ');
3065
+ console.log(` ! cron target ${detail}: ${path}`);
3066
+ const base = basename(path);
3067
+ const src = knownSources[base];
3068
+ if (FIX_FLAG && src && existsSync(src)) {
3069
+ try {
3070
+ mkdirSync(dirname(path), { recursive: true });
3071
+ cpSync(src, path);
3072
+ chmodSync(path, 0o755);
3073
+ console.log(` + Restored ${base} from ${src.replace(HOME, '~')}`);
3074
+ healed++;
3075
+ } catch (e) {
3076
+ console.log(` Restore failed: ${e.message}`);
3077
+ }
3078
+ } else if (src) {
3079
+ console.log(` Run: ldm doctor --fix to restore from ${src.replace(HOME, '~')}`);
3080
+ } else {
3081
+ console.log(` Owner unknown; consult the extension that registered this cron entry`);
3082
+ }
3083
+ }
3084
+ issues += cronTargets.size - healed;
3085
+ } else if (seenTargets.size > 0) {
3086
+ console.log(` + Cron targets under ~/.ldm/bin/: ${seenTargets.size} entr${seenTargets.size === 1 ? 'y' : 'ies'}, all exist and executable`);
3087
+ }
3088
+ }
3089
+
2950
3090
  // 4. Check sacred locations
2951
3091
  const sacred = [
2952
3092
  { path: join(LDM_ROOT, 'memory'), label: 'memory/' },
@@ -31,6 +31,8 @@ Your AIs are only as powerful as what you give them. Here's everything available
31
31
 
32
32
  **OpenClaw**
33
33
  - Open-source agent runtime. Run AI agents 24/7 with identity, memory, and tool access. The existence proof for LDM OS.
34
+ - WIP contributions accepted upstream: `before_message_write` plugin hook (#18197), Codex app-server final chat events (#70815 -> maintainer PR #71293), memory-core seed cache streaming/yield (#73067 -> maintainer PR #73118), and fallback vector top-K streaming (#73069 -> maintainer PR #73100).
35
+ - Submitted / superseded: symlink plugin discovery fix (#45744; bug confirmed, superseded by #69971, not landed as submitted).
34
36
  - [Read more about OpenClaw](https://github.com/openclaw/openclaw)
35
37
 
36
38
  ## Identity
@@ -68,7 +68,7 @@ If I say yes, run: ldm install --dry-run
68
68
  Show me exactly what will change. Don't install anything until I say "install".
69
69
  ```
70
70
 
71
- See [TECHNICAL.md](TECHNICAL.md) for sensors/actuators, the interface table, and real examples.
71
+ See [SPEC.md](SPEC.md) for the architecture layers, the **eight interfaces** (CLI, Module, MCP local stdio, Remote MCP, OpenClaw Plugin, Skill, Claude Code Hook, Claude Code Plugin), the install spec URL convention, track flags (alpha/beta), and the `agent.txt` distinction. See [TECHNICAL.md](TECHNICAL.md) for sensors/actuators, the interface table, and real examples.
72
72
 
73
73
  ---
74
74
 
@@ -4,7 +4,29 @@ Every tool is a sensor, an actuator, or both. Every tool should be accessible th
4
4
 
5
5
  This is the spec.
6
6
 
7
- ## The Six Interfaces
7
+ ## Architecture Layers
8
+
9
+ Five layers. Each one does one job. Together they let any AI safely consume any product.
10
+
11
+ | Layer | What it is | Where it lives |
12
+ |-------|-----------|----------------|
13
+ | **Interface** | What a product exposes (CLI, MCP, Skill, etc.). Eight kinds, listed below in canonical order. | The product repo. |
14
+ | **Installer** | Detects a product's interfaces and installs them all. `ldm install`. Stable, alpha, and beta tracks via flags. | `wip-ldm-os` (`bin/ldm.js`). |
15
+ | **Catalog** | Slug→source resolver (npm package, repo, registry/CLI matches, status) **plus** the trust surface: provenance, version pinning, permission scopes, audits, install/update/revocation. Stays human-readable and browseable as a fallback discovery surface; not the primary steering wheel. | `catalog.json` at the LDM OS root. |
16
+ | **Install Spec** | Agent-readable install runbook published at `wip.computer/install/<slug>.txt`. Track-neutral. Teaches an AI to safely check, explain, dry-run, install, update, and pair the product. | `https://wip.computer/install/<slug>.txt`. See [Install Spec](#install-spec). |
17
+ | **Stacks** | Multi-product bundles. One install brings up several products and their MCP servers. | `catalog.json.stacks`. |
18
+
19
+ Use the install spec URL to learn the safe install flow; use catalog to resolve the slug; use `ldm install` with stable/alpha/beta track flags; installer detects and installs the product's declared interfaces; stacks install bundles.
20
+
21
+ ### Primary flow
22
+
23
+ The user's path is **outcome → agent resolves services → install specs / catalog / auth → bespoke artifact**. Not "browse a plugin store and pick one." The catalog stays browseable for the times a user wants to look around, but it is no longer the steering wheel. The steering wheel is the user's stated outcome and the agent's composition.
24
+
25
+ **Personal context** (goals, preferences, prior experiments, constraints) does not come from this spec. It comes from **Memory Crystal**, a sibling LDM OS component. The universal-installer spec describes how *services* expose themselves; Memory Crystal describes how the *agent* knows you. Both feed the bespoke composition.
26
+
27
+ ## The Eight Interfaces
28
+
29
+ The canonical order is fixed: CLI (1), Module (2), MCP Server local stdio (3), Remote MCP (4), OpenClaw Plugin (5), Skill (6), Claude Code Hook (7), Claude Code Plugin (8). Local and Remote MCP sit next to each other because they are sibling transports of the same protocol. Claude Code Plugin sits last because it bundles the others.
8
30
 
9
31
  ### 1. CLI
10
32
 
@@ -45,15 +67,15 @@ An importable ES module. The programmatic interface. Other tools compose with it
45
67
  }
46
68
  ```
47
69
 
48
- ### 3. MCP Server
70
+ ### 3. MCP Server (local stdio)
49
71
 
50
- A JSON-RPC server implementing the Model Context Protocol. Any MCP-compatible agent can use it.
72
+ A JSON-RPC server implementing the Model Context Protocol over stdio. Spawned as a child process by the agent (Claude Code, Cursor, OpenClaw). For the HTTP/SSE sibling, see [#4 Remote MCP](#4-remote-mcp).
51
73
 
52
74
  **Convention:** `mcp-server.mjs` (or `.js`, `.ts`) at the repo root. Uses `@modelcontextprotocol/sdk`.
53
75
 
54
76
  **Detection:** One of `mcp-server.mjs`, `mcp-server.js`, `mcp-server.ts`, `dist/mcp-server.js` exists.
55
77
 
56
- **Install:** Add to `.mcp.json`:
78
+ **Install:** Add to `.mcp.json` with `command` + `args`:
57
79
 
58
80
  ```json
59
81
  {
@@ -64,7 +86,51 @@ A JSON-RPC server implementing the Model Context Protocol. Any MCP-compatible ag
64
86
  }
65
87
  ```
66
88
 
67
- ### 4. OpenClaw Plugin
89
+ ### 4. Remote MCP
90
+
91
+ 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.
92
+
93
+ **Contract:** Remote MCP endpoint is **declared by package/catalog metadata** and **registered by `ldm install`**. No filesystem-sniffing fallback.
94
+
95
+ **Convention:** `mcp.remote` field in `package.json`:
96
+
97
+ ```json
98
+ {
99
+ "mcp": {
100
+ "remote": {
101
+ "url": "https://example.com/mcp",
102
+ "transport": "streamable-http",
103
+ "auth": "oauth"
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ `url` may be a placeholder (`"https://__DEPLOYED_URL__"`) when the repo ships the server code and the URL is supplied by the catalog at install time.
110
+
111
+ **Detection:** `package.json.mcp.remote.url` is a string.
112
+
113
+ **Install:** Add to `.mcp.json` as a remote entry (`url` + `transport` instead of `command` + `args`). Print a one-line Claude Desktop hint so the user can also add it under Connectors. Implementation tracked in [bugs/installer/](../../ai/product/bugs/installer/2026-04-28--cc-mini--installer-remote-mcp-install.md).
114
+
115
+ ```json
116
+ {
117
+ "tool-name": {
118
+ "url": "https://example.com/mcp",
119
+ "transport": "streamable-http"
120
+ }
121
+ }
122
+ ```
123
+
124
+ **How it differs from #3:** sibling transport, not a flag on #3.
125
+
126
+ | | #3 Local stdio | #4 Remote |
127
+ |---|---|---|
128
+ | Transport | stdio (child process) | HTTPS + SSE or streamable HTTP |
129
+ | Process model | Per-session spawn | Long-running, multi-tenant |
130
+ | Auth | Trust the local process | OAuth or shared secret |
131
+ | Surfaces | Claude Code, Cursor, OpenClaw | Claude Desktop, web, mobile |
132
+
133
+ ### 5. OpenClaw Plugin
68
134
 
69
135
  A plugin for OpenClaw agents. Lifecycle hooks, tool registration, settings.
70
136
 
@@ -74,12 +140,14 @@ A plugin for OpenClaw agents. Lifecycle hooks, tool registration, settings.
74
140
 
75
141
  **Install:** Copy to `~/.openclaw/extensions/<name>/`, run `npm install --omit=dev`.
76
142
 
77
- ### 5. Skill (SKILL.md)
143
+ ### 6. Skill (SKILL.md)
78
144
 
79
145
  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).
80
146
 
81
147
  **Convention:** `SKILL.md` at the repo root. YAML frontmatter with name, description. Optional `references/` directory for context files.
82
148
 
149
+ **Platform variants:** Codex CLI reads `AGENTS.md` instead of `SKILL.md`, with the same role and the same content shape. Treat `AGENTS.md` as the Codex-flavored filename for this same interface, not a separate interface. A repo may ship both (or symlink one to the other) so it works in Codex and SKILL.md-aware agents.
150
+
83
151
  **Detection:** `SKILL.md` exists.
84
152
 
85
153
  **Install:** `SKILL.md` deployed to `~/.openclaw/skills/<name>/`. If `references/` exists, deployed alongside SKILL.md and to `settings/docs/skills/<name>/` in the workspace.
@@ -113,7 +181,7 @@ metadata:
113
181
  ---
114
182
  ```
115
183
 
116
- ### 6. Claude Code Hook
184
+ ### 7. Claude Code Hook
117
185
 
118
186
  A hook that runs during Claude Code's tool lifecycle (PreToolUse, Stop, etc.).
119
187
 
@@ -138,6 +206,58 @@ A hook that runs during Claude Code's tool lifecycle (PreToolUse, Stop, etc.).
138
206
  }
139
207
  ```
140
208
 
209
+ ### 8. Claude Code Plugin
210
+
211
+ A distributable plugin for Claude Code. Bundles skills, agents, hooks, MCP servers, and LSP servers into one installable package. Shareable via marketplaces.
212
+
213
+ **Convention:** `.claude-plugin/plugin.json` at the repo root.
214
+
215
+ **Detection:** `.claude-plugin/plugin.json` exists.
216
+
217
+ **Install:** Registered with Claude Code via `/plugin install` or marketplace.
218
+
219
+ ```
220
+ your-plugin/
221
+ ├── .claude-plugin/
222
+ │ └── plugin.json # manifest (name, version, description)
223
+ ├── skills/ # SKILL.md files
224
+ ├── agents/ # subagent definitions
225
+ ├── hooks/
226
+ │ └── hooks.json # event handlers
227
+ ├── .mcp.json # MCP server configs
228
+ └── .lsp.json # LSP server configs
229
+ ```
230
+
231
+ ```json
232
+ {
233
+ "name": "your-plugin",
234
+ "version": "1.0.0",
235
+ "description": "What it does",
236
+ "author": { "name": "Your Name" }
237
+ }
238
+ ```
239
+
240
+ ### Out of scope by design
241
+
242
+ **Disposable, agent-generated artifacts** (custom dashboards, ephemeral scripts, one-off automations, the 300-line cardio tracker someone vibe-codes in an hour) 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*. The composition output is not itself a numbered interface and never will be.
243
+
244
+ ### Worked example (compact sketch)
245
+
246
+ User says: *"Help me track my resting heart rate over the next 8 weeks. Goal: 50 → 45 bpm. Zone 2 cardio + 1 HIIT/week."*
247
+
248
+ The agent:
249
+
250
+ 1. Reads personal context from Memory Crystal (RHR baseline, prior experiments, units preference).
251
+ 2. Resolves the treadmill via catalog → install spec URL → declared Remote MCP (#4) for workout data.
252
+ 3. Pulls calendar/time semantics for the 8-week window.
253
+ 4. Composes a disposable dashboard (~300 lines).
254
+
255
+ The dashboard is **not** a Universal Interface product. It is the agent's output, assembled from agent-native sensors and actuators. Full version of this example is tracked separately ... see [bugs/installer/2026-04-28--cc-mini--installer-cardio-tracker-worked-example.md](../../ai/product/bugs/installer/2026-04-28--cc-mini--installer-cardio-tracker-worked-example.md).
256
+
257
+ ### Future considerations
258
+
259
+ *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 because no WIP product ships one yet, and the spec should describe interfaces we install and use, not interfaces software could theoretically have.
260
+
141
261
  ## Architecture
142
262
 
143
263
  Every repo that follows this spec has the same basic structure:
@@ -153,9 +273,9 @@ your-tool/
153
273
  ai/ development process (plans, todos, notes)
154
274
  ```
155
275
 
156
- Not every tool needs all six interfaces. Build the ones that make sense.
276
+ Not every tool needs all eight interfaces. Build the ones that make sense.
157
277
 
158
- The minimum viable agent-native tool has two interfaces: **Module** (importable) and **Skill** (agent instructions). Add CLI for humans. Add MCP for agents that speak MCP. Add OpenClaw/CC Hook for specific platforms.
278
+ The minimum viable agent-native tool has two interfaces: **Module** (importable) and **Skill** (agent instructions). Add CLI for humans. Add local MCP (#3) for agents that speak MCP over stdio. Add Remote MCP (#4) when you want Claude Desktop / web / mobile to reach the same server. Add OpenClaw Plugin / CC Hook / CC Plugin for specific platforms.
159
279
 
160
280
  ## The `ai/` Folder
161
281
 
@@ -175,20 +295,73 @@ The `ai/` folder is the development process. It is not part of the published pro
175
295
 
176
296
  **Public/private split:** If a repo is public, the `ai/` folder should not ship. The recommended pattern is to maintain a private working repo (with `ai/`) and a public repo (everything except `ai/`). The public repo has everything an LLM or human needs to understand and use the tool. The `ai/` folder is operational context for the team building it.
177
297
 
178
- ## The Reference Installer
298
+ ## The Installer
179
299
 
180
- `ldm install` is the primary installer (part of LDM OS). `wip-install` is the standalone fallback. Both scan a repo, detect which interfaces exist, and install them all. One command.
300
+ `ldm install` is the primary installer (part of LDM OS). `wip-install` is the standalone fallback. Both scan a repo or slug, detect which interfaces exist, and install them all. One command.
181
301
 
182
302
  ```bash
183
- ldm install /path/to/repo # local (via LDM OS)
184
- ldm install org/repo # from GitHub
185
- ldm install org/repo --dry-run # detect only
186
- wip-install /path/to/repo # standalone fallback (bootstraps LDM OS if needed)
187
- wip-install --json /path/to/repo # JSON output
303
+ ldm install /path/to/repo # local (via LDM OS)
304
+ ldm install org/repo # from GitHub
305
+ ldm install <slug> # from catalog (stable, default)
306
+ ldm install --alpha <slug> # alpha (validation track)
307
+ ldm install --beta <slug> # beta (validation track)
308
+ ldm install <slug> --dry-run # detect only, no changes
309
+ wip-install /path/to/repo # standalone fallback (bootstraps LDM OS if needed)
310
+ wip-install --json /path/to/repo # JSON output
188
311
  ```
189
312
 
313
+ Tracks select the npm dist-tag (or git ref) the installer pulls from. The same install spec URL covers all three; the AI follows the spec, the user (or releasing agent) picks the track via flag.
314
+
190
315
  For toolbox repos (with a `tools/` directory containing sub-tools), the installer enters toolbox mode and installs each sub-tool.
191
316
 
317
+ ## Install Spec
318
+
319
+ An **install spec** is an agent-readable install runbook published at a stable URL:
320
+
321
+ ```
322
+ https://wip.computer/install/<slug>.txt
323
+ ```
324
+
325
+ The contract is the URL and the behavior, not the file origin. An install spec can be generated from `SKILL.md`, mirrored from it, or live alongside it. What matters is that any AI can fetch it, read it, and walk a user through a safe install.
326
+
327
+ ### Behavior contract
328
+
329
+ The spec teaches an AI to:
330
+
331
+ 1. **Check** whether the product is already installed and at what version.
332
+ 2. **Explain** what the product is, what it installs, and what changes for this AI and the user's other AIs.
333
+ 3. **Dry-run** so the user sees what will change before anything is touched.
334
+ 4. **Install** only after explicit user consent.
335
+ 5. **Update** an existing install (skip steps the user already did).
336
+ 6. **Pair** any post-install steps (passkey, device pairing, gateway start, etc.) with explicit consent at each step.
337
+
338
+ ### Tracks
339
+
340
+ One install spec covers all release tracks. The user picks via flag:
341
+
342
+ | Track | Flag | Audience |
343
+ |-------|------|----------|
344
+ | Stable | (default) `ldm install <slug>` | End users. Owner-dogfooded. |
345
+ | Beta | `ldm install --beta <slug>` | Validation; agents may install. |
346
+ | Alpha | `ldm install --alpha <slug>` | Validation; agents may install. |
347
+
348
+ The spec text itself is track-neutral. Tracks are an installer concern, not a copy concern.
349
+
350
+ ### Install spec vs `agent.txt`
351
+
352
+ These are related, not the same. Both are agent-readable. They sit at different scopes:
353
+
354
+ - **`agent.txt`** ... site- or product-level entrypoint for agents. "What can agents do here? What routes exist? What policies apply?" Lives at the root of a site or product (e.g. `wip.computer/agent.txt`).
355
+ - **`install/<slug>.txt`** ... per-product install runbook. "How should an agent safely check, explain, dry-run, install, update, and pair this product?" Lives under `wip.computer/install/`.
356
+
357
+ `agent.txt` can point agents at install specs. An install spec does not replace `agent.txt`.
358
+
359
+ ### Worked example: Codex Remote Control
360
+
361
+ Install spec: [`https://wip.computer/install/wip-codex-remote-control.txt`](https://wip.computer/install/wip-codex-remote-control.txt).
362
+
363
+ The user pastes one prompt into Codex (or any AI). The AI fetches the install spec, checks installed state, explains the product, runs `ldm install --dry-run wip-codex-remote-control`, and only installs (and starts the daemon, and pairs the phone) after the user says yes at each step. Tracks are selected by flag against the same URL.
364
+
192
365
  ## Examples
193
366
 
194
367
  ### AI DevOps Toolbox (this repo)