@wipcomputer/wip-ldm-os 0.4.27 → 0.4.29

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
@@ -20,11 +20,27 @@ LDM OS is a shared infrastructure that enables:
20
20
  Open your AI and paste this:
21
21
 
22
22
  ```
23
- Read https://wip.computer/install/wip-ldm-os.txt and follow the instructions.
23
+ Read https://wip.computer/install/wip-ldm-os.txt
24
24
 
25
- Explain what LDM OS is and what it does. Check if it's already installed.
25
+ Then explain:
26
+ 1. What is LDM OS?
27
+ 2. What does it install on my system?
28
+ 3. What changes for us? (this AI)
29
+ 4. What changes across all my AIs?
26
30
 
27
- If not installed, walk me through setup. Don't install anything until I say "install".
31
+ Check if LDM OS is already installed.
32
+
33
+ If it is, show me what I have and what's new.
34
+
35
+ If not, walk me through setup.
36
+
37
+ Then ask:
38
+ - Do you have questions?
39
+ - Want to see a dry run?
40
+
41
+ If I say yes: I can run ldm install --dry-run if LDM OS is already installed. If not, I'll need to install the CLI first (npm install -g @wipcomputer/wip-ldm-os) to run the dry run.
42
+
43
+ Show me exactly what will change. Don't install anything until I say "install".
28
44
  ```
29
45
 
30
46
  That's it. Your AI reads the spec, explains what it does, and walks you through a dry run before touching anything.
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.27"
8
+ version: "0.4.29"
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
@@ -1936,6 +1936,106 @@ async function main() {
1936
1936
  process.exit(0);
1937
1937
  }
1938
1938
 
1939
+ // ── ldm enable / disable (#111) ──
1940
+
1941
+ async function cmdEnable() {
1942
+ const target = args.slice(1).find(a => !a.startsWith('--'));
1943
+ if (!target) {
1944
+ console.log(' Usage: ldm enable <extension|stack>');
1945
+ console.log(' Example: ldm enable devops-toolbox');
1946
+ console.log(' Stacks: core, web, all');
1947
+ process.exit(1);
1948
+ }
1949
+
1950
+ const { enableExtension } = await import('../lib/deploy.mjs');
1951
+ const stacks = loadCatalog()?.stacks || {};
1952
+ const components = loadCatalog()?.components || [];
1953
+
1954
+ // Resolve stack to component list
1955
+ let names = [target];
1956
+ if (stacks[target]) {
1957
+ const stack = stacks[target];
1958
+ names = stack.components || [];
1959
+ if (stack.includes) {
1960
+ for (const inc of stack.includes) {
1961
+ if (stacks[inc]?.components) names.push(...stacks[inc].components);
1962
+ }
1963
+ }
1964
+ }
1965
+ // Map catalog IDs to registry names
1966
+ const resolvedNames = [];
1967
+ for (const n of names) {
1968
+ const comp = components.find(c => c.id === n);
1969
+ if (comp) {
1970
+ resolvedNames.push(comp.id);
1971
+ for (const m of (comp.registryMatches || [])) resolvedNames.push(m);
1972
+ } else {
1973
+ resolvedNames.push(n);
1974
+ }
1975
+ }
1976
+ const uniqueNames = [...new Set(resolvedNames)];
1977
+
1978
+ const registry = readJSON(REGISTRY_PATH);
1979
+ console.log('');
1980
+ for (const name of uniqueNames) {
1981
+ if (!registry?.extensions?.[name]) continue;
1982
+ const result = await enableExtension(name);
1983
+ if (result.ok) {
1984
+ console.log(` + ${name}: ${result.reason}`);
1985
+ } else {
1986
+ console.log(` ! ${name}: ${result.reason}`);
1987
+ }
1988
+ }
1989
+ console.log('');
1990
+ }
1991
+
1992
+ async function cmdDisable() {
1993
+ const target = args.slice(1).find(a => !a.startsWith('--'));
1994
+ if (!target) {
1995
+ console.log(' Usage: ldm disable <extension|stack>');
1996
+ process.exit(1);
1997
+ }
1998
+
1999
+ const { disableExtension } = await import('../lib/deploy.mjs');
2000
+ const stacks = loadCatalog()?.stacks || {};
2001
+ const components = loadCatalog()?.components || [];
2002
+
2003
+ let names = [target];
2004
+ if (stacks[target]) {
2005
+ const stack = stacks[target];
2006
+ names = stack.components || [];
2007
+ if (stack.includes) {
2008
+ for (const inc of stack.includes) {
2009
+ if (stacks[inc]?.components) names.push(...stacks[inc].components);
2010
+ }
2011
+ }
2012
+ }
2013
+ const resolvedNames = [];
2014
+ for (const n of names) {
2015
+ const comp = components.find(c => c.id === n);
2016
+ if (comp) {
2017
+ resolvedNames.push(comp.id);
2018
+ for (const m of (comp.registryMatches || [])) resolvedNames.push(m);
2019
+ } else {
2020
+ resolvedNames.push(n);
2021
+ }
2022
+ }
2023
+ const uniqueNames = [...new Set(resolvedNames)];
2024
+
2025
+ const registry = readJSON(REGISTRY_PATH);
2026
+ console.log('');
2027
+ for (const name of uniqueNames) {
2028
+ if (!registry?.extensions?.[name]) continue;
2029
+ const result = disableExtension(name);
2030
+ if (result.ok) {
2031
+ console.log(` - ${name}: ${result.reason}`);
2032
+ } else {
2033
+ console.log(` ! ${name}: ${result.reason}`);
2034
+ }
2035
+ }
2036
+ console.log('');
2037
+ }
2038
+
1939
2039
  if (command === '--version' || command === '-v') {
1940
2040
  console.log(PKG_VERSION);
1941
2041
  process.exit(0);
@@ -1973,6 +2073,12 @@ async function main() {
1973
2073
  case 'updates':
1974
2074
  await cmdUpdates();
1975
2075
  break;
2076
+ case 'enable':
2077
+ await cmdEnable();
2078
+ break;
2079
+ case 'disable':
2080
+ await cmdDisable();
2081
+ break;
1976
2082
  default:
1977
2083
  console.error(` Unknown command: ${command}`);
1978
2084
  console.error(` Run: ldm --help`);
package/catalog.json CHANGED
@@ -4,7 +4,12 @@
4
4
  "core": {
5
5
  "name": "WIP Core",
6
6
  "description": "Essential tools for all WIP.computer team members.",
7
- "components": ["memory-crystal", "wip-ai-devops-toolbox", "wip-1password", "wip-markdown-viewer"],
7
+ "components": [
8
+ "memory-crystal",
9
+ "wip-ai-devops-toolbox",
10
+ "wip-1password",
11
+ "wip-markdown-viewer"
12
+ ],
8
13
  "mcpServers": []
9
14
  },
10
15
  "web": {
@@ -12,17 +17,54 @@
12
17
  "description": "Frontend tools for Next.js, React, Tailwind projects.",
13
18
  "components": [],
14
19
  "mcpServers": [
15
- { "name": "playwright", "command": "npx", "args": ["-y", "@playwright/mcp@latest", "--browser", "chromium"] },
16
- { "name": "next-devtools", "command": "npx", "args": ["-y", "next-devtools-mcp@latest"] },
17
- { "name": "shadcn", "command": "npx", "args": ["-y", "shadcn@latest", "mcp"] },
18
- { "name": "tailwindcss-docs", "command": "npx", "args": ["-y", "tailwindcss-docs-mcp"] }
20
+ {
21
+ "name": "playwright",
22
+ "command": "npx",
23
+ "args": [
24
+ "-y",
25
+ "@playwright/mcp@latest",
26
+ "--browser",
27
+ "chromium"
28
+ ]
29
+ },
30
+ {
31
+ "name": "next-devtools",
32
+ "command": "npx",
33
+ "args": [
34
+ "-y",
35
+ "next-devtools-mcp@latest"
36
+ ]
37
+ },
38
+ {
39
+ "name": "shadcn",
40
+ "command": "npx",
41
+ "args": [
42
+ "-y",
43
+ "shadcn@latest",
44
+ "mcp"
45
+ ]
46
+ },
47
+ {
48
+ "name": "tailwindcss-docs",
49
+ "command": "npx",
50
+ "args": [
51
+ "-y",
52
+ "tailwindcss-docs-mcp"
53
+ ]
54
+ }
19
55
  ]
20
56
  },
21
57
  "all": {
22
58
  "name": "Everything",
23
59
  "description": "All WIP tools + web dev stack.",
24
- "includes": ["core", "web"],
25
- "components": ["wip-xai-grok", "wip-xai-x"],
60
+ "includes": [
61
+ "core",
62
+ "web"
63
+ ],
64
+ "components": [
65
+ "wip-xai-grok",
66
+ "wip-xai-x"
67
+ ],
26
68
  "mcpServers": []
27
69
  }
28
70
  },
@@ -33,14 +75,24 @@
33
75
  "description": "Persistent memory for your AI. Search, capture, consolidation.",
34
76
  "npm": "@wipcomputer/memory-crystal",
35
77
  "repo": "wipcomputer/memory-crystal",
36
- "registryMatches": ["memory-crystal"],
37
- "cliMatches": ["crystal"],
78
+ "registryMatches": [
79
+ "memory-crystal"
80
+ ],
81
+ "cliMatches": [
82
+ "crystal"
83
+ ],
38
84
  "recommended": true,
39
85
  "status": "stable",
40
86
  "postInstall": "crystal doctor",
41
87
  "installs": {
42
- "cli": ["crystal"],
43
- "mcp": ["crystal_search", "crystal_remember", "crystal_forget"],
88
+ "cli": [
89
+ "crystal"
90
+ ],
91
+ "mcp": [
92
+ "crystal_search",
93
+ "crystal_remember",
94
+ "crystal_forget"
95
+ ],
44
96
  "ocPlugin": "agent_end hook (conversation capture every turn)",
45
97
  "ccHook": "Stop hook (crystal capture + daily log)",
46
98
  "cron": "crystal-capture.sh (every 1 min, backup capture)",
@@ -53,14 +105,41 @@
53
105
  "description": "Release pipeline, license compliance, repo management, identity file protection.",
54
106
  "npm": "@wipcomputer/wip-ai-devops-toolbox",
55
107
  "repo": "wipcomputer/wip-ai-devops-toolbox",
56
- "registryMatches": ["wip-repos", "wip-release", "wip-file-guard", "wip-license-hook", "wip-repo-permissions-hook", "deploy-public", "post-merge-rename", "wip-license-guard", "wip-repo-init", "wip-readme-format", "wip-branch-guard"],
57
- "cliMatches": ["wip-release", "wip-repos", "wip-file-guard", "wip-branch-guard"],
108
+ "registryMatches": [
109
+ "wip-repos",
110
+ "wip-release",
111
+ "wip-file-guard",
112
+ "wip-license-hook",
113
+ "wip-repo-permissions-hook",
114
+ "deploy-public",
115
+ "post-merge-rename",
116
+ "wip-license-guard",
117
+ "wip-repo-init",
118
+ "wip-readme-format",
119
+ "wip-branch-guard"
120
+ ],
121
+ "cliMatches": [
122
+ "wip-release",
123
+ "wip-repos",
124
+ "wip-file-guard",
125
+ "wip-branch-guard"
126
+ ],
58
127
  "recommended": false,
59
128
  "status": "stable",
60
129
  "postInstall": null,
61
130
  "installs": {
62
- "cli": ["wip-release", "wip-repos", "wip-file-guard", "wip-install"],
63
- "mcp": ["wip-release", "wip-repos", "wip-license-hook", "wip-repo-permissions-hook"],
131
+ "cli": [
132
+ "wip-release",
133
+ "wip-repos",
134
+ "wip-file-guard",
135
+ "wip-install"
136
+ ],
137
+ "mcp": [
138
+ "wip-release",
139
+ "wip-repos",
140
+ "wip-license-hook",
141
+ "wip-repo-permissions-hook"
142
+ ],
64
143
  "ccHook": "branch-guard (blocks writes on main), file-guard (protects identity files), repo-permissions (workspace boundaries)",
65
144
  "tools": "12 sub-tools: release, repos, file-guard, license-hook, license-guard, repo-permissions-hook, deploy-public, post-merge-rename, repo-init, readme-format, branch-guard, universal-installer"
66
145
  }
@@ -71,13 +150,20 @@
71
150
  "description": "1Password secrets for AI agents.",
72
151
  "npm": null,
73
152
  "repo": "wipcomputer/wip-1password",
74
- "registryMatches": ["wip-1password", "op-secrets"],
153
+ "registryMatches": [
154
+ "wip-1password",
155
+ "op-secrets"
156
+ ],
75
157
  "cliMatches": [],
76
158
  "recommended": false,
77
159
  "status": "stable",
78
160
  "postInstall": null,
79
161
  "installs": {
80
- "mcp": ["op_list_items", "op_read_secret", "op_test"],
162
+ "mcp": [
163
+ "op_list_items",
164
+ "op_read_secret",
165
+ "op_test"
166
+ ],
81
167
  "ocPlugin": "op-secrets (headless 1Password via service account)"
82
168
  }
83
169
  },
@@ -87,13 +173,20 @@
87
173
  "description": "Live markdown viewer for AI pair-editing. Updates render instantly in any browser.",
88
174
  "npm": "@wipcomputer/markdown-viewer",
89
175
  "repo": "wipcomputer/wip-markdown-viewer",
90
- "registryMatches": ["wip-markdown-viewer", "markdown-viewer"],
91
- "cliMatches": ["mdview"],
176
+ "registryMatches": [
177
+ "wip-markdown-viewer",
178
+ "markdown-viewer"
179
+ ],
180
+ "cliMatches": [
181
+ "mdview"
182
+ ],
92
183
  "recommended": false,
93
184
  "status": "stable",
94
185
  "postInstall": null,
95
186
  "installs": {
96
- "cli": ["mdview"],
187
+ "cli": [
188
+ "mdview"
189
+ ],
97
190
  "web": "localhost:3000 (live reload markdown renderer)"
98
191
  }
99
192
  },
@@ -103,13 +196,23 @@
103
196
  "description": "xAI Grok API. Search the web, search X, generate images, generate video.",
104
197
  "npm": null,
105
198
  "repo": "wipcomputer/wip-xai-grok",
106
- "registryMatches": ["wip-xai-grok", "grok-search"],
199
+ "registryMatches": [
200
+ "wip-xai-grok",
201
+ "grok-search"
202
+ ],
107
203
  "cliMatches": [],
108
204
  "recommended": false,
109
205
  "status": "stable",
110
206
  "postInstall": null,
111
207
  "installs": {
112
- "mcp": ["grok_search_web", "grok_search_x", "grok_imagine", "grok_edit_image", "grok_generate_video", "grok_poll_video"]
208
+ "mcp": [
209
+ "grok_search_web",
210
+ "grok_search_x",
211
+ "grok_imagine",
212
+ "grok_edit_image",
213
+ "grok_generate_video",
214
+ "grok_poll_video"
215
+ ]
113
216
  }
114
217
  },
115
218
  {
@@ -118,13 +221,23 @@
118
221
  "description": "X Platform API. Read posts, search tweets, post, upload media.",
119
222
  "npm": null,
120
223
  "repo": "wipcomputer/wip-xai-x",
121
- "registryMatches": ["wip-xai-x"],
224
+ "registryMatches": [
225
+ "wip-xai-x"
226
+ ],
122
227
  "cliMatches": [],
123
228
  "recommended": false,
124
229
  "status": "stable",
125
230
  "postInstall": null,
126
231
  "installs": {
127
- "mcp": ["x_fetch_post", "x_search_recent", "x_get_bookmarks", "x_get_user", "x_post_tweet", "x_delete_tweet", "x_upload_media"]
232
+ "mcp": [
233
+ "x_fetch_post",
234
+ "x_search_recent",
235
+ "x_get_bookmarks",
236
+ "x_get_user",
237
+ "x_post_tweet",
238
+ "x_delete_tweet",
239
+ "x_upload_media"
240
+ ]
128
241
  }
129
242
  },
130
243
  {
@@ -133,13 +246,19 @@
133
246
  "description": "AI agent platform. Run AI agents 24/7 with identity, memory, and tool access.",
134
247
  "npm": null,
135
248
  "repo": "openclaw/openclaw",
136
- "registryMatches": ["openclaw"],
137
- "cliMatches": ["openclaw"],
249
+ "registryMatches": [
250
+ "openclaw"
251
+ ],
252
+ "cliMatches": [
253
+ "openclaw"
254
+ ],
138
255
  "recommended": false,
139
256
  "status": "stable",
140
257
  "postInstall": null,
141
258
  "installs": {
142
- "cli": ["openclaw"],
259
+ "cli": [
260
+ "openclaw"
261
+ ],
143
262
  "runtime": "24/7 agent gateway on localhost:18789",
144
263
  "plugins": "Extensions system at ~/.openclaw/extensions/"
145
264
  }
@@ -150,7 +269,10 @@
150
269
  "description": "Memory consolidation protocol for AI agents with bounded context windows.",
151
270
  "npm": null,
152
271
  "repo": "wipcomputer/dream-weaver-protocol",
153
- "registryMatches": ["dream-weaver-protocol", "dream-weaver"],
272
+ "registryMatches": [
273
+ "dream-weaver-protocol",
274
+ "dream-weaver"
275
+ ],
154
276
  "cliMatches": [],
155
277
  "recommended": false,
156
278
  "status": "stable",
@@ -159,23 +281,6 @@
159
281
  "skill": "SKILL.md (protocol documentation for agents)",
160
282
  "docs": "19-page paper on memory consolidation. No runtime components."
161
283
  }
162
- },
163
- {
164
- "id": "wip-bridge",
165
- "name": "Bridge",
166
- "description": "Cross-platform agent bridge. Enables Claude Code CLI to talk to OpenClaw CLI without a human in the middle.",
167
- "npm": null,
168
- "repo": "wipcomputer/wip-bridge-deprecated",
169
- "registryMatches": ["wip-bridge", "lesa-bridge"],
170
- "cliMatches": [],
171
- "recommended": false,
172
- "status": "included",
173
- "postInstall": null,
174
- "installs": {
175
- "note": "Included with LDM OS v0.3.0+. No separate install needed.",
176
- "mcp": ["lesa_send_message", "lesa_check_inbox", "lesa_conversation_search", "lesa_memory_search", "lesa_read_workspace", "oc_skills_list"],
177
- "cli": ["lesa"]
178
- }
179
284
  }
180
285
  ]
181
286
  }
package/lib/deploy.mjs CHANGED
@@ -76,11 +76,17 @@ function saveRegistry(registry) {
76
76
  writeJSON(REGISTRY_PATH, registry);
77
77
  }
78
78
 
79
+ // Core extensions are always enabled. Everything else defaults to disabled on first install.
80
+ const CORE_EXTENSIONS = new Set(['memory-crystal']);
81
+
79
82
  function updateRegistry(name, info) {
80
83
  const registry = loadRegistry();
84
+ const existing = registry.extensions[name];
85
+ const isCore = CORE_EXTENSIONS.has(name);
81
86
  registry.extensions[name] = {
82
- ...registry.extensions[name],
87
+ ...existing,
83
88
  ...info,
89
+ enabled: existing?.enabled ?? isCore,
84
90
  updatedAt: new Date().toISOString(),
85
91
  };
86
92
  saveRegistry(registry);
@@ -772,16 +778,32 @@ export function installSingleTool(toolPath) {
772
778
  }
773
779
  }
774
780
 
781
+ // Only register MCP, hooks, and skills if extension is enabled (#111)
782
+ const registry = loadRegistry();
783
+ const isEnabled = registry.extensions?.[toolName]?.enabled ?? CORE_EXTENSIONS.has(toolName);
784
+
775
785
  if (interfaces.mcp) {
776
- if (registerMCP(toolPath, interfaces.mcp, toolName)) installed++;
786
+ if (isEnabled) {
787
+ if (registerMCP(toolPath, interfaces.mcp, toolName)) installed++;
788
+ } else {
789
+ skip(`MCP: ${toolName} installed but not enabled. Run: ldm enable ${toolName}`);
790
+ }
777
791
  }
778
792
 
779
793
  if (interfaces.claudeCodeHook) {
780
- if (installClaudeCodeHook(toolPath, interfaces.claudeCodeHook)) installed++;
794
+ if (isEnabled) {
795
+ if (installClaudeCodeHook(toolPath, interfaces.claudeCodeHook)) installed++;
796
+ } else {
797
+ skip(`Hook: ${toolName} installed but not enabled`);
798
+ }
781
799
  }
782
800
 
783
801
  if (interfaces.skill) {
784
- if (installSkill(toolPath, toolName)) installed++;
802
+ if (isEnabled) {
803
+ if (installSkill(toolPath, toolName)) installed++;
804
+ } else {
805
+ skip(`Skill: ${toolName} installed but not enabled`);
806
+ }
785
807
  }
786
808
 
787
809
  if (interfaces.module) {
@@ -868,6 +890,93 @@ export async function installFromPath(repoPath) {
868
890
  return { tools: 1, interfaces: installed };
869
891
  }
870
892
 
893
+ // ── Enable / Disable (#111) ──
894
+
895
+ function unregisterMCP(name) {
896
+ try {
897
+ execSync(`claude mcp remove --scope user ${name} 2>/dev/null`, { stdio: 'pipe', timeout: 10000 });
898
+ } catch {}
899
+ // Also clean ~/.claude.json directly
900
+ const claudeJson = join(HOME, '.claude.json');
901
+ try {
902
+ const config = readJSON(claudeJson);
903
+ if (config?.mcpServers?.[name]) {
904
+ delete config.mcpServers[name];
905
+ writeJSON(claudeJson, config);
906
+ }
907
+ } catch {}
908
+ }
909
+
910
+ function removeClaudeCodeHook(name) {
911
+ const settingsPath = join(HOME, '.claude', 'settings.json');
912
+ try {
913
+ const settings = readJSON(settingsPath);
914
+ if (!settings?.hooks) return;
915
+ let changed = false;
916
+ for (const [event, entries] of Object.entries(settings.hooks)) {
917
+ if (!Array.isArray(entries)) continue;
918
+ for (const entry of entries) {
919
+ if (!entry.hooks || !Array.isArray(entry.hooks)) continue;
920
+ const before = entry.hooks.length;
921
+ entry.hooks = entry.hooks.filter(h => !h.command?.includes(name));
922
+ if (entry.hooks.length < before) changed = true;
923
+ }
924
+ // Remove empty entries
925
+ settings.hooks[event] = entries.filter(e => e.hooks?.length > 0);
926
+ }
927
+ if (changed) writeJSON(settingsPath, settings);
928
+ } catch {}
929
+ }
930
+
931
+ function removeSkill(name) {
932
+ const skillDir = join(OC_ROOT, 'skills', name);
933
+ try {
934
+ if (existsSync(skillDir)) {
935
+ execSync(`rm -rf "${skillDir}"`, { stdio: 'pipe' });
936
+ }
937
+ } catch {}
938
+ }
939
+
940
+ export async function enableExtension(name) {
941
+ const reg = loadRegistry();
942
+ const entry = reg.extensions?.[name];
943
+ if (!entry) return { ok: false, reason: 'not installed' };
944
+ if (entry.enabled) return { ok: true, reason: 'already enabled' };
945
+
946
+ const extPath = entry.ldmPath || join(LDM_EXTENSIONS, name);
947
+ if (!existsSync(extPath)) return { ok: false, reason: 'extension dir missing' };
948
+
949
+ const { detectInterfaces } = await import('./detect.mjs');
950
+ const { interfaces } = detectInterfaces(extPath);
951
+
952
+ if (interfaces.mcp) registerMCP(extPath, interfaces.mcp, name);
953
+ if (interfaces.claudeCodeHook) installClaudeCodeHook(extPath, interfaces.claudeCodeHook);
954
+ if (interfaces.skill) installSkill(extPath, name);
955
+
956
+ entry.enabled = true;
957
+ entry.updatedAt = new Date().toISOString();
958
+ saveRegistry(reg);
959
+ return { ok: true, reason: 'enabled' };
960
+ }
961
+
962
+ export function disableExtension(name) {
963
+ if (CORE_EXTENSIONS.has(name)) return { ok: false, reason: 'core extension, cannot disable' };
964
+
965
+ const reg = loadRegistry();
966
+ const entry = reg.extensions?.[name];
967
+ if (!entry) return { ok: false, reason: 'not installed' };
968
+ if (!entry.enabled) return { ok: true, reason: 'already disabled' };
969
+
970
+ unregisterMCP(name);
971
+ removeClaudeCodeHook(name);
972
+ removeSkill(name);
973
+
974
+ entry.enabled = false;
975
+ entry.updatedAt = new Date().toISOString();
976
+ saveRegistry(reg);
977
+ return { ok: true, reason: 'disabled' };
978
+ }
979
+
871
980
  // ── Exports for ldm CLI ──
872
981
 
873
- export { loadRegistry, saveRegistry, updateRegistry, readJSON, writeJSON, runBuildIfNeeded };
982
+ export { loadRegistry, saveRegistry, updateRegistry, readJSON, writeJSON, runBuildIfNeeded, CORE_EXTENSIONS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
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",