@wipcomputer/wip-ldm-os 0.4.29 → 0.4.30
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/SKILL.md +1 -1
- package/bin/ldm.js +278 -21
- package/package.json +1 -1
package/SKILL.md
CHANGED
package/bin/ldm.js
CHANGED
|
@@ -30,6 +30,7 @@ const LDM_ROOT = join(HOME, '.ldm');
|
|
|
30
30
|
const LDM_EXTENSIONS = join(LDM_ROOT, 'extensions');
|
|
31
31
|
const VERSION_PATH = join(LDM_ROOT, 'version.json');
|
|
32
32
|
const REGISTRY_PATH = join(LDM_EXTENSIONS, 'registry.json');
|
|
33
|
+
const LDM_TMP = join(LDM_ROOT, 'tmp');
|
|
33
34
|
|
|
34
35
|
// Install log (#101): append to ~/.ldm/logs/install.log
|
|
35
36
|
import { appendFileSync } from 'node:fs';
|
|
@@ -227,7 +228,21 @@ function loadCatalog() {
|
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
function findInCatalog(id) {
|
|
230
|
-
|
|
231
|
+
const q = id.toLowerCase();
|
|
232
|
+
const catalog = loadCatalog();
|
|
233
|
+
// Exact id match
|
|
234
|
+
const exact = catalog.find(c => c.id === id);
|
|
235
|
+
if (exact) return exact;
|
|
236
|
+
// Partial id match (e.g. "xai-grok" matches "wip-xai-grok")
|
|
237
|
+
const partial = catalog.find(c => c.id.toLowerCase().includes(q) || q.includes(c.id.toLowerCase()));
|
|
238
|
+
if (partial) return partial;
|
|
239
|
+
// Name match (case-insensitive, e.g. "xAI Grok")
|
|
240
|
+
const byName = catalog.find(c => c.name && c.name.toLowerCase() === q);
|
|
241
|
+
if (byName) return byName;
|
|
242
|
+
// registryMatches match
|
|
243
|
+
const byRegistry = catalog.find(c => (c.registryMatches || []).some(m => m.toLowerCase() === q));
|
|
244
|
+
if (byRegistry) return byRegistry;
|
|
245
|
+
return null;
|
|
231
246
|
}
|
|
232
247
|
|
|
233
248
|
// ── ldm init ──
|
|
@@ -484,8 +499,22 @@ async function cmdInstall() {
|
|
|
484
499
|
return cmdInstallCatalog();
|
|
485
500
|
}
|
|
486
501
|
|
|
502
|
+
// If target is a private repo (org/name-private), redirect to public (#134)
|
|
503
|
+
let resolvedTarget = target;
|
|
504
|
+
if (target.match(/^[\w-]+\/[\w.-]+-private$/) && !existsSync(resolve(target))) {
|
|
505
|
+
const publicRepo = target.replace(/-private$/, '');
|
|
506
|
+
const catalogHit = findInCatalog(basename(publicRepo));
|
|
507
|
+
if (catalogHit) {
|
|
508
|
+
console.log(` Redirecting ${target} to public repo: ${catalogHit.repo}`);
|
|
509
|
+
resolvedTarget = catalogHit.repo;
|
|
510
|
+
} else {
|
|
511
|
+
console.log(` Redirecting ${target} to public repo: ${publicRepo}`);
|
|
512
|
+
resolvedTarget = publicRepo;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
487
516
|
// Check if target is a catalog ID (e.g. "memory-crystal")
|
|
488
|
-
const catalogEntry = findInCatalog(
|
|
517
|
+
const catalogEntry = findInCatalog(resolvedTarget);
|
|
489
518
|
if (catalogEntry) {
|
|
490
519
|
console.log('');
|
|
491
520
|
console.log(` Resolved "${target}" via catalog to ${catalogEntry.repo}`);
|
|
@@ -493,10 +522,11 @@ async function cmdInstall() {
|
|
|
493
522
|
// Use the repo field to clone from GitHub
|
|
494
523
|
const repoTarget = catalogEntry.repo;
|
|
495
524
|
const repoName = basename(repoTarget);
|
|
496
|
-
const repoPath = join(
|
|
525
|
+
const repoPath = join(LDM_TMP, `ldm-install-${repoName}`);
|
|
497
526
|
const httpsUrl = `https://github.com/${repoTarget}.git`;
|
|
498
527
|
const sshUrl = `git@github.com:${repoTarget}.git`;
|
|
499
528
|
|
|
529
|
+
mkdirSync(LDM_TMP, { recursive: true });
|
|
500
530
|
console.log(` Cloning ${repoTarget}...`);
|
|
501
531
|
try {
|
|
502
532
|
if (existsSync(repoPath)) {
|
|
@@ -523,8 +553,8 @@ async function cmdInstall() {
|
|
|
523
553
|
|
|
524
554
|
await installFromPath(repoPath);
|
|
525
555
|
|
|
526
|
-
// Clean up
|
|
527
|
-
if (!DRY_RUN &&
|
|
556
|
+
// Clean up staging clone after install (#32, #135)
|
|
557
|
+
if (!DRY_RUN && repoPath.startsWith(LDM_TMP)) {
|
|
528
558
|
try { execSync(`rm -rf "${repoPath}"`, { stdio: 'pipe' }); } catch {}
|
|
529
559
|
}
|
|
530
560
|
return;
|
|
@@ -534,10 +564,10 @@ async function cmdInstall() {
|
|
|
534
564
|
let repoPath;
|
|
535
565
|
|
|
536
566
|
// Check if target looks like an npm package (starts with @ or is a plain name without /)
|
|
537
|
-
if (
|
|
567
|
+
if (resolvedTarget.startsWith('@') || (!resolvedTarget.includes('/') && !existsSync(resolve(resolvedTarget)))) {
|
|
538
568
|
// Try npm install to temp dir
|
|
539
|
-
const npmName =
|
|
540
|
-
const tempDir = join(
|
|
569
|
+
const npmName = resolvedTarget;
|
|
570
|
+
const tempDir = join(LDM_TMP, `ldm-install-npm-${Date.now()}`);
|
|
541
571
|
console.log('');
|
|
542
572
|
console.log(` Installing ${npmName} from npm...`);
|
|
543
573
|
try {
|
|
@@ -560,19 +590,20 @@ async function cmdInstall() {
|
|
|
560
590
|
}
|
|
561
591
|
}
|
|
562
592
|
|
|
563
|
-
if (!repoPath && (
|
|
564
|
-
const isShorthand =
|
|
593
|
+
if (!repoPath && (resolvedTarget.startsWith('http') || resolvedTarget.startsWith('git@') || resolvedTarget.match(/^[\w-]+\/[\w.-]+$/))) {
|
|
594
|
+
const isShorthand = resolvedTarget.match(/^[\w-]+\/[\w.-]+$/);
|
|
565
595
|
const httpsUrl = isShorthand
|
|
566
|
-
? `https://github.com/${
|
|
567
|
-
:
|
|
596
|
+
? `https://github.com/${resolvedTarget}.git`
|
|
597
|
+
: resolvedTarget;
|
|
568
598
|
const sshUrl = isShorthand
|
|
569
|
-
? `git@github.com:${
|
|
570
|
-
:
|
|
599
|
+
? `git@github.com:${resolvedTarget}.git`
|
|
600
|
+
: resolvedTarget.replace(/^https:\/\/github\.com\//, 'git@github.com:');
|
|
571
601
|
const repoName = basename(httpsUrl).replace('.git', '');
|
|
572
|
-
repoPath = join(
|
|
602
|
+
repoPath = join(LDM_TMP, `ldm-install-${repoName}`);
|
|
573
603
|
|
|
604
|
+
mkdirSync(LDM_TMP, { recursive: true });
|
|
574
605
|
console.log('');
|
|
575
|
-
console.log(` Cloning ${isShorthand ?
|
|
606
|
+
console.log(` Cloning ${isShorthand ? resolvedTarget : httpsUrl}...`);
|
|
576
607
|
try {
|
|
577
608
|
if (existsSync(repoPath)) {
|
|
578
609
|
execSync(`rm -rf "${repoPath}"`, { stdio: 'pipe' });
|
|
@@ -590,7 +621,7 @@ async function cmdInstall() {
|
|
|
590
621
|
process.exit(1);
|
|
591
622
|
}
|
|
592
623
|
} else if (!repoPath) {
|
|
593
|
-
repoPath = resolve(
|
|
624
|
+
repoPath = resolve(resolvedTarget);
|
|
594
625
|
if (!existsSync(repoPath)) {
|
|
595
626
|
console.error(` x Path not found: ${repoPath}`);
|
|
596
627
|
process.exit(1);
|
|
@@ -605,8 +636,8 @@ async function cmdInstall() {
|
|
|
605
636
|
|
|
606
637
|
await installFromPath(repoPath);
|
|
607
638
|
|
|
608
|
-
// Clean up
|
|
609
|
-
if (!DRY_RUN &&
|
|
639
|
+
// Clean up staging clone after install (#32, #135)
|
|
640
|
+
if (!DRY_RUN && repoPath.startsWith(LDM_TMP)) {
|
|
610
641
|
try { execSync(`rm -rf "${repoPath}"`, { stdio: 'pipe' }); } catch {}
|
|
611
642
|
}
|
|
612
643
|
}
|
|
@@ -918,13 +949,21 @@ async function cmdInstallCatalog() {
|
|
|
918
949
|
}
|
|
919
950
|
} catch {}
|
|
920
951
|
|
|
921
|
-
// Check orphaned /tmp/
|
|
952
|
+
// Check orphaned staging dirs (old /tmp/ and new ~/.ldm/tmp/)
|
|
922
953
|
try {
|
|
923
954
|
const tmpCount = readdirSync('/private/tmp').filter(d => d.startsWith('ldm-install-')).length;
|
|
924
955
|
if (tmpCount > 0) {
|
|
925
956
|
healthIssues.push(` ! ${tmpCount} orphaned /tmp/ldm-install-* dirs (would clean up)`);
|
|
926
957
|
}
|
|
927
958
|
} catch {}
|
|
959
|
+
try {
|
|
960
|
+
if (existsSync(LDM_TMP)) {
|
|
961
|
+
const ldmTmpCount = readdirSync(LDM_TMP).filter(d => d.startsWith('ldm-install-')).length;
|
|
962
|
+
if (ldmTmpCount > 0) {
|
|
963
|
+
healthIssues.push(` ! ${ldmTmpCount} orphaned ~/.ldm/tmp/ldm-install-* dirs (would clean up)`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
} catch {}
|
|
928
967
|
|
|
929
968
|
if (healthIssues.length > 0) {
|
|
930
969
|
console.log('');
|
|
@@ -1075,7 +1114,7 @@ async function cmdInstallCatalog() {
|
|
|
1075
1114
|
}
|
|
1076
1115
|
} catch {}
|
|
1077
1116
|
|
|
1078
|
-
// 3. Clean orphaned /tmp/ldm
|
|
1117
|
+
// 3. Clean orphaned staging dirs (old /tmp/ and new ~/.ldm/tmp/)
|
|
1079
1118
|
try {
|
|
1080
1119
|
const tmpDirs = readdirSync('/private/tmp').filter(d => d.startsWith('ldm-install-'));
|
|
1081
1120
|
if (tmpDirs.length > 0) {
|
|
@@ -1087,6 +1126,19 @@ async function cmdInstallCatalog() {
|
|
|
1087
1126
|
console.log(` + Cleaned ${tmpDirs.length} orphaned /tmp/ clone(s)`);
|
|
1088
1127
|
}
|
|
1089
1128
|
} catch {}
|
|
1129
|
+
try {
|
|
1130
|
+
if (existsSync(LDM_TMP)) {
|
|
1131
|
+
const ldmTmpDirs = readdirSync(LDM_TMP).filter(d => d.startsWith('ldm-install-'));
|
|
1132
|
+
if (ldmTmpDirs.length > 0) {
|
|
1133
|
+
console.log(` Cleaning ${ldmTmpDirs.length} orphaned ~/.ldm/tmp/ dirs...`);
|
|
1134
|
+
for (const d of ldmTmpDirs) {
|
|
1135
|
+
try { execSync(`rm -rf "${join(LDM_TMP, d)}"`, { stdio: 'pipe', timeout: 10000 }); } catch {}
|
|
1136
|
+
}
|
|
1137
|
+
healthFixes++;
|
|
1138
|
+
console.log(` + Cleaned ${ldmTmpDirs.length} orphaned ~/.ldm/tmp/ clone(s)`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
} catch {}
|
|
1090
1142
|
|
|
1091
1143
|
if (healthFixes > 0) {
|
|
1092
1144
|
console.log(` ${healthFixes} health issue(s) fixed.`);
|
|
@@ -2036,6 +2088,208 @@ async function main() {
|
|
|
2036
2088
|
console.log('');
|
|
2037
2089
|
}
|
|
2038
2090
|
|
|
2091
|
+
// ── ldm uninstall (#114) ──
|
|
2092
|
+
|
|
2093
|
+
async function cmdUninstall() {
|
|
2094
|
+
const keepData = !args.includes('--all');
|
|
2095
|
+
const isDryRun = args.includes('--dry-run');
|
|
2096
|
+
|
|
2097
|
+
console.log('');
|
|
2098
|
+
console.log(' LDM OS Uninstall');
|
|
2099
|
+
console.log(' ────────────────────────────────────');
|
|
2100
|
+
|
|
2101
|
+
if (keepData) {
|
|
2102
|
+
console.log(' Your data will be PRESERVED:');
|
|
2103
|
+
console.log(' ~/.ldm/memory/ (crystal.db, shared memory)');
|
|
2104
|
+
console.log(' ~/.ldm/agents/ (identity, journals, daily logs)');
|
|
2105
|
+
console.log('');
|
|
2106
|
+
console.log(' Use --all to remove everything including data.');
|
|
2107
|
+
} else {
|
|
2108
|
+
console.log(' WARNING: --all flag set. ALL data will be removed.');
|
|
2109
|
+
console.log(' This includes crystal.db, agent files, journals, everything.');
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
console.log('');
|
|
2113
|
+
console.log(' Will remove:');
|
|
2114
|
+
|
|
2115
|
+
// 1. MCP servers
|
|
2116
|
+
const claudeJsonPath = join(HOME, '.claude.json');
|
|
2117
|
+
try {
|
|
2118
|
+
const claudeJson = JSON.parse(readFileSync(claudeJsonPath, 'utf8'));
|
|
2119
|
+
const mcpNames = Object.keys(claudeJson.mcpServers || {}).filter(n =>
|
|
2120
|
+
n.includes('crystal') || n.includes('wip-') || n.includes('memory') ||
|
|
2121
|
+
n.includes('grok') || n.includes('lesa') || n.includes('1password')
|
|
2122
|
+
);
|
|
2123
|
+
if (mcpNames.length > 0) {
|
|
2124
|
+
console.log(` MCP servers: ${mcpNames.join(', ')}`);
|
|
2125
|
+
}
|
|
2126
|
+
} catch {}
|
|
2127
|
+
|
|
2128
|
+
// 2. CC hooks
|
|
2129
|
+
const settingsPath = join(HOME, '.claude', 'settings.json');
|
|
2130
|
+
try {
|
|
2131
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
2132
|
+
let hookCount = 0;
|
|
2133
|
+
for (const [event, entries] of Object.entries(settings.hooks || {})) {
|
|
2134
|
+
for (const entry of (Array.isArray(entries) ? entries : [])) {
|
|
2135
|
+
for (const h of (entry.hooks || [])) {
|
|
2136
|
+
if (h.command?.includes('.ldm') || h.command?.includes('wip-')) hookCount++;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
if (hookCount > 0) console.log(` CC hooks: ${hookCount} hook(s)`);
|
|
2141
|
+
} catch {}
|
|
2142
|
+
|
|
2143
|
+
// 3. Skills
|
|
2144
|
+
const skillsDir = join(HOME, '.openclaw', 'skills');
|
|
2145
|
+
try {
|
|
2146
|
+
const skills = readdirSync(skillsDir).filter(d => d !== '.DS_Store');
|
|
2147
|
+
if (skills.length > 0) console.log(` Skills: ${skills.join(', ')}`);
|
|
2148
|
+
} catch {}
|
|
2149
|
+
|
|
2150
|
+
// 4. Cron jobs
|
|
2151
|
+
try {
|
|
2152
|
+
const crontab = execSync('crontab -l 2>/dev/null', { encoding: 'utf8' });
|
|
2153
|
+
const ldmLines = crontab.split('\n').filter(l => l.includes('.ldm') || l.includes('crystal-capture') || l.includes('process-monitor'));
|
|
2154
|
+
if (ldmLines.length > 0) console.log(` Cron jobs: ${ldmLines.length}`);
|
|
2155
|
+
} catch {}
|
|
2156
|
+
|
|
2157
|
+
// 5. Global npm packages
|
|
2158
|
+
try {
|
|
2159
|
+
const npmList = execSync('npm list -g --depth=0 --json 2>/dev/null', { encoding: 'utf8' });
|
|
2160
|
+
const deps = JSON.parse(npmList).dependencies || {};
|
|
2161
|
+
const wipPkgs = Object.keys(deps).filter(n => n.startsWith('@wipcomputer/'));
|
|
2162
|
+
if (wipPkgs.length > 0) console.log(` npm packages: ${wipPkgs.join(', ')}`);
|
|
2163
|
+
} catch {}
|
|
2164
|
+
|
|
2165
|
+
// 6. Directories
|
|
2166
|
+
console.log(` ~/.ldm/extensions/`);
|
|
2167
|
+
if (!keepData) {
|
|
2168
|
+
console.log(` ~/.ldm/memory/`);
|
|
2169
|
+
console.log(` ~/.ldm/agents/`);
|
|
2170
|
+
}
|
|
2171
|
+
console.log(` ~/.ldm/state/, bin/, hooks/, logs/, sessions/, messages/`);
|
|
2172
|
+
|
|
2173
|
+
if (isDryRun) {
|
|
2174
|
+
console.log('');
|
|
2175
|
+
console.log(' Dry run. Nothing removed.');
|
|
2176
|
+
console.log('');
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// Confirm
|
|
2181
|
+
if (process.stdin.isTTY) {
|
|
2182
|
+
const { createInterface } = await import('readline');
|
|
2183
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2184
|
+
const answer = await new Promise(resolve => {
|
|
2185
|
+
rl.question('\n Type "uninstall" to confirm: ', resolve);
|
|
2186
|
+
});
|
|
2187
|
+
rl.close();
|
|
2188
|
+
if (answer.trim() !== 'uninstall') {
|
|
2189
|
+
console.log(' Cancelled.');
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
console.log('');
|
|
2195
|
+
console.log(' Removing...');
|
|
2196
|
+
|
|
2197
|
+
// 1. Unregister MCP servers
|
|
2198
|
+
try {
|
|
2199
|
+
const claudeJson = JSON.parse(readFileSync(claudeJsonPath, 'utf8'));
|
|
2200
|
+
const mcpNames = Object.keys(claudeJson.mcpServers || {}).filter(n =>
|
|
2201
|
+
n.includes('crystal') || n.includes('wip-') || n.includes('memory') ||
|
|
2202
|
+
n.includes('grok') || n.includes('lesa') || n.includes('1password')
|
|
2203
|
+
);
|
|
2204
|
+
for (const name of mcpNames) {
|
|
2205
|
+
delete claudeJson.mcpServers[name];
|
|
2206
|
+
}
|
|
2207
|
+
writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + '\n');
|
|
2208
|
+
console.log(` + Removed ${mcpNames.length} MCP server(s)`);
|
|
2209
|
+
} catch {}
|
|
2210
|
+
|
|
2211
|
+
// 2. Remove CC hooks
|
|
2212
|
+
try {
|
|
2213
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
2214
|
+
let removed = 0;
|
|
2215
|
+
for (const [event, entries] of Object.entries(settings.hooks || {})) {
|
|
2216
|
+
if (!Array.isArray(entries)) continue;
|
|
2217
|
+
for (const entry of entries) {
|
|
2218
|
+
if (!entry.hooks) continue;
|
|
2219
|
+
const before = entry.hooks.length;
|
|
2220
|
+
entry.hooks = entry.hooks.filter(h => !h.command?.includes('.ldm') && !h.command?.includes('wip-'));
|
|
2221
|
+
removed += before - entry.hooks.length;
|
|
2222
|
+
}
|
|
2223
|
+
settings.hooks[event] = entries.filter(e => e.hooks?.length > 0);
|
|
2224
|
+
}
|
|
2225
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
2226
|
+
console.log(` + Removed ${removed} CC hook(s)`);
|
|
2227
|
+
} catch {}
|
|
2228
|
+
|
|
2229
|
+
// 3. Remove skills
|
|
2230
|
+
try {
|
|
2231
|
+
const skills = readdirSync(skillsDir).filter(d => d !== '.DS_Store');
|
|
2232
|
+
for (const s of skills) {
|
|
2233
|
+
execSync(`rm -rf "${join(skillsDir, s)}"`, { stdio: 'pipe' });
|
|
2234
|
+
}
|
|
2235
|
+
console.log(` + Removed ${skills.length} skill(s)`);
|
|
2236
|
+
} catch {}
|
|
2237
|
+
|
|
2238
|
+
// 4. Remove cron jobs
|
|
2239
|
+
try {
|
|
2240
|
+
const crontab = execSync('crontab -l 2>/dev/null', { encoding: 'utf8' });
|
|
2241
|
+
const filtered = crontab.split('\n').filter(l =>
|
|
2242
|
+
!l.includes('.ldm') && !l.includes('crystal-capture') && !l.includes('process-monitor')
|
|
2243
|
+
).join('\n');
|
|
2244
|
+
execSync(`echo "${filtered}" | crontab -`, { stdio: 'pipe' });
|
|
2245
|
+
console.log(' + Cleaned cron jobs');
|
|
2246
|
+
} catch {}
|
|
2247
|
+
|
|
2248
|
+
// 5. Remove npm packages
|
|
2249
|
+
try {
|
|
2250
|
+
const npmList = execSync('npm list -g --depth=0 --json 2>/dev/null', { encoding: 'utf8' });
|
|
2251
|
+
const deps = JSON.parse(npmList).dependencies || {};
|
|
2252
|
+
const wipPkgs = Object.keys(deps).filter(n => n.startsWith('@wipcomputer/'));
|
|
2253
|
+
for (const pkg of wipPkgs) {
|
|
2254
|
+
if (pkg === '@wipcomputer/wip-ldm-os') continue; // uninstall self last
|
|
2255
|
+
try { execSync(`npm uninstall -g ${pkg}`, { stdio: 'pipe', timeout: 30000 }); } catch {}
|
|
2256
|
+
}
|
|
2257
|
+
console.log(` + Removed ${wipPkgs.length - 1} npm package(s)`);
|
|
2258
|
+
} catch {}
|
|
2259
|
+
|
|
2260
|
+
// 6. Remove directories
|
|
2261
|
+
const dirsToRemove = ['extensions', 'state', 'bin', 'hooks', 'logs', 'sessions', 'messages', 'shared', '_trash'];
|
|
2262
|
+
if (!keepData) {
|
|
2263
|
+
dirsToRemove.push('memory', 'agents', 'secrets', 'backups');
|
|
2264
|
+
}
|
|
2265
|
+
for (const dir of dirsToRemove) {
|
|
2266
|
+
const p = join(LDM_ROOT, dir);
|
|
2267
|
+
if (existsSync(p)) {
|
|
2268
|
+
execSync(`rm -rf "${p}"`, { stdio: 'pipe' });
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
// Remove config and version files
|
|
2272
|
+
for (const f of ['version.json', 'config.json']) {
|
|
2273
|
+
const p = join(LDM_ROOT, f);
|
|
2274
|
+
if (existsSync(p)) unlinkSync(p);
|
|
2275
|
+
}
|
|
2276
|
+
console.log(' + Removed ~/.ldm/ contents');
|
|
2277
|
+
|
|
2278
|
+
if (keepData) {
|
|
2279
|
+
console.log('');
|
|
2280
|
+
console.log(' Preserved:');
|
|
2281
|
+
console.log(' ~/.ldm/memory/ (your data)');
|
|
2282
|
+
console.log(' ~/.ldm/agents/ (identity + journals)');
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// 7. Self-uninstall
|
|
2286
|
+
console.log('');
|
|
2287
|
+
console.log(' To finish, run: npm uninstall -g @wipcomputer/wip-ldm-os');
|
|
2288
|
+
console.log('');
|
|
2289
|
+
console.log(' LDM OS uninstalled.');
|
|
2290
|
+
console.log('');
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2039
2293
|
if (command === '--version' || command === '-v') {
|
|
2040
2294
|
console.log(PKG_VERSION);
|
|
2041
2295
|
process.exit(0);
|
|
@@ -2079,6 +2333,9 @@ async function main() {
|
|
|
2079
2333
|
case 'disable':
|
|
2080
2334
|
await cmdDisable();
|
|
2081
2335
|
break;
|
|
2336
|
+
case 'uninstall':
|
|
2337
|
+
await cmdUninstall();
|
|
2338
|
+
break;
|
|
2082
2339
|
default:
|
|
2083
2340
|
console.error(` Unknown command: ${command}`);
|
|
2084
2341
|
console.error(` Run: ldm --help`);
|