@wipcomputer/wip-ldm-os 0.4.1 → 0.4.3

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
@@ -1,6 +1,6 @@
1
1
  ###### WIP Computer
2
2
 
3
- # LDM OS
3
+ # LDM OS: Learning Dreaming Machines
4
4
 
5
5
  ## All your AIs. One system.
6
6
 
@@ -47,23 +47,27 @@ That's it. Your AI reads the spec, explains what it does, and walks you through
47
47
 
48
48
  Ships with LDM OS.
49
49
 
50
+ **Bridge**
51
+ - Cross-platform agent bridge. Enables Claude Code CLI to talk to OpenClaw CLI without a human in the middle.
52
+ - [Read more about Bridge](docs/bridge/README.md)
53
+
50
54
  **Universal Installer**
51
55
  - Point any skill, application, or plugin at any AI running LDM OS, and it will convert those skills to work with all of your AIs.
52
56
  - Build applications that work with any AI, even ones that don't have LDM OS.
53
- - [Read more about Universal Installer](docs/universal-installer.md)
57
+ - [Read more about Universal Installer](docs/universal-installer/README.md)
54
58
 
55
59
  **Shared Workspace**
56
60
  - One directory for all your AIs. Memories, tools, identity files, boot config. Every AI you use reads from and writes to the same place.
57
61
  - Lives in one folder on your computer. Easy to back up, easy to move, easy to own.
58
- - [Read more about Shared Workspace](docs/shared-workspace.md)
62
+ - [Read more about Shared Workspace](docs/shared-workspace/README.md)
59
63
 
60
64
  **System Pulse**
61
65
  - Is everything working? What's installed? What needs fixing? A complete picture of your AI setup in seconds.
62
- - [Read more about System Pulse](docs/system-pulse.md)
66
+ - [Read more about System Pulse](docs/system-pulse/README.md)
63
67
 
64
68
  **Recall**
65
69
  - Every session, your AI starts with full context. Identity, memory, tools, what happened yesterday. No blank slates. No repeating yourself.
66
- - [Read more about Recall](docs/recall.md)
70
+ - [Read more about Recall](docs/recall/README.md)
67
71
 
68
72
  **LUME**
69
73
  - Language for Unified Memory and Emergence. A memory language for AI agents to document their own learning and maintain continuity across sessions. Not a programming language. A way for your AI to write memories to itself, retrieve past learnings, track unfinished thoughts, and pass context between sessions.
@@ -95,11 +99,7 @@ The OS connects your AIs. Add-ons are what they actually use. Each one is a full
95
99
  - Open-source agent runtime. Run AI agents 24/7 with identity, memory, and tool access. The existence proof for LDM OS.
96
100
  - [Read more about OpenClaw](https://github.com/openclaw/openclaw)
97
101
 
98
- **Bridge**
99
- - Cross-platform agent bridge. Enables Claude Code CLI to talk to OpenClaw CLI without a human in the middle.
100
- - [Read more about Bridge](https://github.com/wipcomputer/wip-bridge)
101
-
102
- [See all skills](docs/optional-skills.md)
102
+ [See all skills](docs/skills/README.md)
103
103
 
104
104
  ## More Info
105
105
 
package/SKILL.md CHANGED
@@ -5,7 +5,7 @@ license: MIT
5
5
  interface: [cli, skill]
6
6
  metadata:
7
7
  display-name: "LDM OS"
8
- version: "0.4.1"
8
+ version: "0.4.3"
9
9
  homepage: "https://github.com/wipcomputer/wip-ldm-os"
10
10
  author: "Parker Todd Brooks"
11
11
  category: infrastructure
package/bin/ldm.js CHANGED
@@ -17,7 +17,7 @@
17
17
  * ldm --version Show version
18
18
  */
19
19
 
20
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
20
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, cpSync, chmodSync } from 'node:fs';
21
21
  import { join, basename, resolve, dirname } from 'node:path';
22
22
  import { execSync } from 'node:child_process';
23
23
  import { fileURLToPath } from 'node:url';
@@ -146,6 +146,28 @@ function cleanStaleHooks() {
146
146
  return cleaned;
147
147
  }
148
148
 
149
+ // ── Boot hook sync (#49) ──
150
+
151
+ function syncBootHook() {
152
+ const srcBoot = join(__dirname, '..', 'src', 'boot', 'boot-hook.mjs');
153
+ const destBoot = join(LDM_ROOT, 'shared', 'boot', 'boot-hook.mjs');
154
+
155
+ if (!existsSync(srcBoot)) return false;
156
+
157
+ try {
158
+ const srcContent = readFileSync(srcBoot, 'utf8');
159
+ let destContent = '';
160
+ try { destContent = readFileSync(destBoot, 'utf8'); } catch {}
161
+
162
+ if (srcContent !== destContent) {
163
+ mkdirSync(dirname(destBoot), { recursive: true });
164
+ writeFileSync(destBoot, srcContent);
165
+ return true;
166
+ }
167
+ } catch {}
168
+ return false;
169
+ }
170
+
149
171
  // ── Catalog helpers ──
150
172
 
151
173
  function loadCatalog() {
@@ -172,6 +194,7 @@ async function cmdInit() {
172
194
  join(LDM_ROOT, 'messages'),
173
195
  join(LDM_ROOT, 'shared', 'boot'),
174
196
  join(LDM_ROOT, 'shared', 'cron'),
197
+ join(LDM_ROOT, 'hooks'),
175
198
  ];
176
199
 
177
200
  const existing = existsSync(VERSION_PATH);
@@ -230,6 +253,27 @@ async function cmdInit() {
230
253
  console.log(` + registry.json created`);
231
254
  }
232
255
 
256
+ // Install global git pre-commit hook (blocks commits on main)
257
+ const hooksDir = join(LDM_ROOT, 'hooks');
258
+ const preCommitDest = join(hooksDir, 'pre-commit');
259
+ const preCommitSrc = join(__dirname, '..', 'templates', 'hooks', 'pre-commit');
260
+ if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
261
+ if (existsSync(preCommitSrc)) {
262
+ cpSync(preCommitSrc, preCommitDest);
263
+ chmodSync(preCommitDest, 0o755);
264
+ // Set global hooksPath if not already set to somewhere else
265
+ try {
266
+ const currentHooksPath = execSync('git config --global core.hooksPath', { encoding: 'utf8' }).trim();
267
+ if (currentHooksPath !== hooksDir) {
268
+ console.log(` ! core.hooksPath already set to ${currentHooksPath}. Not overwriting.`);
269
+ }
270
+ } catch {
271
+ // Not set. Set it.
272
+ execSync(`git config --global core.hooksPath "${hooksDir}"`);
273
+ console.log(` + git pre-commit hook installed (blocks commits on main)`);
274
+ }
275
+ }
276
+
233
277
  console.log('');
234
278
  console.log(` LDM OS v${PKG_VERSION} initialized at ${LDM_ROOT}`);
235
279
  console.log('');
@@ -478,7 +522,7 @@ function autoDetectExtensions() {
478
522
  const dirs = readdirSync(LDM_EXTENSIONS, { withFileTypes: true });
479
523
  for (const dir of dirs) {
480
524
  if (!dir.isDirectory()) continue;
481
- if (dir.name === '_trash' || dir.name.startsWith('.')) continue;
525
+ if (dir.name === '_trash' || dir.name.startsWith('.') || dir.name.startsWith('ldm-install-')) continue;
482
526
 
483
527
  const extPath = join(LDM_EXTENSIONS, dir.name);
484
528
  const pkgPath = join(extPath, 'package.json');
@@ -559,14 +603,61 @@ async function cmdInstallCatalog() {
559
603
  console.log('');
560
604
  }
561
605
 
562
- if (DRY_RUN) {
563
- // Show what an update would do
564
- const updatable = Object.values(reconciled).filter(e =>
565
- e.registryHasSource
566
- );
606
+ // Build the update plan: extensions with valid source paths + catalog items with npm updates (#55)
607
+ const fromSource = Object.values(reconciled).filter(e => e.registryHasSource);
567
608
 
609
+ // For extensions without valid source: check catalog for repo, check npm for newer version
610
+ const fromCatalog = [];
611
+ for (const [name, entry] of Object.entries(reconciled)) {
612
+ if (entry.registryHasSource) continue; // already handled above
613
+ if (!entry.deployedLdm && !entry.deployedOc) continue; // not installed
614
+
615
+ // Find this extension in the catalog
616
+ const catalogEntry = components.find(c => {
617
+ const matches = c.registryMatches || [c.id];
618
+ return matches.includes(name) || c.id === name;
619
+ });
620
+ if (!catalogEntry?.repo) continue;
621
+
622
+ // Check npm for newer version
623
+ const npmPkg = catalogEntry.npm;
624
+ const currentVersion = entry.ldmVersion || entry.ocVersion;
625
+ let latestVersion = null;
626
+
627
+ if (npmPkg && currentVersion) {
628
+ try {
629
+ latestVersion = execSync(`npm view ${npmPkg} version 2>/dev/null`, {
630
+ encoding: 'utf8', timeout: 10000,
631
+ }).trim();
632
+ } catch {}
633
+ }
634
+
635
+ const hasUpdate = latestVersion && currentVersion && latestVersion !== currentVersion;
636
+ fromCatalog.push({
637
+ ...entry,
638
+ catalogRepo: catalogEntry.repo,
639
+ catalogNpm: npmPkg,
640
+ currentVersion,
641
+ latestVersion,
642
+ hasUpdate,
643
+ });
644
+ }
645
+
646
+ const updatable = fromSource;
647
+ const npmUpdates = fromCatalog.filter(e => e.hasUpdate);
648
+ const totalUpdates = updatable.length + npmUpdates.length;
649
+
650
+ if (DRY_RUN) {
568
651
  if (updatable.length > 0) {
569
652
  console.log(` Would update ${updatable.length} extension(s) from source repos.`);
653
+ }
654
+ if (npmUpdates.length > 0) {
655
+ console.log(` Would update ${npmUpdates.length} extension(s) from npm:`);
656
+ for (const e of npmUpdates) {
657
+ console.log(` ${e.name}: v${e.currentVersion} -> v${e.latestVersion} (${e.catalogNpm})`);
658
+ }
659
+ }
660
+ if (totalUpdates > 0) {
570
661
  console.log(' No data (crystal.db, agent files) would be touched.');
571
662
  console.log(' Old versions would be moved to ~/.ldm/_trash/ (never deleted).');
572
663
  } else {
@@ -579,18 +670,15 @@ async function cmdInstallCatalog() {
579
670
  return;
580
671
  }
581
672
 
582
- // Real update: only touch things with linked source repos
583
- const updatable = Object.values(reconciled).filter(e =>
584
- e.registryHasSource
585
- );
586
-
587
- if (updatable.length === 0) {
588
- console.log(' No extensions have linked source repos to update from.');
589
- console.log(' Link them with: ldm install <org/repo>');
673
+ if (totalUpdates === 0 && available.length === 0) {
674
+ console.log(' Everything is up to date.');
590
675
  console.log('');
676
+ return;
677
+ }
591
678
 
592
- // Still offer catalog install if TTY
593
- if (available.length > 0 && !YES_FLAG && !NONE_FLAG && process.stdin.isTTY) {
679
+ if (totalUpdates === 0 && available.length > 0) {
680
+ // Nothing to update, but catalog items available
681
+ if (!YES_FLAG && !NONE_FLAG && process.stdin.isTTY) {
594
682
  const { createInterface } = await import('node:readline');
595
683
  const rl = createInterface({ input: process.stdin, output: process.stdout });
596
684
  const answer = await new Promise((resolve) => {
@@ -599,7 +687,6 @@ async function cmdInstallCatalog() {
599
687
  resolve(a.trim().toLowerCase());
600
688
  });
601
689
  });
602
-
603
690
  if (answer && answer !== 'none' && answer !== 'n') {
604
691
  let toInstall = [];
605
692
  if (answer === 'all' || answer === 'a') {
@@ -624,13 +711,19 @@ async function cmdInstallCatalog() {
624
711
  // Write revert manifest before starting
625
712
  const { createRevertManifest } = await import('../lib/safe.mjs');
626
713
  const manifestPath = createRevertManifest(
627
- `ldm install (update ${updatable.length} extensions)`,
628
- updatable.map(e => ({
629
- action: 'update',
714
+ `ldm install (update ${totalUpdates} extensions)`,
715
+ [...updatable.map(e => ({
716
+ action: 'update-from-source',
630
717
  name: e.name,
631
718
  currentVersion: e.ldmVersion || e.registryVersion,
632
719
  source: e.registrySource,
633
- }))
720
+ })), ...npmUpdates.map(e => ({
721
+ action: 'update-from-catalog',
722
+ name: e.name,
723
+ currentVersion: e.currentVersion,
724
+ latestVersion: e.latestVersion,
725
+ repo: e.catalogRepo,
726
+ }))]
634
727
  );
635
728
  console.log(` Revert plan saved: ${manifestPath}`);
636
729
  console.log('');
@@ -639,13 +732,31 @@ async function cmdInstallCatalog() {
639
732
  setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT });
640
733
 
641
734
  let updated = 0;
735
+
736
+ // Update from source repos (local paths)
642
737
  for (const entry of updatable) {
643
738
  await installFromPath(entry.registrySource);
644
739
  updated++;
645
740
  }
646
741
 
742
+ // Update from catalog repos (clone from GitHub for extensions without valid source) (#55)
743
+ for (const entry of npmUpdates) {
744
+ console.log(` Updating ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion} (from ${entry.catalogRepo})...`);
745
+ try {
746
+ execSync(`ldm install ${entry.catalogRepo}`, { stdio: 'inherit' });
747
+ updated++;
748
+ } catch (e) {
749
+ console.error(` x Failed to update ${entry.name}: ${e.message}`);
750
+ }
751
+ }
752
+
753
+ // Sync boot hook from npm package (#49)
754
+ if (syncBootHook()) {
755
+ ok('Boot hook updated (sessions, messages, updates now active)');
756
+ }
757
+
647
758
  console.log('');
648
- console.log(` Updated ${updated}/${updatable.length} extension(s).`);
759
+ console.log(` Updated ${updated}/${totalUpdates} extension(s).`);
649
760
 
650
761
  // Check if CLI itself is outdated (#29)
651
762
  checkCliVersion();
@@ -731,6 +842,30 @@ async function cmdDoctor() {
731
842
  }
732
843
  }
733
844
 
845
+ // --fix: clean registry entries with /tmp/ sources or ldm-install- names (#54)
846
+ if (FIX_FLAG) {
847
+ const registry = readJSON(REGISTRY_PATH);
848
+ if (registry?.extensions) {
849
+ const staleNames = [];
850
+ for (const [name, info] of Object.entries(registry.extensions)) {
851
+ const src = info?.source || '';
852
+ const isTmpSource = src.startsWith('/tmp/') || src.startsWith('/private/tmp/');
853
+ const isTmpName = name.startsWith('ldm-install-');
854
+ if (isTmpSource || isTmpName) {
855
+ staleNames.push(name);
856
+ }
857
+ }
858
+ for (const name of staleNames) {
859
+ delete registry.extensions[name];
860
+ console.log(` + Removed stale registry entry: ${name} (/tmp/ clone)`);
861
+ issues = Math.max(0, issues - 1);
862
+ }
863
+ if (staleNames.length > 0) {
864
+ writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
865
+ }
866
+ }
867
+ }
868
+
734
869
  // 4. Check sacred locations
735
870
  const sacred = [
736
871
  { path: join(LDM_ROOT, 'memory'), label: 'memory/' },
@@ -1345,7 +1480,7 @@ async function main() {
1345
1480
  await cmdUpdateAll();
1346
1481
  break;
1347
1482
  case 'doctor':
1348
- cmdDoctor();
1483
+ await cmdDoctor();
1349
1484
  break;
1350
1485
  case 'status':
1351
1486
  cmdStatus();
@@ -28,3 +28,7 @@ LDM OS does not currently implement ACP-Comm. The file-based message bus and ses
28
28
  ## License Compatibility
29
29
 
30
30
  Both protocols are Apache 2.0, fully compatible with LDM OS's MIT + AGPLv3 dual license.
31
+
32
+ ---
33
+
34
+ [Technical Reference](./TECHNICAL.md)
@@ -0,0 +1,42 @@
1
+ # ACP Compatibility ... Technical Reference
2
+
3
+ ## Protocols
4
+
5
+ | Protocol | Full Name | Origin | License | Wire Format |
6
+ |----------|-----------|--------|---------|-------------|
7
+ | ACP-Client | Agent Client Protocol | Zed Industries | Apache 2.0 | JSON-RPC (stdio) + HTTP/WebSocket |
8
+ | ACP-Comm | Agent Communication Protocol | IBM / Linux Foundation | Apache 2.0 | REST/HTTP |
9
+ | MCP | Model Context Protocol | Anthropic | MIT | JSON-RPC (stdio) |
10
+
11
+ ## How LDM OS Uses Each
12
+
13
+ **MCP (current):** All LDM OS tools (bridge, sessions, messages, updates, crystal) are MCP servers. Claude Code, Cursor, and any MCP client can use them. This is the primary interface.
14
+
15
+ **ACP-Client (available, not configured):** OpenClaw implements ACP-Client via `@agentclientprotocol/sdk`. It enables IDE-to-agent communication (Zed, VS Code). LDM OS could expose services via ACP-Client for IDE integration. The transport-agnostic core design supports adding ACP-Client as another wrapper.
16
+
17
+ **ACP-Comm (not implemented):** Agent-to-agent REST protocol. LDM OS's file-based message bus and session registry serve the same purpose locally. Cloud relay (Phase 7) may evaluate ACP-Comm as a wire protocol for remote agent-to-agent communication.
18
+
19
+ ## Architecture
20
+
21
+ ```
22
+ LDM OS Core (pure functions, zero deps)
23
+ |
24
+ |-- MCP wrapper (current, all tools)
25
+ |-- ACP-Client wrapper (future, IDE integration)
26
+ |-- ACP-Comm wrapper (future, cloud relay)
27
+ |-- HTTP wrapper (future, web/iOS)
28
+ ```
29
+
30
+ Same core logic. Different transports. The core doesn't know or care which protocol is calling it.
31
+
32
+ ## License Compatibility
33
+
34
+ Both ACP protocols are Apache 2.0. LDM OS is MIT + AGPLv3 dual license. Apache 2.0 is compatible with both. No license conflicts.
35
+
36
+ ## Key Files
37
+
38
+ | File | What |
39
+ |------|------|
40
+ | `src/bridge/mcp-server.ts` | MCP server (current interface) |
41
+ | `lib/sessions.mjs` | Session register (could expose via ACP-Comm) |
42
+ | `lib/messages.mjs` | Message bus (could expose via ACP-Comm) |
@@ -0,0 +1,55 @@
1
+ ###### WIP Computer
2
+
3
+ # Bridge
4
+
5
+ ## Your AIs talk to each other.
6
+
7
+ Cross-platform agent communication. Bridge (MCP), Agent Client Protocol (ACP-Client, Zed Industries), and Agent Communication Protocol (ACP-Comm, IBM/Linux Foundation). Three protocols, one system. Claude Code sends a message, OpenClaw responds. OpenClaw sends a task, Claude Code executes it.
8
+
9
+ ## Three Protocols, One System
10
+
11
+ LDM OS uses three complementary protocols. Bridge is one of them.
12
+
13
+ | | Bridge | ACP-Client | ACP-Comm |
14
+ |---|---|---|---|
15
+ | **What** | Agent-to-agent messaging + shared memory | IDE-to-agent communication | Agent-to-agent REST API |
16
+ | **Protocol** | MCP (JSON-RPC over stdio) | JSON-RPC over stdio + WebSocket | REST/HTTP |
17
+ | **Built by** | WIP Computer | Zed Industries | IBM / Linux Foundation |
18
+ | **In LDM OS** | Core (v0.3.0+) | Available via OpenClaw | Planned (Cloud Relay) |
19
+ | **What it connects** | Claude Code <-> OpenClaw agents | IDEs (Zed, VS Code) <-> agents | Cloud agents <-> each other |
20
+ | **Memory access** | Yes (search + read across agents) | No | No |
21
+ | **Skill sharing** | Yes (OpenClaw skills as MCP tools) | No | No |
22
+ | **Where it runs** | Localhost only | Localhost (stdio) + remote (WebSocket) | Cloud (HTTP endpoints) |
23
+
24
+ **Bridge** is how your AIs talk to each other and share memory. **ACP-Client** is how IDEs talk to agents (OpenClaw already implements this). **ACP-Comm** is how agents would talk across the network (planned for Cloud Relay, Phase 7).
25
+
26
+ All three are Apache 2.0 compatible with our MIT + AGPL license. See [ACP docs](../acp/README.md).
27
+
28
+ ## Tools
29
+
30
+ | Tool | What |
31
+ |------|------|
32
+ | `lesa_send_message` | Send a message to the OpenClaw agent. Gets a response. |
33
+ | `lesa_check_inbox` | Check for messages the agent sent to you. |
34
+ | `lesa_conversation_search` | Semantic search over conversation history. |
35
+ | `lesa_memory_search` | Keyword search across workspace files. |
36
+ | `lesa_read_workspace` | Read a file from the agent's workspace. |
37
+ | `oc_skills_list` | List all OpenClaw skills. |
38
+ | `oc_skill_*` | Run any OpenClaw skill with scripts. |
39
+
40
+ ## Message Flow
41
+
42
+ ```
43
+ CC --lesa_send_message--> OpenClaw Gateway (localhost:18789) --> Lesa
44
+ CC <--lesa_check_inbox--- HTTP Inbox (localhost:18790) <-- Lesa
45
+ ```
46
+
47
+ Both directions are live. Everything is localhost. No cloud.
48
+
49
+ ## Part of LDM OS
50
+
51
+ Bridge ships with LDM OS v0.3.0+. The standalone repo is deprecated: [wip-bridge-deprecated](https://github.com/wipcomputer/wip-bridge-deprecated).
52
+
53
+ ---
54
+
55
+ [Technical Reference](./TECHNICAL.md)
@@ -0,0 +1,153 @@
1
+ # Bridge ... Technical Reference
2
+
3
+ ## Architecture
4
+
5
+ ```
6
+ Claude Code CLI
7
+ |
8
+ |-- MCP stdio --> wip-bridge MCP server (single process)
9
+ | |
10
+ | |-- lesa_send_message --> HTTP POST localhost:18789 (OpenClaw gateway)
11
+ | | |
12
+ | | v
13
+ | | Lesa's agent pipeline
14
+ | | |
15
+ | | v
16
+ | | Response returned
17
+ | |
18
+ | |-- lesa_check_inbox --> HTTP GET localhost:18790 (Bridge inbox)
19
+ | |
20
+ | |-- lesa_conversation_search --> SQLite (context-embeddings.sqlite)
21
+ | |
22
+ | |-- lesa_memory_search --> filesystem (workspace/*.md)
23
+ | |
24
+ | |-- oc_skill_* --> exec scripts in ~/.openclaw/extensions/*/skills/
25
+ ```
26
+
27
+ ## MCP Tools
28
+
29
+ | Tool | What | Transport |
30
+ |------|------|-----------|
31
+ | `lesa_send_message` | Send message to OpenClaw agent | HTTP POST to gateway (localhost:18789) |
32
+ | `lesa_check_inbox` | Check for pending messages from agent | In-memory queue (drained on read) |
33
+ | `lesa_conversation_search` | Semantic search over conversation history | SQLite + OpenAI embeddings |
34
+ | `lesa_memory_search` | Keyword search across workspace .md files | Filesystem scan |
35
+ | `lesa_read_workspace` | Read a specific workspace file | Filesystem |
36
+ | `oc_skill_*` | Execute OpenClaw skill scripts | child_process exec |
37
+ | `oc_skills_list` | List all available OpenClaw skills | Filesystem scan |
38
+
39
+ ## Config Resolution
40
+
41
+ Bridge resolves config in two steps:
42
+
43
+ 1. **LDM OS path** (`~/.ldm/config.json`): checks for `openclawDir`, `workspaceDir`, `dbPath`
44
+ 2. **Legacy path** (`OPENCLAW_DIR` env or `~/.openclaw`): fallback
45
+
46
+ Both return the same `BridgeConfig` shape: `openclawDir`, `workspaceDir`, `dbPath`, `inboxPort`, `embeddingModel`, `embeddingDimensions`.
47
+
48
+ ## Gateway Authentication
49
+
50
+ Bridge reads the gateway auth token from `~/.openclaw/openclaw.json` (`gateway.auth.token`). Every HTTP request to the gateway includes `Authorization: Bearer <token>`.
51
+
52
+ ## Conversation Search
53
+
54
+ Two modes:
55
+ 1. **Vector search** (if OpenAI API key available): embeds query, computes cosine similarity against all conversation chunks, applies recency weighting (exponential decay, half-life ~50 days)
56
+ 2. **Text search** (fallback): SQL LIKE query against chunk text
57
+
58
+ The API key is resolved from: environment variable > 1Password via service account token.
59
+
60
+ ## Inbox Server
61
+
62
+ Bridge starts a localhost-only HTTP server on port 18790:
63
+ - `POST /message`: push a message (from OpenClaw agent)
64
+ - `GET /status`: check pending count
65
+
66
+ Messages are held in memory. `lesa_check_inbox` drains the queue.
67
+
68
+ ## Key Files
69
+
70
+ | File | What |
71
+ |------|------|
72
+ | `src/bridge/core.ts` | Pure logic: config, messaging, search, skills |
73
+ | `src/bridge/mcp-server.ts` | MCP server: tool registration, inbox HTTP server |
74
+ | `src/bridge/cli.ts` | CLI wrapper (`lesa` command) |
75
+ | `dist/bridge/` | Compiled output (ships with npm package) |
76
+
77
+ ## Node Communication (Future)
78
+
79
+ Bridge currently works localhost only (Core). For Node -> Core communication:
80
+ - Phase 7 (Cloud Relay) handles messaging via encrypted Cloudflare R2 dead drops
81
+ - Search, workspace read, and skill execution from a Node are NOT yet covered
82
+ - Two proposed solutions: proxy pattern (Node sends requests through relay) and sync pattern (replicate crystal.db to Node)
83
+
84
+ See `ai/products/plans-prds/current/ldm-stack-spec.md` for the full platform matrix.
85
+
86
+ ## CLI Usage
87
+
88
+ ```bash
89
+ lesa send "What are you working on?" # Message the OpenClaw agent
90
+ lesa search "API key resolution" # Semantic search (recency-weighted)
91
+ lesa memory "compaction" # Keyword search across workspace files
92
+ lesa read MEMORY.md # Read a workspace file
93
+ lesa read memory/2026-02-10.md # Read a daily log
94
+ lesa status # Show bridge configuration
95
+ lesa diagnose # Check gateway, inbox, DB, skills health
96
+ ```
97
+
98
+ ## Skill Bridge
99
+
100
+ On startup, Bridge scans OpenClaw's skill directories and exposes them as MCP tools. Claude Code gets the same skills the OpenClaw agent has.
101
+
102
+ 1. Scans `extensions/*/node_modules/openclaw/skills/` (built-in) and `extensions/*/skills/` (custom)
103
+ 2. Parses each `SKILL.md` frontmatter for name, description, requirements
104
+ 3. Skills with a `scripts/` folder get registered as executable `oc_skill_{name}` tools
105
+ 4. All skills show up in `oc_skills_list`
106
+
107
+ Skills that need API keys get them from the environment. The op-secrets plugin sets these from 1Password.
108
+
109
+ ## Inbox Watcher (Auto-Relay)
110
+
111
+ Polls the inbox endpoint and auto-injects messages into a running Claude Code tmux session.
112
+
113
+ ```bash
114
+ bash scripts/watch.sh # Alert mode (notification only)
115
+ bash scripts/watch.sh --auto claude:0.0 # Auto-inject into tmux pane
116
+ ```
117
+
118
+ | Setting | Default | Description |
119
+ |---------|---------|-------------|
120
+ | `POLL_INTERVAL` | `5` | Seconds between inbox checks |
121
+ | `COOLDOWN` | `30` | Minimum seconds between alerts |
122
+ | `INBOX_URL` | `http://127.0.0.1:18790/status` | Inbox status endpoint |
123
+
124
+ ## OpenClaw Skills (Lesa -> CC)
125
+
126
+ Two skills shipped with Bridge for the reverse direction:
127
+
128
+ | Skill | What |
129
+ |-------|------|
130
+ | `claude-code` | Invoke Claude Code CLI for coding tasks (`claude -p`) |
131
+ | `send-to-claude-code` | Push messages into CC's live inbox (POST localhost:18790) |
132
+
133
+ ## Environment Variables
134
+
135
+ | Variable | Required | Default | Description |
136
+ |----------|----------|---------|-------------|
137
+ | `OPENCLAW_DIR` | Yes | `~/.openclaw` | Path to OpenClaw installation |
138
+ | `OPENAI_API_KEY` | For semantic search | Resolved from 1Password | OpenAI API key for embeddings |
139
+ | `LESA_BRIDGE_INBOX_PORT` | No | `18790` | Inbox HTTP server port |
140
+
141
+ ## Ports
142
+
143
+ | Port | Service | Bound to |
144
+ |------|---------|----------|
145
+ | 18789 | OpenClaw gateway | localhost (managed by OpenClaw) |
146
+ | 18790 | Bridge inbox | localhost (managed by Bridge) |
147
+
148
+ ## Requirements
149
+
150
+ - OpenClaw running with gateway enabled
151
+ - `gateway.auth.token` set in `openclaw.json`
152
+ - `gateway.http.endpoints.chatCompletions.enabled: true`
153
+ - OpenAI API key for semantic search (falls back to text search without it)
@@ -27,3 +27,7 @@ With Recall, your AI remembers. Not just facts, but the arc of what you're build
27
27
  ## Part of LDM OS
28
28
 
29
29
  Recall is included with LDM OS. It activates automatically after `ldm init`.
30
+
31
+ ---
32
+
33
+ [Technical Reference](./TECHNICAL.md)
@@ -0,0 +1,41 @@
1
+ # Recall ... Technical Reference
2
+
3
+ ## Boot Sequence
4
+
5
+ Recall is implemented as a SessionStart hook. When Claude Code opens a session, the boot hook loads context files in order:
6
+
7
+ ```
8
+ 1. CLAUDE.md Identity + structure (harness config)
9
+ 2. SHARED-CONTEXT.md Current state (under 50 lines)
10
+ 3. Most recent journal Narrative from last session
11
+ 4. Daily logs (today + yesterday) Recency
12
+ 5. Full history (cold start) Dream Weaver narrative
13
+ 6. CONTEXT.md Agent's own state
14
+ 7. SOUL.md Who this agent is
15
+ 8. Agent journals Check for newer entries
16
+ 9. Agent daily logs Check for newer entries
17
+ ```
18
+
19
+ ## Key Files
20
+
21
+ | File | What |
22
+ |------|------|
23
+ | `src/boot/boot-hook.mjs` | SessionStart hook (reads + injects context) |
24
+ | `src/boot/boot-config.json` | Which files to load, in what order |
25
+ | `src/boot/installer.mjs` | Deploys boot hook to `~/.ldm/shared/boot/` |
26
+
27
+ ## How It Works
28
+
29
+ The boot hook reads `boot-config.json` to determine which files to load. Each entry specifies a path pattern and priority. Files are read in priority order and concatenated into the session context.
30
+
31
+ The hook runs with a 15-second timeout. If any file is missing, it's skipped silently. The boot sequence is additive. It never modifies files, only reads them.
32
+
33
+ ## Harness Differences
34
+
35
+ **Claude Code CLI:** Full boot sequence. Identity, context, journals, daily logs all loaded via SessionStart hook.
36
+
37
+ **OpenClaw:** Has its own boot sequence (workspace files). Recall provides backup context loading if the harness boot fails or is incomplete.
38
+
39
+ ## Session Registration
40
+
41
+ On boot, Recall also registers the session in the Agent Register (`~/.ldm/sessions/`). This enables other sessions to discover this one via `ldm sessions`.
@@ -35,3 +35,7 @@ LDM OS never touches your existing data during install or update. Your memories,
35
35
  ## Part of LDM OS
36
36
 
37
37
  Shared Workspace is included with LDM OS. Run `ldm init` to create it.
38
+
39
+ ---
40
+
41
+ [Technical Reference](./TECHNICAL.md)
@@ -0,0 +1,75 @@
1
+ # Shared Workspace ... Technical Reference
2
+
3
+ ## Directory Structure
4
+
5
+ ```
6
+ ~/.ldm/
7
+ config.json Machine-level config
8
+ version.json Installed version + timestamps
9
+ extensions/
10
+ registry.json Extension metadata
11
+ memory-crystal/ Extension files
12
+ wip-release/ Extension files
13
+ ...
14
+ agents/
15
+ cc-mini/ Claude Code on Mac Mini
16
+ IDENTITY.md
17
+ SOUL.md
18
+ CONTEXT.md
19
+ REFERENCE.md
20
+ config.json
21
+ memory/
22
+ transcripts/ Raw JSONL session files
23
+ sessions/ MD per-session summaries
24
+ daily/ Daily log breadcrumbs
25
+ journals/ Dream Weaver narrative output
26
+ oc-lesa-mini/ OpenClaw/Lesa on Mac Mini
27
+ memory/
28
+ transcripts/ Raw JSONL copies (backup)
29
+ workspace/ Workspace .md snapshots
30
+ daily/ Daily log copies
31
+ memory/
32
+ crystal.db Shared vector DB (all agents)
33
+ sessions/ Active session files (Agent Register)
34
+ messages/ Inter-session messages (Message Bus)
35
+ shared/
36
+ boot/ Boot hook files
37
+ cron/ Cron job scripts
38
+ state/ Capture watermarks, role state
39
+ secrets/ Encryption keys, relay tokens
40
+ bin/ Deployed scripts
41
+ backups/ Daily backups
42
+ ```
43
+
44
+ ## Sacred Data
45
+
46
+ These paths are never overwritten during install or update:
47
+
48
+ - `memory/` (crystal.db, all vector data)
49
+ - `agents/` (identity, soul, context, journals, transcripts)
50
+ - `secrets/` (encryption keys, tokens)
51
+ - `state/` (watermarks, capture state)
52
+ - `backups/` (daily snapshots)
53
+
54
+ Only `extensions/` and `shared/` are updated by `ldm install`.
55
+
56
+ ## Agent Identity
57
+
58
+ One agent per harness per machine. The agent ID is deterministic:
59
+
60
+ | Agent ID | Harness | Machine |
61
+ |----------|---------|---------|
62
+ | `cc-mini` | Claude Code CLI | Mac Mini |
63
+ | `cc-air` | Claude Code CLI | MacBook Air |
64
+ | `oc-lesa-mini` | OpenClaw | Mac Mini |
65
+
66
+ All agents share one `crystal.db`. Memory is shared. Identity is per-agent.
67
+
68
+ ## Key Files
69
+
70
+ | File | What |
71
+ |------|------|
72
+ | `bin/ldm.js` | CLI: `ldm init` creates the workspace |
73
+ | `lib/deploy.mjs` | Extension deployment engine |
74
+ | `lib/state.mjs` | System state detection |
75
+ | `lib/safe.mjs` | Trash management (never delete, always move) |
@@ -6,6 +6,11 @@ Your AIs are only as powerful as what you give them. Here's everything available
6
6
 
7
7
  ## Core
8
8
 
9
+ **Bridge**
10
+ - Cross-platform agent communication. Bridge (MCP), Agent Client Protocol (ACP-Client), and Agent Communication Protocol (ACP-Comm). Three protocols, one system.
11
+ - *Included with LDM OS*
12
+ - [Read more about Bridge](../bridge/README.md)
13
+
9
14
  **Memory Crystal**
10
15
  - All your AI tools. One shared memory. Private, searchable, sovereign. Memory Crystal lets all your AIs remember you ... together. You use multiple AIs. They don't talk to each other. They can't search what the others know. Memory Crystal fixes this. All your AIs share one memory. Searchable and private. Anywhere in the world.
11
16
  - *Stable*
@@ -28,10 +33,6 @@ Your AIs are only as powerful as what you give them. Here's everything available
28
33
  - Open-source agent runtime. Run AI agents 24/7 with identity, memory, and tool access. The existence proof for LDM OS.
29
34
  - [Read more about OpenClaw](https://github.com/openclaw/openclaw)
30
35
 
31
- **Bridge**
32
- - Cross-platform agent bridge. Enables Claude Code CLI to talk to OpenClaw CLI without a human in the middle.
33
- - [Read more about Bridge](https://github.com/wipcomputer/wip-bridge)
34
-
35
36
  ## Identity
36
37
 
37
38
  **Mirror Test** *(not yet public)*
@@ -75,3 +76,7 @@ Your AIs are only as powerful as what you give them. Here's everything available
75
76
  **X Platform**
76
77
  - X Platform API. Read posts, search tweets, post, upload media.
77
78
  - [Read more about X Platform](https://github.com/wipcomputer/wip-xai-x)
79
+
80
+ ---
81
+
82
+ [Technical Reference](../universal-installer/TECHNICAL.md)
@@ -24,3 +24,7 @@ System Pulse tells you the state of your entire AI setup in seconds. What's inst
24
24
  ## Part of LDM OS
25
25
 
26
26
  System Pulse is included with LDM OS. Available after `ldm init`.
27
+
28
+ ---
29
+
30
+ [Technical Reference](./TECHNICAL.md)
@@ -0,0 +1,56 @@
1
+ # System Pulse ... Technical Reference
2
+
3
+ ## Commands
4
+
5
+ ### ldm doctor
6
+
7
+ Checks system health across all components:
8
+
9
+ 1. `~/.ldm/` exists
10
+ 2. `version.json` valid and version matches CLI
11
+ 3. Full system state scan (extensions, MCP, CLIs, skills)
12
+ 4. Reconciliation (registry vs deployed vs MCP registered)
13
+ 5. Sacred directories exist (memory/, agents/, state/, sessions/, messages/)
14
+ 6. Claude Code hooks configured (checks for stale paths)
15
+ 7. MCP servers registered
16
+ 8. CLI binaries on PATH
17
+
18
+ With `--fix`:
19
+ - Removes stale registry entries (registered but not deployed)
20
+ - Removes stale hook paths from `~/.claude/settings.json` (missing files, `/tmp/` paths)
21
+
22
+ ### ldm status
23
+
24
+ Quick overview: version, install date, extension count, extension list with versions.
25
+
26
+ ### ldm updates
27
+
28
+ Shows cached update check results. Use `--check` to re-scan npm for newer versions of installed extensions.
29
+
30
+ ## State Detection
31
+
32
+ `lib/state.mjs` scans the real system:
33
+
34
+ | Source | What it finds |
35
+ |--------|--------------|
36
+ | `~/.claude.json` | MCP servers (user scope) |
37
+ | `~/.ldm/extensions/` | Deployed extensions (LDM) |
38
+ | `~/.openclaw/extensions/` | Deployed extensions (OpenClaw) |
39
+ | `~/.ldm/extensions/registry.json` | Registry metadata |
40
+ | PATH | CLI binaries (with 5s timeout per binary) |
41
+ | `~/.openclaw/skills/` | Deployed skills |
42
+
43
+ Reconciliation compares all sources and determines status:
44
+ - `healthy`: registered + deployed + source available
45
+ - `installed-unlinked`: registered + deployed, no source repo
46
+ - `registered-missing`: in registry but not deployed
47
+ - `deployed-unregistered`: deployed but not in registry
48
+ - `mcp-only`: MCP server without LDM management
49
+
50
+ ## Key Files
51
+
52
+ | File | What |
53
+ |------|------|
54
+ | `bin/ldm.js` | `cmdDoctor()`, `cmdStatus()`, `cmdUpdates()` |
55
+ | `lib/state.mjs` | `detectSystemState()`, `reconcileState()`, `formatReconciliation()` |
56
+ | `lib/updates.mjs` | npm version checking, manifest caching |
@@ -243,6 +243,49 @@ The `ai/` folder is the development process. It is not part of the published pro
243
243
 
244
244
  **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.
245
245
 
246
+ ## Catalog
247
+
248
+ Skills are defined in `catalog.json` at the LDM OS root. Each entry has:
249
+
250
+ ```json
251
+ {
252
+ "id": "memory-crystal",
253
+ "name": "Memory Crystal",
254
+ "description": "Persistent memory for your AI.",
255
+ "npm": "@wipcomputer/memory-crystal",
256
+ "repo": "wipcomputer/memory-crystal",
257
+ "registryMatches": ["memory-crystal"],
258
+ "cliMatches": ["crystal"],
259
+ "recommended": true,
260
+ "status": "stable"
261
+ }
262
+ ```
263
+
264
+ ## Stacks
265
+
266
+ Stacks group skills for team installs. Defined in `catalog.json`:
267
+
268
+ ```json
269
+ {
270
+ "stacks": {
271
+ "core": {
272
+ "name": "WIP Core",
273
+ "components": ["memory-crystal", "wip-ai-devops-toolbox", "wip-1password", "wip-markdown-viewer"],
274
+ "mcpServers": []
275
+ },
276
+ "web": {
277
+ "name": "Web Development",
278
+ "components": [],
279
+ "mcpServers": [
280
+ { "name": "playwright", "command": "npx", "args": ["-y", "@playwright/mcp@latest"] }
281
+ ]
282
+ }
283
+ }
284
+ }
285
+ ```
286
+
287
+ Stacks are composable via the `includes` field.
288
+
246
289
  ## The Installer
247
290
 
248
291
  `ldm install` scans any repo, detects which interfaces exist, and installs them all. One command.
package/lib/deploy.mjs CHANGED
@@ -682,10 +682,18 @@ export function installSingleTool(toolPath) {
682
682
  }
683
683
 
684
684
  let installed = 0;
685
+ // Don't store /tmp/ clone paths as source (#54). Use the repo URL from package.json if available.
686
+ let source = toolPath;
687
+ const isTmpPath = toolPath.startsWith('/tmp/') || toolPath.startsWith('/private/tmp/');
688
+ if (isTmpPath && pkg?.repository?.url) {
689
+ source = pkg.repository.url.replace(/^git\+/, '').replace(/\.git$/, '');
690
+ } else if (isTmpPath) {
691
+ source = null; // better than a /tmp/ path
692
+ }
685
693
  const registryInfo = {
686
694
  name: toolName,
687
695
  version: pkg?.version || 'unknown',
688
- source: toolPath,
696
+ source,
689
697
  interfaces: ifaceNames,
690
698
  };
691
699
 
package/lib/state.mjs CHANGED
@@ -89,11 +89,11 @@ function detectCLIBinaries() {
89
89
  const binaries = {};
90
90
  for (const bin of knownBins) {
91
91
  try {
92
- const path = execSync(`which ${bin} 2>/dev/null`, { encoding: 'utf8' }).trim();
92
+ const path = execSync(`which ${bin} 2>/dev/null`, { encoding: 'utf8', timeout: 5000 }).trim();
93
93
  if (path) {
94
94
  binaries[bin] = { path };
95
95
  try {
96
- const ver = execSync(`${bin} --version 2>/dev/null`, { encoding: 'utf8' }).trim().split('\n')[0];
96
+ const ver = execSync(`${bin} --version 2>/dev/null`, { encoding: 'utf8', timeout: 5000 }).trim().split('\n')[0];
97
97
  binaries[bin].version = ver;
98
98
  } catch {}
99
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
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",
package/src/bridge/cli.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // lesa-bridge/cli.ts: CLI interface.
2
+ // wip-bridge/cli.ts: CLI interface.
3
3
  // lesa send "message", lesa inbox, lesa search "query", lesa read <file>
4
4
 
5
5
  import { existsSync, statSync } from "node:fs";
@@ -17,7 +17,7 @@ import {
17
17
  const config = resolveConfig();
18
18
 
19
19
  function usage(): void {
20
- console.log(`lesa-bridge: Claude Code CLI ↔ OpenClaw TUI agent bridge
20
+ console.log(`wip-bridge: Claude Code CLI ↔ OpenClaw TUI agent bridge
21
21
 
22
22
  Usage:
23
23
  lesa send <message> Send a message to the OpenClaw agent
@@ -130,7 +130,7 @@ async function main(): Promise<void> {
130
130
  }
131
131
 
132
132
  case "status": {
133
- console.log(`lesa-bridge status`);
133
+ console.log(`wip-bridge status`);
134
134
  console.log(` OpenClaw dir: ${config.openclawDir}`);
135
135
  console.log(` Workspace: ${config.workspaceDir}`);
136
136
  console.log(` Database: ${config.dbPath}`);
@@ -140,7 +140,7 @@ async function main(): Promise<void> {
140
140
  }
141
141
 
142
142
  case "diagnose": {
143
- console.log("lesa-bridge diagnose\n");
143
+ console.log("wip-bridge diagnose\n");
144
144
  let issues = 0;
145
145
 
146
146
  // 1. OpenClaw dir
@@ -1,4 +1,4 @@
1
- // lesa-bridge/core.ts: Pure logic. Zero framework deps.
1
+ // wip-bridge/core.ts: Pure logic. Zero framework deps.
2
2
  // Handles messaging, memory search, and workspace access for OpenClaw agents.
3
3
 
4
4
  import { execSync, exec } from "node:child_process";
@@ -1,4 +1,4 @@
1
- // lesa-bridge/mcp-server.ts: MCP server wrapping core.
1
+ // wip-bridge/mcp-server.ts: MCP server wrapping core.
2
2
  // Thin layer: registers tools, starts inbox HTTP server, connects transport.
3
3
 
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -73,12 +73,12 @@ function startInboxServer(cfg: BridgeConfig): void {
73
73
  timestamp: new Date().toISOString(),
74
74
  };
75
75
  const queued = pushInbox(msg);
76
- console.error(`lesa-bridge inbox: message from ${msg.from}`);
76
+ console.error(`wip-bridge inbox: message from ${msg.from}`);
77
77
 
78
78
  try {
79
79
  server.sendLoggingMessage({
80
80
  level: "info",
81
- logger: "lesa-bridge",
81
+ logger: "wip-bridge",
82
82
  data: `[OpenClaw → Claude Code] ${msg.from}: ${msg.message}`,
83
83
  });
84
84
  } catch {}
@@ -103,18 +103,18 @@ function startInboxServer(cfg: BridgeConfig): void {
103
103
  });
104
104
 
105
105
  httpServer.listen(cfg.inboxPort, "127.0.0.1", () => {
106
- console.error(`lesa-bridge inbox listening on 127.0.0.1:${cfg.inboxPort}`);
106
+ console.error(`wip-bridge inbox listening on 127.0.0.1:${cfg.inboxPort}`);
107
107
  });
108
108
 
109
109
  httpServer.on("error", (err: Error) => {
110
- console.error(`lesa-bridge inbox server error: ${err.message}`);
110
+ console.error(`wip-bridge inbox server error: ${err.message}`);
111
111
  });
112
112
  }
113
113
 
114
114
  // ── MCP Server ───────────────────────────────────────────────────────
115
115
 
116
116
  const server = new McpServer({
117
- name: "lesa-bridge",
117
+ name: "wip-bridge",
118
118
  version: "0.3.0",
119
119
  });
120
120
 
@@ -344,7 +344,7 @@ function registerSkillTools(skills: SkillInfo[]): void {
344
344
  }
345
345
  );
346
346
 
347
- console.error(`lesa-bridge: registered ${executableSkills.length} skill tools + oc_skills_list (${skills.length} total skills)`);
347
+ console.error(`wip-bridge: registered ${executableSkills.length} skill tools + oc_skills_list (${skills.length} total skills)`);
348
348
  }
349
349
 
350
350
  // ── Start ────────────────────────────────────────────────────────────
@@ -357,12 +357,12 @@ async function main() {
357
357
  const skills = discoverSkills(config.openclawDir);
358
358
  registerSkillTools(skills);
359
359
  } catch (err: any) {
360
- console.error(`lesa-bridge: skill discovery failed: ${err.message}`);
360
+ console.error(`wip-bridge: skill discovery failed: ${err.message}`);
361
361
  }
362
362
 
363
363
  const transport = new StdioServerTransport();
364
364
  await server.connect(transport);
365
- console.error(`lesa-bridge MCP server running (openclaw: ${config.openclawDir})`);
365
+ console.error(`wip-bridge MCP server running (openclaw: ${config.openclawDir})`);
366
366
  }
367
367
 
368
368
  main().catch((error) => {
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+ # LDM OS: Global pre-commit hook
3
+ # Blocks commits on main/master. Use a branch or worktree.
4
+ # Installed by: ldm init
5
+ # Location: ~/.ldm/hooks/pre-commit
6
+ # Activated by: git config --global core.hooksPath ~/.ldm/hooks
7
+
8
+ branch=$(git branch --show-current 2>/dev/null)
9
+
10
+ if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
11
+ echo ""
12
+ echo " BLOCKED: Cannot commit on $branch."
13
+ echo " Create a branch first: git checkout -b cc-mini/your-feature"
14
+ echo " Or use a worktree: git worktree add /tmp/wt-name -b branch-name"
15
+ echo ""
16
+ echo " To bypass (emergencies only): git commit --no-verify"
17
+ echo ""
18
+ exit 1
19
+ fi