@wipcomputer/wip-ldm-os 0.4.73-alpha.9 → 0.4.75-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +52 -0
- package/SKILL.md +8 -1
- package/bin/ldm.js +600 -81
- package/dist/bridge/chunk-3RG5ZIWI.js +10 -0
- package/dist/bridge/{chunk-LF7EMFBY.js → chunk-7NH6JBIO.js} +127 -49
- package/dist/bridge/cli.js +2 -1
- package/dist/bridge/core.d.ts +13 -1
- package/dist/bridge/core.js +4 -1
- package/dist/bridge/mcp-server.js +52 -7
- package/dist/bridge/openclaw.d.ts +5 -0
- package/dist/bridge/openclaw.js +11 -0
- package/docs/bridge/TECHNICAL.md +86 -0
- package/docs/doc-pipeline/README.md +74 -0
- package/docs/doc-pipeline/TECHNICAL.md +79 -0
- package/lib/deploy.mjs +175 -13
- package/lib/detect.mjs +20 -6
- package/package.json +2 -2
- package/shared/docs/README.md.tmpl +2 -2
- package/shared/docs/dev-guide-wipcomputerinc.md.tmpl +378 -0
- package/shared/docs/how-releases-work.md.tmpl +3 -1
- package/shared/docs/how-worktrees-work.md.tmpl +12 -7
- package/shared/rules/git-conventions.md +3 -3
- package/shared/rules/release-pipeline.md +1 -1
- package/shared/rules/security.md +1 -1
- package/shared/rules/workspace-boundaries.md +1 -1
- package/shared/rules/writing-style.md +1 -1
- package/shared/templates/claude-md-level1.md +7 -3
- package/src/bridge/core.ts +160 -56
- package/src/bridge/mcp-server.ts +93 -8
- package/src/bridge/openclaw.ts +14 -0
- package/src/hooks/inbox-check-hook.mjs +232 -0
- package/src/hooks/inbox-rewake-hook.mjs +388 -0
- package/src/hosted-mcp/.env.example +3 -0
- package/src/hosted-mcp/demo/agent.html +300 -0
- package/src/hosted-mcp/demo/agent.txt +84 -0
- package/src/hosted-mcp/demo/fallback.jpg +0 -0
- package/src/hosted-mcp/demo/footer.js +74 -0
- package/src/hosted-mcp/demo/index.html +1303 -0
- package/src/hosted-mcp/demo/login.html +548 -0
- package/src/hosted-mcp/demo/privacy.html +223 -0
- package/src/hosted-mcp/demo/sprites.jpg +0 -0
- package/src/hosted-mcp/demo/sprites.png +0 -0
- package/src/hosted-mcp/demo/tos.html +198 -0
- package/src/hosted-mcp/deploy.sh +70 -0
- package/src/hosted-mcp/ecosystem.config.cjs +14 -0
- package/src/hosted-mcp/inbox.mjs +64 -0
- package/src/hosted-mcp/legal/internet-services/terms/site.html +205 -0
- package/src/hosted-mcp/legal/privacy/en-ww/index.html +230 -0
- package/src/hosted-mcp/nginx/mcp-oauth.conf +98 -0
- package/src/hosted-mcp/nginx/mcp-server.conf +17 -0
- package/src/hosted-mcp/nginx/wip.computer.conf +45 -0
- package/src/hosted-mcp/package-lock.json +2092 -0
- package/src/hosted-mcp/package.json +23 -0
- package/src/hosted-mcp/prisma/migrations/20260406233014_init/migration.sql +68 -0
- package/src/hosted-mcp/prisma/migrations/migration_lock.toml +3 -0
- package/src/hosted-mcp/prisma/schema.prisma +57 -0
- package/src/hosted-mcp/prisma.config.ts +14 -0
- package/src/hosted-mcp/server.mjs +2093 -0
- package/src/hosted-mcp/shared/kaleidoscope.css +139 -0
- package/src/hosted-mcp/shared/kaleidoscope.js +192 -0
- package/src/hosted-mcp/tools.mjs +73 -0
- package/templates/hooks/pre-commit +5 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Documentation Pipeline: Technical Details
|
|
2
|
+
|
|
3
|
+
## File Paths
|
|
4
|
+
|
|
5
|
+
### Repo doc templates (in the LDM OS repo)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
shared/docs/*.md.tmpl Templates for home docs
|
|
9
|
+
shared/rules/*.md Source for agent rules
|
|
10
|
+
shared/boot/ Boot sequence config
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Templates use placeholders from `~/.ldm/config.json` (workspace path, agent names, org name, etc.).
|
|
14
|
+
|
|
15
|
+
### Installed locations
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
~/.ldm/config.json Org config (agents, paths, co-authors)
|
|
19
|
+
~/.ldm/shared/rules/*.md Agent rules (source for ~/.claude/rules/)
|
|
20
|
+
~/.ldm/shared/dev-guide-*.md Org dev guide
|
|
21
|
+
~/.ldm/shared/boot/ Boot sequence config
|
|
22
|
+
~/.ldm/shared/prompts/ Cron prompts
|
|
23
|
+
~/.ldm/templates/ CLAUDE.md templates, install prompt, etc.
|
|
24
|
+
~/wipcomputerinc/library/documentation/ Personalized human docs (from templates)
|
|
25
|
+
~/.claude/rules/ Deployed rules (copied from ~/.ldm/shared/rules/)
|
|
26
|
+
~/.claude/CLAUDE.md Level 1 global (generated from template + config)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### What ldm install deploys
|
|
30
|
+
|
|
31
|
+
| Source | Destination | When |
|
|
32
|
+
|--------|------------|------|
|
|
33
|
+
| `shared/rules/*.md` | `~/.ldm/shared/rules/` then `~/.claude/rules/` | Every install |
|
|
34
|
+
| `shared/docs/*.md.tmpl` | `~/wipcomputerinc/library/documentation/` | Every install |
|
|
35
|
+
| `shared/boot/` | `~/.ldm/shared/boot/` | Every install |
|
|
36
|
+
| `shared/prompts/` | `~/.ldm/shared/prompts/` | Every install |
|
|
37
|
+
| `shared/templates/` | `~/.ldm/templates/` | Every install |
|
|
38
|
+
|
|
39
|
+
**Known bug (April 2026):** The installer currently deploys shared rules on `ldm init` (first install) but not on every `ldm install`. This means rule updates in new versions don't propagate until the user re-initializes. This needs to be fixed so rules deploy on every install.
|
|
40
|
+
|
|
41
|
+
**Known bug (April 2026):** The installer previously deployed home docs to `settings/docs/`. This path was renamed to `library/documentation/` on March 28, 2026. The installer must deploy to the correct path.
|
|
42
|
+
|
|
43
|
+
## How templates work
|
|
44
|
+
|
|
45
|
+
Home doc templates at `shared/docs/*.md.tmpl` contain placeholders:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Workspace: {{workspace}}
|
|
49
|
+
Agents: {{agents}}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The installer reads `~/.ldm/config.json`, substitutes the placeholders, and writes the personalized docs to `~/wipcomputerinc/library/documentation/`.
|
|
53
|
+
|
|
54
|
+
## CLAUDE.md cascade
|
|
55
|
+
|
|
56
|
+
Three levels. Claude Code reads all of them, walking up from CWD:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Level 1: ~/.claude/CLAUDE.md Global. ~30 lines. Universal rules.
|
|
60
|
+
Level 2: ~/wipcomputerinc/CLAUDE.md Workspace. ~150 lines. Org context.
|
|
61
|
+
Level 3: <repo>/CLAUDE.md Per-repo. ~50-86 lines. Repo-specific.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
After context compaction, CWD may shift to a repo. Without Level 3, all context is lost. Every repo must have a CLAUDE.md.
|
|
65
|
+
|
|
66
|
+
## Config paths (correct as of April 2026)
|
|
67
|
+
|
|
68
|
+
References in CLAUDE.md and rules must use absolute paths:
|
|
69
|
+
|
|
70
|
+
| Reference | Correct path |
|
|
71
|
+
|-----------|-------------|
|
|
72
|
+
| Org config | `~/.ldm/config.json` |
|
|
73
|
+
| Dev guide | `~/.ldm/shared/dev-guide-wipcomputerinc.md` |
|
|
74
|
+
| Human docs | `~/wipcomputerinc/library/documentation/` |
|
|
75
|
+
| Agent rules | `~/.ldm/shared/rules/` (source) or `~/.claude/rules/` (deployed) |
|
|
76
|
+
| Workspace CLAUDE.md | `~/wipcomputerinc/CLAUDE.md` |
|
|
77
|
+
| Global CLAUDE.md | `~/.claude/CLAUDE.md` |
|
|
78
|
+
|
|
79
|
+
NOT `settings/config.json`. NOT `settings/docs/`. Those paths are from before the March 28 rename.
|
package/lib/deploy.mjs
CHANGED
|
@@ -394,14 +394,38 @@ function runBuildIfNeeded(repoPath) {
|
|
|
394
394
|
|
|
395
395
|
function compareSemver(a, b) {
|
|
396
396
|
if (!a || !b) return 0;
|
|
397
|
-
|
|
398
|
-
const
|
|
397
|
+
if (a === b) return 0;
|
|
398
|
+
const [aBase, aPre] = a.split('-', 2);
|
|
399
|
+
const [bBase, bPre] = b.split('-', 2);
|
|
400
|
+
const pa = aBase.split('.').map(Number);
|
|
401
|
+
const pb = bBase.split('.').map(Number);
|
|
399
402
|
for (let i = 0; i < 3; i++) {
|
|
400
403
|
const na = pa[i] || 0;
|
|
401
404
|
const nb = pb[i] || 0;
|
|
402
405
|
if (na > nb) return 1;
|
|
403
406
|
if (na < nb) return -1;
|
|
404
407
|
}
|
|
408
|
+
// Base versions equal. Stable > prerelease.
|
|
409
|
+
if (!aPre && bPre) return 1; // a is stable, b is prerelease -> a is newer
|
|
410
|
+
if (aPre && !bPre) return -1; // a is prerelease, b is stable -> b is newer
|
|
411
|
+
// Both have prereleases. Compare segments.
|
|
412
|
+
if (aPre && bPre) {
|
|
413
|
+
const aSegs = aPre.split('.');
|
|
414
|
+
const bSegs = bPre.split('.');
|
|
415
|
+
for (let i = 0; i < Math.max(aSegs.length, bSegs.length); i++) {
|
|
416
|
+
const as = aSegs[i] || '';
|
|
417
|
+
const bs = bSegs[i] || '';
|
|
418
|
+
const an = Number(as);
|
|
419
|
+
const bn = Number(bs);
|
|
420
|
+
if (!isNaN(an) && !isNaN(bn)) {
|
|
421
|
+
if (an > bn) return 1;
|
|
422
|
+
if (an < bn) return -1;
|
|
423
|
+
} else {
|
|
424
|
+
if (as > bs) return 1;
|
|
425
|
+
if (as < bs) return -1;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
405
429
|
return 0;
|
|
406
430
|
}
|
|
407
431
|
|
|
@@ -508,6 +532,81 @@ function safeDeployDir(repoPath, destDir, name) {
|
|
|
508
532
|
}
|
|
509
533
|
}
|
|
510
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Update tools.allow in openclaw.json to include a newly deployed plugin.
|
|
537
|
+
* OpenClaw 2026.4.8+ enforces tools.allow as an exclusive allowlist.
|
|
538
|
+
* Without this, newly installed plugins are blocked from running.
|
|
539
|
+
*
|
|
540
|
+
* This function MUST remain at module top level. Nesting it inside another
|
|
541
|
+
* function puts it out of scope for its call sites in the install handlers
|
|
542
|
+
* and produces a ReferenceError at runtime. See:
|
|
543
|
+
* ai/product/bugs/installer/2026-04-11--cc-mini--update-tools-allow-reference-error.md
|
|
544
|
+
*/
|
|
545
|
+
function updateToolsAllow(pluginName) {
|
|
546
|
+
const ocConfigPath = join(OC_ROOT, 'openclaw.json');
|
|
547
|
+
if (!existsSync(ocConfigPath)) return;
|
|
548
|
+
try {
|
|
549
|
+
const raw = readFileSync(ocConfigPath, 'utf8');
|
|
550
|
+
const config = JSON.parse(raw);
|
|
551
|
+
if (!config.tools?.allow || !Array.isArray(config.tools.allow)) return;
|
|
552
|
+
if (config.tools.allow.includes(pluginName)) return;
|
|
553
|
+
config.tools.allow.push(pluginName);
|
|
554
|
+
writeFileSync(ocConfigPath, JSON.stringify(config, null, 2) + '\n');
|
|
555
|
+
log(`Added "${pluginName}" to openclaw.json tools.allow`);
|
|
556
|
+
} catch (e) {
|
|
557
|
+
log(`Warning: failed to update tools.allow for ${pluginName}: ${e.message}`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Reconcile tools.allow against plugins.entries in ~/.openclaw/openclaw.json.
|
|
563
|
+
*
|
|
564
|
+
* In OpenClaw 2026.4.8+, any plugin registered in plugins.entries but missing
|
|
565
|
+
* from tools.allow is silently blocked at runtime. Each blocked tool call
|
|
566
|
+
* spawns a root-key approval prompt to the user, flooding iMessage with
|
|
567
|
+
* approve-ids. This was observed on 2026-04-11 for model-provider plugins
|
|
568
|
+
* (anthropic, openai, xai) and imessage, which were enabled in plugins.entries
|
|
569
|
+
* but never added to tools.allow, because the per-plugin updateToolsAllow path
|
|
570
|
+
* only runs during new plugin deploys and the alpha.27/28 ReferenceError had
|
|
571
|
+
* silently dropped those entries anyway.
|
|
572
|
+
*
|
|
573
|
+
* This function is the self-healing step: at install time, walk plugins.entries,
|
|
574
|
+
* find any enabled plugin whose name is not already in tools.allow, and add it.
|
|
575
|
+
* Idempotent. Disabled plugins are skipped. Runs at both ends of installFromPath
|
|
576
|
+
* so a single `ldm install` repairs existing broken state without requiring a
|
|
577
|
+
* new plugin deploy.
|
|
578
|
+
*
|
|
579
|
+
* Background:
|
|
580
|
+
* ai/product/bugs/code-fka-devopstoolkit/2026-04-11--cc-mini--update-tools-allow-reference-error.md
|
|
581
|
+
*
|
|
582
|
+
* This function MUST remain at module top level, same as updateToolsAllow.
|
|
583
|
+
*/
|
|
584
|
+
function reconcileToolsAllow() {
|
|
585
|
+
const ocConfigPath = join(OC_ROOT, 'openclaw.json');
|
|
586
|
+
if (!existsSync(ocConfigPath)) return;
|
|
587
|
+
try {
|
|
588
|
+
const raw = readFileSync(ocConfigPath, 'utf8');
|
|
589
|
+
const config = JSON.parse(raw);
|
|
590
|
+
if (!config.plugins?.entries || typeof config.plugins.entries !== 'object') return;
|
|
591
|
+
if (!config.tools?.allow || !Array.isArray(config.tools.allow)) return;
|
|
592
|
+
|
|
593
|
+
const enabledPlugins = Object.entries(config.plugins.entries)
|
|
594
|
+
.filter(([, entry]) => entry && entry.enabled !== false)
|
|
595
|
+
.map(([name]) => name);
|
|
596
|
+
|
|
597
|
+
const allow = config.tools.allow;
|
|
598
|
+
const missing = enabledPlugins.filter(name => !allow.includes(name));
|
|
599
|
+
|
|
600
|
+
if (missing.length === 0) return;
|
|
601
|
+
|
|
602
|
+
for (const name of missing) allow.push(name);
|
|
603
|
+
writeFileSync(ocConfigPath, JSON.stringify(config, null, 2) + '\n');
|
|
604
|
+
log(`Reconciled openclaw.json tools.allow: added ${missing.join(', ')}`);
|
|
605
|
+
} catch (e) {
|
|
606
|
+
log(`Warning: failed to reconcile tools.allow: ${e.message}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
511
610
|
// ── OpenClaw plugin naming (fix #8) ──
|
|
512
611
|
|
|
513
612
|
function resolveOcPluginName(repoPath, toolName) {
|
|
@@ -768,7 +867,6 @@ function registerMCP(repoPath, door, toolName) {
|
|
|
768
867
|
const envFlag = existsSync(OC_ROOT) ? ` -e OPENCLAW_HOME="${OC_ROOT}"` : '';
|
|
769
868
|
execSync(`claude mcp add --scope user ${name}${envFlag} -- node "${mcpPath}"`, { stdio: 'pipe' });
|
|
770
869
|
ok(`MCP: registered ${name} at user scope`);
|
|
771
|
-
return true;
|
|
772
870
|
} catch (e) {
|
|
773
871
|
// Fallback: write to ~/.claude.json directly
|
|
774
872
|
try {
|
|
@@ -780,15 +878,53 @@ function registerMCP(repoPath, door, toolName) {
|
|
|
780
878
|
};
|
|
781
879
|
writeJSON(ccUserPath, mcpConfig);
|
|
782
880
|
ok(`MCP: registered ${name} in ~/.claude.json (fallback)`);
|
|
783
|
-
return true;
|
|
784
881
|
} catch (e2) {
|
|
785
882
|
fail(`MCP: registration failed. ${e.message}`);
|
|
786
883
|
return false;
|
|
787
884
|
}
|
|
788
885
|
}
|
|
886
|
+
|
|
887
|
+
// Also register with OpenClaw so the MCP tools are available to all
|
|
888
|
+
// OpenClaw agents (e.g. Lēsa) without exec-approval gates. CC
|
|
889
|
+
// registration alone only gives tools to Claude Code sessions, not
|
|
890
|
+
// to OpenClaw's agent pipeline. Discovered 2026-04-11 when Lēsa
|
|
891
|
+
// lost xAI image gen tools after switching from Grok to Claude CLI.
|
|
892
|
+
try {
|
|
893
|
+
const ocMcpConfig = JSON.stringify({ command: 'node', args: [mcpPath] });
|
|
894
|
+
execSync(`openclaw mcp set ${name} '${ocMcpConfig}'`, { stdio: 'pipe' });
|
|
895
|
+
ok(`MCP: registered ${name} with OpenClaw`);
|
|
896
|
+
} catch {
|
|
897
|
+
// Non-fatal: OpenClaw may not be installed on all machines
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Add to OpenClaw tools.allow so the MCP tools are pre-authorized
|
|
901
|
+
updateToolsAllow(name);
|
|
902
|
+
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Install Claude Code hook(s) for an extension.
|
|
908
|
+
*
|
|
909
|
+
* Accepts either a single door object (legacy) or an array of door objects
|
|
910
|
+
* (new in 2026-04-05 for wip-branch-guard 1.9.73 which registers on both
|
|
911
|
+
* PreToolUse and SessionStart). Normalizes to an array and installs each
|
|
912
|
+
* door independently.
|
|
913
|
+
*
|
|
914
|
+
* Returns true if at least one door installed successfully.
|
|
915
|
+
*/
|
|
916
|
+
function installClaudeCodeHook(repoPath, doorOrDoors) {
|
|
917
|
+
const doors = Array.isArray(doorOrDoors) ? doorOrDoors : [doorOrDoors];
|
|
918
|
+
let anyOk = false;
|
|
919
|
+
for (const door of doors) {
|
|
920
|
+
if (installClaudeCodeHookEvent(repoPath, door)) {
|
|
921
|
+
anyOk = true;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return anyOk;
|
|
789
925
|
}
|
|
790
926
|
|
|
791
|
-
function
|
|
927
|
+
function installClaudeCodeHookEvent(repoPath, door) {
|
|
792
928
|
const settingsPath = join(HOME, '.claude', 'settings.json');
|
|
793
929
|
let settings = readJSON(settingsPath);
|
|
794
930
|
|
|
@@ -802,6 +938,8 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
802
938
|
const installedGuard = join(extDir, 'guard.mjs');
|
|
803
939
|
|
|
804
940
|
// Deploy guard.mjs to ~/.ldm/extensions/{toolName}/ (#85: always update, not just when missing)
|
|
941
|
+
// Idempotent across multi-door invocations: two doors on the same repo
|
|
942
|
+
// will both trigger this copy, which is a filesystem no-op after the first.
|
|
805
943
|
const srcGuard = join(repoPath, 'guard.mjs');
|
|
806
944
|
if (existsSync(srcGuard)) {
|
|
807
945
|
try {
|
|
@@ -819,21 +957,30 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
819
957
|
? `node ${installedGuard}`
|
|
820
958
|
: (door.command || `node "${srcGuard}"`);
|
|
821
959
|
|
|
960
|
+
const event = door.event || 'PreToolUse';
|
|
961
|
+
|
|
822
962
|
if (DRY_RUN) {
|
|
823
|
-
ok(`Claude Code: would add ${
|
|
963
|
+
ok(`Claude Code: would add ${event} hook (dry run)`);
|
|
824
964
|
return true;
|
|
825
965
|
}
|
|
826
966
|
|
|
827
967
|
if (!settings.hooks) settings.hooks = {};
|
|
828
|
-
const event = door.event || 'PreToolUse';
|
|
829
968
|
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
830
969
|
|
|
831
|
-
|
|
832
|
-
|
|
970
|
+
// Match existing entries by the guard command path + the matcher, so that
|
|
971
|
+
// a single extension registering on multiple events (each with its own
|
|
972
|
+
// matcher) creates one entry per event rather than all entries colliding
|
|
973
|
+
// on the same hook slot. Before this change the existing-entry check was
|
|
974
|
+
// per-extension, not per-extension-per-event.
|
|
975
|
+
const doorMatcher = door.matcher || undefined;
|
|
976
|
+
const existingIdx = settings.hooks[event].findIndex(entry => {
|
|
977
|
+
const sameMatcher = (entry.matcher || undefined) === doorMatcher;
|
|
978
|
+
if (!sameMatcher) return false;
|
|
979
|
+
return entry.hooks?.some(h => {
|
|
833
980
|
const cmd = h.command || '';
|
|
834
981
|
return cmd.includes(`/${toolName}/`) || cmd === hookCommand;
|
|
835
|
-
})
|
|
836
|
-
);
|
|
982
|
+
});
|
|
983
|
+
});
|
|
837
984
|
|
|
838
985
|
if (existingIdx !== -1) {
|
|
839
986
|
const existingCmd = settings.hooks[event][existingIdx].hooks?.[0]?.command || '';
|
|
@@ -854,7 +1001,7 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
854
1001
|
}
|
|
855
1002
|
|
|
856
1003
|
settings.hooks[event].push({
|
|
857
|
-
matcher:
|
|
1004
|
+
matcher: doorMatcher,
|
|
858
1005
|
hooks: [{
|
|
859
1006
|
type: 'command',
|
|
860
1007
|
command: hookCommand,
|
|
@@ -1016,6 +1163,8 @@ export function installSingleTool(toolPath) {
|
|
|
1016
1163
|
installed++;
|
|
1017
1164
|
registryInfo.ldmPath = join(LDM_EXTENSIONS, toolName);
|
|
1018
1165
|
registryInfo.ocPath = join(OC_EXTENSIONS, toolName);
|
|
1166
|
+
// Update tools.allow in openclaw.json so OC 2026.4.8+ doesn't block the plugin
|
|
1167
|
+
updateToolsAllow(toolName);
|
|
1019
1168
|
}
|
|
1020
1169
|
} else if (interfaces.mcp) {
|
|
1021
1170
|
const extName = basename(toolPath);
|
|
@@ -1115,10 +1264,19 @@ export function installToolbox(repoPath) {
|
|
|
1115
1264
|
// ── Full install pipeline ──
|
|
1116
1265
|
|
|
1117
1266
|
export async function installFromPath(repoPath) {
|
|
1267
|
+
// Heal tools.allow before any install runs, so the current session picks up
|
|
1268
|
+
// any drift left by earlier broken installs (alpha.27/28 ReferenceError).
|
|
1269
|
+
// Idempotent: no-op if plugins.entries and tools.allow are already in sync.
|
|
1270
|
+
reconcileToolsAllow();
|
|
1271
|
+
|
|
1118
1272
|
const subTools = detectToolbox(repoPath);
|
|
1119
1273
|
|
|
1120
1274
|
if (subTools.length > 0) {
|
|
1121
|
-
|
|
1275
|
+
const result = installToolbox(repoPath);
|
|
1276
|
+
// Heal again after toolbox install in case any plugin was newly registered
|
|
1277
|
+
// in plugins.entries but never added to tools.allow by its deploy path.
|
|
1278
|
+
reconcileToolsAllow();
|
|
1279
|
+
return result;
|
|
1122
1280
|
}
|
|
1123
1281
|
|
|
1124
1282
|
const installed = installSingleTool(repoPath);
|
|
@@ -1135,6 +1293,10 @@ export async function installFromPath(repoPath) {
|
|
|
1135
1293
|
console.log('');
|
|
1136
1294
|
}
|
|
1137
1295
|
|
|
1296
|
+
// Final reconcile pass after single-tool install, for the same reason as
|
|
1297
|
+
// the toolbox branch above.
|
|
1298
|
+
reconcileToolsAllow();
|
|
1299
|
+
|
|
1138
1300
|
return { tools: 1, interfaces: installed };
|
|
1139
1301
|
}
|
|
1140
1302
|
|
package/lib/detect.mjs
CHANGED
|
@@ -53,16 +53,27 @@ export function detectInterfaces(repoPath) {
|
|
|
53
53
|
interfaces.skill = { path: join(repoPath, 'SKILL.md') };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// 6. Claude Code Hook: guard.mjs or claudeCode.hook in package.json
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// 6. Claude Code Hook: guard.mjs or claudeCode.hook(s) in package.json
|
|
57
|
+
//
|
|
58
|
+
// Supports three shapes:
|
|
59
|
+
// - Legacy singular: pkg.claudeCode.hook = { event, matcher, ... }
|
|
60
|
+
// - New plural array: pkg.claudeCode.hooks = [{ event, matcher, ... }, ...]
|
|
61
|
+
// (one extension can register on multiple events, e.g. both PreToolUse
|
|
62
|
+
// and SessionStart. Added 2026-04-05 for wip-branch-guard 1.9.73.)
|
|
63
|
+
// - Implicit: a bare guard.mjs file with no package.json declaration.
|
|
64
|
+
//
|
|
65
|
+
// Normalized to an array internally so deploy.mjs has one code path.
|
|
66
|
+
if (Array.isArray(pkg?.claudeCode?.hooks)) {
|
|
67
|
+
interfaces.claudeCodeHook = pkg.claudeCode.hooks;
|
|
68
|
+
} else if (pkg?.claudeCode?.hook) {
|
|
69
|
+
interfaces.claudeCodeHook = [pkg.claudeCode.hook];
|
|
59
70
|
} else if (existsSync(join(repoPath, 'guard.mjs'))) {
|
|
60
|
-
interfaces.claudeCodeHook = {
|
|
71
|
+
interfaces.claudeCodeHook = [{
|
|
61
72
|
event: 'PreToolUse',
|
|
62
73
|
matcher: 'Edit|Write',
|
|
63
74
|
command: `node "${join(repoPath, 'guard.mjs')}"`,
|
|
64
75
|
timeout: 5,
|
|
65
|
-
};
|
|
76
|
+
}];
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
// 7. Claude Code Plugin: .claude-plugin/plugin.json
|
|
@@ -93,7 +104,10 @@ export function describeInterfaces(interfaces) {
|
|
|
93
104
|
if (interfaces.mcp) lines.push(`MCP Server: ${interfaces.mcp.file}`);
|
|
94
105
|
if (interfaces.openclaw) lines.push(`OpenClaw Plugin: ${interfaces.openclaw.config?.name || 'detected'}`);
|
|
95
106
|
if (interfaces.skill) lines.push(`Skill: SKILL.md`);
|
|
96
|
-
if (interfaces.claudeCodeHook)
|
|
107
|
+
if (interfaces.claudeCodeHook) {
|
|
108
|
+
const events = interfaces.claudeCodeHook.map(h => h.event || 'PreToolUse');
|
|
109
|
+
lines.push(`Claude Code Hook: ${events.join(', ')}`);
|
|
110
|
+
}
|
|
97
111
|
if (interfaces.claudeCodePlugin) lines.push(`Claude Code Plugin: ${interfaces.claudeCodePlugin.manifest?.name || 'detected'}`);
|
|
98
112
|
|
|
99
113
|
return `${names.length} interface(s): ${names.join(', ')}\n${lines.map(l => ` ${l}`).join('\n')}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/wip-ldm-os",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.75-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
|
|
6
6
|
"engines": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"lesa": "dist/bridge/cli.js"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build:bridge": "cd src/bridge && npm install && npx tsup core.ts mcp-server.ts cli.ts --format esm --dts --clean --outDir ../../dist/bridge",
|
|
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
21
|
"prepublishOnly": "npm run build:bridge",
|
|
22
22
|
"fmt": "npx prettier --write 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'",
|
|
@@ -6,7 +6,7 @@ Updates come from two sources:
|
|
|
6
6
|
- **config.json** ... when you change your org settings, the "Your System" sections regenerate
|
|
7
7
|
- **System hooks** ... when tools are installed, updated, or reconfigured, the relevant docs update automatically
|
|
8
8
|
|
|
9
|
-
If something is wrong, update
|
|
9
|
+
If something is wrong, update `~/.ldm/config.json` or run `ldm install`. The docs will follow.
|
|
10
10
|
|
|
11
11
|
## What's Here
|
|
12
12
|
|
|
@@ -32,4 +32,4 @@ Each doc has two sections:
|
|
|
32
32
|
1. **Universal** ... how the feature works for everyone (top of file)
|
|
33
33
|
2. **Your System** ... your specific configuration (bottom, after the `---` separator)
|
|
34
34
|
|
|
35
|
-
Universal content comes from the LDM OS repo. "Your System" content is generated from your
|
|
35
|
+
Universal content comes from the LDM OS repo. "Your System" content is generated from your `~/.ldm/config.json`.
|