@hanzlaa/rcode 3.4.15 → 3.4.17
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/cli/doctor.js +76 -1
- package/cli/index.js +5 -0
- package/cli/install.js +37 -5
- package/cli/nuke.js +404 -0
- package/cli/uninstall.js +28 -5
- package/cli/update.js +23 -8
- package/dist/rcode.js +466 -16
- package/package.json +1 -1
package/cli/doctor.js
CHANGED
|
@@ -259,6 +259,77 @@ function runCompliance(packageRoot) {
|
|
|
259
259
|
return failing;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
// ---------- Duplicate-installation check ----------
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Detect rcode/rihal-code installations across all known package managers.
|
|
266
|
+
* Multiple installs cause duplicate slash commands in Claude Code (#637-#644).
|
|
267
|
+
* Returns { count, installs[], warnings[] }.
|
|
268
|
+
*/
|
|
269
|
+
function checkDuplicateInstalls() {
|
|
270
|
+
const os = require('os');
|
|
271
|
+
const home = os.homedir();
|
|
272
|
+
const installs = [];
|
|
273
|
+
|
|
274
|
+
// Resolve every global node_modules dir we know about.
|
|
275
|
+
const dirs = [];
|
|
276
|
+
for (const cmd of [['npm', ['root', '-g']], ['pnpm', ['root', '-g']]]) {
|
|
277
|
+
try {
|
|
278
|
+
const r = spawnSync(cmd[0], cmd[1], { encoding: 'utf8' });
|
|
279
|
+
if (r.status === 0 && r.stdout.trim()) dirs.push({ manager: cmd[0], dir: r.stdout.trim() });
|
|
280
|
+
} catch { /* not installed */ }
|
|
281
|
+
}
|
|
282
|
+
// Also scan all nvm node versions — npm root -g only covers the active one.
|
|
283
|
+
const nvmRoot = path.join(home, '.nvm', 'versions', 'node');
|
|
284
|
+
if (fs.existsSync(nvmRoot)) {
|
|
285
|
+
for (const v of fs.readdirSync(nvmRoot)) {
|
|
286
|
+
const dir = path.join(nvmRoot, v, 'lib', 'node_modules');
|
|
287
|
+
if (fs.existsSync(dir) && !dirs.some(d => d.dir === dir)) {
|
|
288
|
+
dirs.push({ manager: `nvm/${v}`, dir });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (const { manager, dir } of dirs) {
|
|
294
|
+
for (const scope of ['@hanzlaa', '@hanzlahabib']) {
|
|
295
|
+
const scopeDir = path.join(dir, scope);
|
|
296
|
+
if (!fs.existsSync(scopeDir)) continue;
|
|
297
|
+
for (const pkg of fs.readdirSync(scopeDir)) {
|
|
298
|
+
if (pkg === 'rcode' || pkg === 'rihal-code' || pkg.startsWith('rihal')) {
|
|
299
|
+
let version = 'unknown';
|
|
300
|
+
try {
|
|
301
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(scopeDir, pkg, 'package.json'), 'utf8'));
|
|
302
|
+
version = pkgJson.version;
|
|
303
|
+
} catch {}
|
|
304
|
+
installs.push({ manager, scope, pkg, version, dir: path.join(scopeDir, pkg) });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return { count: installs.length, installs };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function printDuplicateChecks(result) {
|
|
314
|
+
if (result.count === 0) {
|
|
315
|
+
console.log(` ✓ No global rcode installs found`);
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
if (result.count === 1) {
|
|
319
|
+
const i = result.installs[0];
|
|
320
|
+
console.log(` ✓ Single global install: [${i.manager}] ${i.scope}/${i.pkg}@${i.version}`);
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
// Multiple installs — DUPLICATE — this causes the duplicate-commands bug.
|
|
324
|
+
console.log(` ✗ ${result.count} global installs detected — this causes duplicate slash commands!`);
|
|
325
|
+
for (const i of result.installs) {
|
|
326
|
+
console.log(` [${i.manager}] ${i.scope}/${i.pkg}@${i.version}`);
|
|
327
|
+
console.log(` ${i.dir}`);
|
|
328
|
+
}
|
|
329
|
+
console.log(`\n Fix: rcode nuke --yes && npm install -g @hanzlaa/rcode`);
|
|
330
|
+
return 1;
|
|
331
|
+
}
|
|
332
|
+
|
|
262
333
|
// ---------- Entrypoint ----------
|
|
263
334
|
|
|
264
335
|
module.exports = function doctor(args, { packageRoot }) {
|
|
@@ -270,10 +341,14 @@ module.exports = function doctor(args, { packageRoot }) {
|
|
|
270
341
|
const checks = runPreflight(cwd, packageRoot);
|
|
271
342
|
const preflightFailures = printChecks(checks);
|
|
272
343
|
|
|
344
|
+
console.log(`\nDuplicate installations:`);
|
|
345
|
+
const dupResult = checkDuplicateInstalls();
|
|
346
|
+
const duplicateFailures = printDuplicateChecks(dupResult);
|
|
347
|
+
|
|
273
348
|
console.log(`\nPackage compliance:`);
|
|
274
349
|
const complianceFailures = runCompliance(packageRoot);
|
|
275
350
|
|
|
276
|
-
const totalFailures = preflightFailures + complianceFailures;
|
|
351
|
+
const totalFailures = preflightFailures + complianceFailures + duplicateFailures;
|
|
277
352
|
console.log();
|
|
278
353
|
if (totalFailures === 0) {
|
|
279
354
|
console.log(`✅ All checks passed.`);
|
package/cli/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const COMMANDS = {
|
|
|
25
25
|
update: require('./update'),
|
|
26
26
|
uninstall: require('./uninstall'),
|
|
27
27
|
remove: require('./uninstall'), // alias
|
|
28
|
+
nuke: require('./nuke'), // full cleanup across all package managers + global state
|
|
28
29
|
dashboard: require('./dashboard'),
|
|
29
30
|
serve: require('./dashboard'),
|
|
30
31
|
digest: require('./digest'),
|
|
@@ -57,6 +58,10 @@ Usage:
|
|
|
57
58
|
update Refresh skill files (backs up .rihal/ state first)
|
|
58
59
|
uninstall Remove Rihal Code from the current project
|
|
59
60
|
remove Alias for uninstall
|
|
61
|
+
nuke Wipe ALL rihal/rcode installs everywhere (global packages,
|
|
62
|
+
binaries, ~/.claude/* rihal artifacts, ~/.rihal/, project artifacts)
|
|
63
|
+
Default = dry-run. Pass --yes to remove. Pass --include-planning
|
|
64
|
+
to also remove .planning/ in CWD.
|
|
60
65
|
config Get/set project configuration (project_name, user_name, etc.)
|
|
61
66
|
context Memory bank freshness (--check | --refresh | --install-hook)
|
|
62
67
|
github-sync Sync .rihal/ phases/epics/stories to GitHub (dry-run default)
|
package/cli/install.js
CHANGED
|
@@ -945,6 +945,26 @@ function buildInstallPlan(ide = 'claude', target = process.cwd()) {
|
|
|
945
945
|
}
|
|
946
946
|
}
|
|
947
947
|
}
|
|
948
|
+
// When both claude and vscode are in the IDE list, vscode writes commands to
|
|
949
|
+
// .claude/commands/rihal/{name}.md (subdirectory) while claude writes them to
|
|
950
|
+
// .claude/commands/rihal-{name}.md (root). Claude Code reads the full tree
|
|
951
|
+
// recursively, so both sets appear as slash commands — duplicates in the UI.
|
|
952
|
+
// Drop the vscode-style subdir entries when claude entries already cover them.
|
|
953
|
+
if (ide.includes('claude') && ide.includes('vscode')) {
|
|
954
|
+
const claudeCommandRels = new Set(
|
|
955
|
+
merged
|
|
956
|
+
.filter(e => e.ide === 'claude' && e.rel.split(path.sep).join('/').startsWith('.claude/commands/'))
|
|
957
|
+
.map(e => path.basename(e.rel, '.md').replace(/^rihal-/, ''))
|
|
958
|
+
);
|
|
959
|
+
return merged.filter(e => {
|
|
960
|
+
const rel = e.rel.split(path.sep).join('/');
|
|
961
|
+
if (e.ide === 'vscode' && rel.startsWith('.claude/commands/rihal/')) {
|
|
962
|
+
const baseName = path.basename(e.rel, path.extname(e.rel));
|
|
963
|
+
return !claudeCommandRels.has(baseName);
|
|
964
|
+
}
|
|
965
|
+
return true;
|
|
966
|
+
});
|
|
967
|
+
}
|
|
948
968
|
return merged;
|
|
949
969
|
}
|
|
950
970
|
|
|
@@ -1731,12 +1751,18 @@ async function install(opts) {
|
|
|
1731
1751
|
const globalClaudeCommands = path.join(os.homedir(), '.claude', 'commands');
|
|
1732
1752
|
const projectClaudeCommands = path.join(opts.target, '.claude', 'commands');
|
|
1733
1753
|
const isProjectInstall = opts.target !== os.homedir();
|
|
1734
|
-
|
|
1754
|
+
// Run dedup even when force:true — only forceOverwrite skips it.
|
|
1755
|
+
if (isProjectInstall && !opts.forceOverwrite) {
|
|
1735
1756
|
try {
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
fs.
|
|
1757
|
+
// Check both root-level rihal-*.md AND the rihal/ subdirectory (vscode-style).
|
|
1758
|
+
const globalHasRihal = fs.existsSync(globalClaudeCommands) && (
|
|
1759
|
+
fs.readdirSync(globalClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md')) ||
|
|
1760
|
+
fs.existsSync(path.join(globalClaudeCommands, 'rihal'))
|
|
1761
|
+
);
|
|
1762
|
+
const projectHasRihal = fs.existsSync(projectClaudeCommands) && (
|
|
1763
|
+
fs.readdirSync(projectClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md')) ||
|
|
1764
|
+
fs.existsSync(path.join(projectClaudeCommands, 'rihal'))
|
|
1765
|
+
);
|
|
1740
1766
|
if (globalHasRihal && !projectHasRihal) {
|
|
1741
1767
|
// Global commands exist, project has none yet — filter them out of the plan
|
|
1742
1768
|
// so we don't create duplicates. Project gets .rihal/ state only.
|
|
@@ -1754,11 +1780,17 @@ async function install(opts) {
|
|
|
1754
1780
|
} else if (globalHasRihal && projectHasRihal) {
|
|
1755
1781
|
// Both exist — project commands are duplicates. Remove project-level ones.
|
|
1756
1782
|
try {
|
|
1783
|
+
// Remove root-level rihal-*.md files
|
|
1757
1784
|
const projectCommandFiles = fs.readdirSync(projectClaudeCommands)
|
|
1758
1785
|
.filter(f => f.startsWith('rihal-') && f.endsWith('.md'));
|
|
1759
1786
|
for (const f of projectCommandFiles) {
|
|
1760
1787
|
fs.unlinkSync(path.join(projectClaudeCommands, f));
|
|
1761
1788
|
}
|
|
1789
|
+
// Remove rihal/ subdirectory (vscode-style commands)
|
|
1790
|
+
const rihalSubdir = path.join(projectClaudeCommands, 'rihal');
|
|
1791
|
+
if (fs.existsSync(rihalSubdir)) {
|
|
1792
|
+
fs.rmSync(rihalSubdir, { recursive: true, force: true });
|
|
1793
|
+
}
|
|
1762
1794
|
const projectAgentsDir = path.join(opts.target, '.claude', 'agents');
|
|
1763
1795
|
if (fs.existsSync(projectAgentsDir)) {
|
|
1764
1796
|
const agentFiles = fs.readdirSync(projectAgentsDir)
|
package/cli/nuke.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rcode nuke — one-shot full cleanup
|
|
3
|
+
*
|
|
4
|
+
* Removes EVERY trace of rihal/rcode from the system:
|
|
5
|
+
* - Global npm/pnpm/yarn/bun installs (both @hanzlaa/rcode and legacy @hanzlahabib/rihal-code)
|
|
6
|
+
* - Global binaries (rcode, rihal, rihal-code) in all known PATH dirs
|
|
7
|
+
* - Global Claude Code artifacts (~/.claude/commands/rihal*, ~/.claude/agents/rihal-*, ~/.claude/skills/rihal-*)
|
|
8
|
+
* - Global state (~/.rihal/)
|
|
9
|
+
* - Project-level artifacts in CWD (.claude/commands/rihal*, .claude/agents/rihal-*, .rihal/, .planning/ optional)
|
|
10
|
+
*
|
|
11
|
+
* Default mode: dry-run — prints what *would* be removed.
|
|
12
|
+
* Pass --yes to actually remove. Pass --include-planning to also remove .planning/ in CWD.
|
|
13
|
+
*
|
|
14
|
+
* Why this exists: gaps #1-#4 in the duplicate-commands incident (May 2026).
|
|
15
|
+
* - Multiple package managers can hold separate copies invisibly to each other.
|
|
16
|
+
* - rcode uninstall only cleans the project, not global package installs.
|
|
17
|
+
* - Users had no single command to fully reset.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const { spawnSync } = require('child_process');
|
|
26
|
+
|
|
27
|
+
function exists(p) {
|
|
28
|
+
try { fs.accessSync(p); return true; } catch { return false; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readDirSafe(p) {
|
|
32
|
+
try { return fs.readdirSync(p); } catch { return []; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve where each package manager keeps its global node_modules.
|
|
37
|
+
* Returns a list of { manager, dir } — dir may not exist.
|
|
38
|
+
*/
|
|
39
|
+
function getGlobalNodeModulesDirs() {
|
|
40
|
+
const home = os.homedir();
|
|
41
|
+
const candidates = [];
|
|
42
|
+
|
|
43
|
+
// npm — npm root -g resolves to the active node version's lib/node_modules.
|
|
44
|
+
try {
|
|
45
|
+
const r = spawnSync('npm', ['root', '-g'], { encoding: 'utf8' });
|
|
46
|
+
if (r.status === 0 && r.stdout.trim()) candidates.push({ manager: 'npm', dir: r.stdout.trim() });
|
|
47
|
+
} catch { /* npm not installed */ }
|
|
48
|
+
|
|
49
|
+
// pnpm — `pnpm root -g`
|
|
50
|
+
try {
|
|
51
|
+
const r = spawnSync('pnpm', ['root', '-g'], { encoding: 'utf8' });
|
|
52
|
+
if (r.status === 0 && r.stdout.trim()) candidates.push({ manager: 'pnpm', dir: r.stdout.trim() });
|
|
53
|
+
} catch { /* pnpm not installed */ }
|
|
54
|
+
|
|
55
|
+
// yarn classic — `yarn global dir` returns the parent; node_modules is inside.
|
|
56
|
+
try {
|
|
57
|
+
const r = spawnSync('yarn', ['global', 'dir'], { encoding: 'utf8' });
|
|
58
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
59
|
+
candidates.push({ manager: 'yarn', dir: path.join(r.stdout.trim(), 'node_modules') });
|
|
60
|
+
}
|
|
61
|
+
} catch { /* yarn not installed */ }
|
|
62
|
+
|
|
63
|
+
// bun — `bun pm bin -g` returns the bin dir; sibling install/global has node_modules.
|
|
64
|
+
try {
|
|
65
|
+
const r = spawnSync('bun', ['pm', 'bin', '-g'], { encoding: 'utf8' });
|
|
66
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
67
|
+
// bun layout: ~/.bun/install/global/node_modules + ~/.bun/bin
|
|
68
|
+
candidates.push({ manager: 'bun', dir: path.join(home, '.bun', 'install', 'global', 'node_modules') });
|
|
69
|
+
}
|
|
70
|
+
} catch { /* bun not installed */ }
|
|
71
|
+
|
|
72
|
+
// Hardcoded fallbacks for stale/dead version managers (nvm versions that npm root doesn't know about).
|
|
73
|
+
const nvmRoot = path.join(home, '.nvm', 'versions', 'node');
|
|
74
|
+
if (exists(nvmRoot)) {
|
|
75
|
+
for (const v of readDirSafe(nvmRoot)) {
|
|
76
|
+
const dir = path.join(nvmRoot, v, 'lib', 'node_modules');
|
|
77
|
+
if (exists(dir) && !candidates.some(c => c.dir === dir)) {
|
|
78
|
+
candidates.push({ manager: `nvm/${v}`, dir });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return candidates;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* For a given global node_modules dir, return a list of rihal/rcode package paths.
|
|
88
|
+
* Looks for both @hanzlaa/rcode (current) and @hanzlahabib/rihal-code (legacy).
|
|
89
|
+
*/
|
|
90
|
+
function findRihalPackages(globalNodeModules) {
|
|
91
|
+
const found = [];
|
|
92
|
+
for (const scope of ['@hanzlaa', '@hanzlahabib']) {
|
|
93
|
+
const scopeDir = path.join(globalNodeModules, scope);
|
|
94
|
+
if (!exists(scopeDir)) continue;
|
|
95
|
+
for (const pkg of readDirSafe(scopeDir)) {
|
|
96
|
+
// Match rcode, rihal-code, or anything starting with rihal
|
|
97
|
+
if (pkg === 'rcode' || pkg === 'rihal-code' || pkg.startsWith('rihal')) {
|
|
98
|
+
found.push({ scope, pkg, dir: path.join(scopeDir, pkg) });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve global bin directories where rcode/rihal/rihal-code may live.
|
|
107
|
+
*/
|
|
108
|
+
function getGlobalBinDirs() {
|
|
109
|
+
const home = os.homedir();
|
|
110
|
+
const dirs = new Set();
|
|
111
|
+
|
|
112
|
+
// npm prefix bin
|
|
113
|
+
try {
|
|
114
|
+
const r = spawnSync('npm', ['prefix', '-g'], { encoding: 'utf8' });
|
|
115
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(path.join(r.stdout.trim(), 'bin'));
|
|
116
|
+
} catch {}
|
|
117
|
+
|
|
118
|
+
// pnpm bin
|
|
119
|
+
try {
|
|
120
|
+
const r = spawnSync('pnpm', ['bin', '-g'], { encoding: 'utf8' });
|
|
121
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(r.stdout.trim());
|
|
122
|
+
} catch {}
|
|
123
|
+
|
|
124
|
+
// yarn bin
|
|
125
|
+
try {
|
|
126
|
+
const r = spawnSync('yarn', ['global', 'bin'], { encoding: 'utf8' });
|
|
127
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(r.stdout.trim());
|
|
128
|
+
} catch {}
|
|
129
|
+
|
|
130
|
+
// bun
|
|
131
|
+
try {
|
|
132
|
+
const r = spawnSync('bun', ['pm', 'bin', '-g'], { encoding: 'utf8' });
|
|
133
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(r.stdout.trim());
|
|
134
|
+
} catch {}
|
|
135
|
+
|
|
136
|
+
// Hardcoded fallbacks
|
|
137
|
+
dirs.add(path.join(home, '.local', 'share', 'pnpm'));
|
|
138
|
+
dirs.add(path.join(home, '.local', 'bin'));
|
|
139
|
+
dirs.add(path.join(home, '.bun', 'bin'));
|
|
140
|
+
|
|
141
|
+
// nvm bins
|
|
142
|
+
const nvmRoot = path.join(home, '.nvm', 'versions', 'node');
|
|
143
|
+
if (exists(nvmRoot)) {
|
|
144
|
+
for (const v of readDirSafe(nvmRoot)) {
|
|
145
|
+
dirs.add(path.join(nvmRoot, v, 'bin'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return [...dirs].filter(exists);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const RCODE_BINS = ['rcode', 'rihal', 'rihal-code'];
|
|
153
|
+
|
|
154
|
+
function findRihalBins(binDir) {
|
|
155
|
+
const found = [];
|
|
156
|
+
for (const name of RCODE_BINS) {
|
|
157
|
+
const p = path.join(binDir, name);
|
|
158
|
+
if (exists(p)) {
|
|
159
|
+
let target = null;
|
|
160
|
+
try { target = fs.readlinkSync(p); } catch {}
|
|
161
|
+
// Only flag if it's clearly ours (target points at rcode/rihal-code package).
|
|
162
|
+
// If it's not a symlink, include it anyway — bare scripts in pnpm bin etc.
|
|
163
|
+
if (!target || /rcode|rihal-code|@hanzlaa|@hanzlahabib/.test(target)) {
|
|
164
|
+
found.push({ name, path: p, target });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return found;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Find rihal-related artifacts in a Claude Code config dir (~/.claude or .claude in CWD).
|
|
173
|
+
*/
|
|
174
|
+
function findClaudeArtifacts(claudeDir) {
|
|
175
|
+
const found = [];
|
|
176
|
+
if (!exists(claudeDir)) return found;
|
|
177
|
+
|
|
178
|
+
// .claude/commands/rihal-*.md (claude-style root)
|
|
179
|
+
const cmdRoot = path.join(claudeDir, 'commands');
|
|
180
|
+
if (exists(cmdRoot)) {
|
|
181
|
+
for (const f of readDirSafe(cmdRoot)) {
|
|
182
|
+
if (f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))) {
|
|
183
|
+
found.push({ kind: 'command', path: path.join(cmdRoot, f) });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// .claude/commands/rihal/ (vscode-style subdir)
|
|
187
|
+
const rihalSubdir = path.join(cmdRoot, 'rihal');
|
|
188
|
+
if (exists(rihalSubdir)) {
|
|
189
|
+
found.push({ kind: 'commands-dir', path: rihalSubdir });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// .claude/agents/rihal-*.md
|
|
194
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
195
|
+
if (exists(agentsDir)) {
|
|
196
|
+
for (const f of readDirSafe(agentsDir)) {
|
|
197
|
+
if (f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))) {
|
|
198
|
+
found.push({ kind: 'agent', path: path.join(agentsDir, f) });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// .claude/skills/rihal-*
|
|
204
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
205
|
+
if (exists(skillsDir)) {
|
|
206
|
+
for (const d of readDirSafe(skillsDir)) {
|
|
207
|
+
if (d.startsWith('rihal-')) {
|
|
208
|
+
found.push({ kind: 'skill-dir', path: path.join(skillsDir, d) });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return found;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function buildPlan({ includePlanning }) {
|
|
217
|
+
const home = os.homedir();
|
|
218
|
+
const cwd = process.cwd();
|
|
219
|
+
const plan = {
|
|
220
|
+
packages: [],
|
|
221
|
+
bins: [],
|
|
222
|
+
globalClaude: [],
|
|
223
|
+
globalRihal: null,
|
|
224
|
+
projectClaude: [],
|
|
225
|
+
projectRihal: null,
|
|
226
|
+
projectPlanning: null,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Global node_modules packages
|
|
230
|
+
for (const { manager, dir } of getGlobalNodeModulesDirs()) {
|
|
231
|
+
for (const pkg of findRihalPackages(dir)) {
|
|
232
|
+
plan.packages.push({ manager, ...pkg });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Global binaries
|
|
237
|
+
for (const binDir of getGlobalBinDirs()) {
|
|
238
|
+
for (const bin of findRihalBins(binDir)) {
|
|
239
|
+
plan.bins.push({ binDir, ...bin });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Global Claude artifacts (~/.claude/)
|
|
244
|
+
plan.globalClaude = findClaudeArtifacts(path.join(home, '.claude'));
|
|
245
|
+
|
|
246
|
+
// Global state (~/.rihal/)
|
|
247
|
+
const globalRihal = path.join(home, '.rihal');
|
|
248
|
+
if (exists(globalRihal)) plan.globalRihal = globalRihal;
|
|
249
|
+
|
|
250
|
+
// Project-level (CWD only — never recurse, user may have many projects)
|
|
251
|
+
plan.projectClaude = findClaudeArtifacts(path.join(cwd, '.claude'));
|
|
252
|
+
const projectRihal = path.join(cwd, '.rihal');
|
|
253
|
+
if (exists(projectRihal) && cwd !== home) plan.projectRihal = projectRihal;
|
|
254
|
+
|
|
255
|
+
if (includePlanning) {
|
|
256
|
+
const projectPlanning = path.join(cwd, '.planning');
|
|
257
|
+
if (exists(projectPlanning) && cwd !== home) plan.projectPlanning = projectPlanning;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return plan;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function printPlan(plan, { dryRun }) {
|
|
264
|
+
const banner = dryRun ? '[DRY RUN — pass --yes to remove]' : '[REMOVING]';
|
|
265
|
+
console.log(`\n🔥 rcode nuke ${banner}\n`);
|
|
266
|
+
|
|
267
|
+
let total = 0;
|
|
268
|
+
const section = (title, items) => {
|
|
269
|
+
if (!items || (Array.isArray(items) && items.length === 0)) return;
|
|
270
|
+
console.log(`${title}`);
|
|
271
|
+
if (Array.isArray(items)) {
|
|
272
|
+
for (const item of items) {
|
|
273
|
+
console.log(` • ${item.description || item.path || item.dir || JSON.stringify(item)}`);
|
|
274
|
+
total++;
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
console.log(` • ${items}`);
|
|
278
|
+
total++;
|
|
279
|
+
}
|
|
280
|
+
console.log('');
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
section('📦 Global packages (node_modules):', plan.packages.map(p => ({
|
|
284
|
+
description: `[${p.manager}] ${p.scope}/${p.pkg} → ${p.dir}`,
|
|
285
|
+
})));
|
|
286
|
+
|
|
287
|
+
section('🔗 Global binaries:', plan.bins.map(b => ({
|
|
288
|
+
description: `${b.path}${b.target ? ` → ${b.target}` : ''}`,
|
|
289
|
+
})));
|
|
290
|
+
|
|
291
|
+
section('🤖 ~/.claude/ artifacts:', plan.globalClaude.map(a => ({
|
|
292
|
+
description: `[${a.kind}] ${a.path}`,
|
|
293
|
+
})));
|
|
294
|
+
|
|
295
|
+
if (plan.globalRihal) section('🗂️ ~/.rihal/ (global state):', plan.globalRihal);
|
|
296
|
+
|
|
297
|
+
section('🤖 ./.claude/ artifacts (current project):', plan.projectClaude.map(a => ({
|
|
298
|
+
description: `[${a.kind}] ${a.path}`,
|
|
299
|
+
})));
|
|
300
|
+
|
|
301
|
+
if (plan.projectRihal) section('🗂️ ./.rihal/ (project state):', plan.projectRihal);
|
|
302
|
+
if (plan.projectPlanning) section('📋 ./.planning/ (your work — only with --include-planning):', plan.projectPlanning);
|
|
303
|
+
|
|
304
|
+
if (total === 0) {
|
|
305
|
+
console.log(' ✓ nothing to remove — system is clean.\n');
|
|
306
|
+
} else {
|
|
307
|
+
console.log(` Total: ${total} item(s).\n`);
|
|
308
|
+
}
|
|
309
|
+
return total;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function rmrf(p) {
|
|
313
|
+
try { fs.rmSync(p, { recursive: true, force: true }); return true; }
|
|
314
|
+
catch (err) { console.warn(` ⚠ failed to remove ${p}: ${err.message}`); return false; }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function uninstallPackage(manager, scope, pkg) {
|
|
318
|
+
const fullName = `${scope}/${pkg}`;
|
|
319
|
+
const cmd = manager === 'pnpm' ? ['pnpm', ['remove', '-g', fullName]]
|
|
320
|
+
: manager === 'yarn' ? ['yarn', ['global', 'remove', fullName]]
|
|
321
|
+
: manager === 'bun' ? ['bun', ['remove', '-g', fullName]]
|
|
322
|
+
: ['npm', ['uninstall', '-g', fullName]];
|
|
323
|
+
try {
|
|
324
|
+
const r = spawnSync(cmd[0], cmd[1], { stdio: 'pipe', encoding: 'utf8' });
|
|
325
|
+
return r.status === 0;
|
|
326
|
+
} catch { return false; }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function executePlan(plan) {
|
|
330
|
+
let removed = 0;
|
|
331
|
+
|
|
332
|
+
// Try clean uninstall via package manager first (cleans bins automatically)
|
|
333
|
+
for (const p of plan.packages) {
|
|
334
|
+
const manager = p.manager.startsWith('nvm/') ? 'npm' : p.manager;
|
|
335
|
+
process.stdout.write(` ${manager} uninstall ${p.scope}/${p.pkg} ... `);
|
|
336
|
+
const ok = uninstallPackage(manager, p.scope, p.pkg);
|
|
337
|
+
console.log(ok ? '✓' : '⚠ failed via PM, falling back to rm');
|
|
338
|
+
if (!ok) rmrf(p.dir);
|
|
339
|
+
removed++;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Force-remove any leftover bins (broken symlinks etc.)
|
|
343
|
+
for (const b of plan.bins) {
|
|
344
|
+
if (exists(b.path)) {
|
|
345
|
+
if (rmrf(b.path)) { console.log(` ✓ removed bin ${b.path}`); removed++; }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Claude artifacts (global)
|
|
350
|
+
for (const a of plan.globalClaude) {
|
|
351
|
+
if (rmrf(a.path)) { console.log(` ✓ removed ${a.path}`); removed++; }
|
|
352
|
+
}
|
|
353
|
+
if (plan.globalRihal && rmrf(plan.globalRihal)) {
|
|
354
|
+
console.log(` ✓ removed ${plan.globalRihal}`); removed++;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Claude artifacts (project)
|
|
358
|
+
for (const a of plan.projectClaude) {
|
|
359
|
+
if (rmrf(a.path)) { console.log(` ✓ removed ${a.path}`); removed++; }
|
|
360
|
+
}
|
|
361
|
+
if (plan.projectRihal && rmrf(plan.projectRihal)) {
|
|
362
|
+
console.log(` ✓ removed ${plan.projectRihal}`); removed++;
|
|
363
|
+
}
|
|
364
|
+
if (plan.projectPlanning && rmrf(plan.projectPlanning)) {
|
|
365
|
+
console.log(` ✓ removed ${plan.projectPlanning}`); removed++;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return removed;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
module.exports = function nuke(args = []) {
|
|
372
|
+
const dryRun = !args.includes('--yes') && !args.includes('-y');
|
|
373
|
+
const includePlanning = args.includes('--include-planning');
|
|
374
|
+
|
|
375
|
+
// Safety: detect if CWD is the rcode source repo itself.
|
|
376
|
+
const cwdPkgJson = path.join(process.cwd(), 'package.json');
|
|
377
|
+
if (exists(cwdPkgJson)) {
|
|
378
|
+
try {
|
|
379
|
+
const pkg = JSON.parse(fs.readFileSync(cwdPkgJson, 'utf8'));
|
|
380
|
+
if (pkg.name === '@hanzlaa/rcode' || pkg.name === '@hanzlahabib/rihal-code') {
|
|
381
|
+
console.log('\n⚠ You are inside the rcode source repo.');
|
|
382
|
+
console.log(' Nuke would remove this repo\'s .claude/, .rihal/, and possibly .planning/.');
|
|
383
|
+
console.log(' That is your source code — almost certainly not what you want.');
|
|
384
|
+
console.log(' Run nuke from a different directory, or pass --i-know-what-im-doing to override.\n');
|
|
385
|
+
if (!args.includes('--i-know-what-im-doing')) return;
|
|
386
|
+
}
|
|
387
|
+
} catch { /* package.json unreadable, ignore */ }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const plan = buildPlan({ includePlanning });
|
|
391
|
+
const total = printPlan(plan, { dryRun });
|
|
392
|
+
|
|
393
|
+
if (total === 0 || dryRun) {
|
|
394
|
+
if (dryRun && total > 0) {
|
|
395
|
+
console.log('To actually remove these, re-run with: rcode nuke --yes');
|
|
396
|
+
console.log('To also remove .planning/ (your work): rcode nuke --yes --include-planning\n');
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log('Executing...\n');
|
|
402
|
+
const removed = executePlan(plan);
|
|
403
|
+
console.log(`\n✅ Done. Removed ${removed} item(s). Reinstall: npm install -g @hanzlaa/rcode\n`);
|
|
404
|
+
};
|
package/cli/uninstall.js
CHANGED
|
@@ -138,9 +138,17 @@ function buildPlan(cwd, editors) {
|
|
|
138
138
|
.readdirSync(skillsDir)
|
|
139
139
|
.filter((name) => name.startsWith('rihal-') || isKnownSkillName(name));
|
|
140
140
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
// Collect commands from vscode-style subdir (.claude/commands/rihal/) and
|
|
142
|
+
// claude-style root-level files (.claude/commands/rihal-*.md).
|
|
143
|
+
const commandsSubdir = path.join(cwd, '.claude/commands/rihal');
|
|
144
|
+
if (fs.existsSync(commandsSubdir)) {
|
|
145
|
+
plan.claude.commands = fs.readdirSync(commandsSubdir);
|
|
146
|
+
}
|
|
147
|
+
const commandsRoot = path.join(cwd, '.claude/commands');
|
|
148
|
+
if (fs.existsSync(commandsRoot)) {
|
|
149
|
+
const rootFiles = fs.readdirSync(commandsRoot)
|
|
150
|
+
.filter(f => f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc')));
|
|
151
|
+
plan.claude.commands = [...plan.claude.commands, ...rootFiles];
|
|
144
152
|
}
|
|
145
153
|
// v2 installs agents to .claude/agents/rihal-*.md — scan for them
|
|
146
154
|
const agentsDir = path.join(cwd, '.claude/agents');
|
|
@@ -499,11 +507,26 @@ async function runUninstall(args) {
|
|
|
499
507
|
removed += n;
|
|
500
508
|
if (n > 0) console.log(` ✓ removed ${n} Claude skills`);
|
|
501
509
|
|
|
510
|
+
// Remove vscode-style subdir .claude/commands/rihal/
|
|
502
511
|
const commandsDir = path.join(cwd, '.claude/commands/rihal');
|
|
503
512
|
if (fs.existsSync(commandsDir)) {
|
|
504
513
|
fs.rmSync(commandsDir, { recursive: true, force: true });
|
|
505
|
-
|
|
506
|
-
|
|
514
|
+
}
|
|
515
|
+
// Remove claude-style root-level rihal-*.md files
|
|
516
|
+
const commandsRoot = path.join(cwd, '.claude/commands');
|
|
517
|
+
let commandsRemoved = 0;
|
|
518
|
+
if (fs.existsSync(commandsRoot)) {
|
|
519
|
+
for (const f of fs.readdirSync(commandsRoot)) {
|
|
520
|
+
if (f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))) {
|
|
521
|
+
fs.unlinkSync(path.join(commandsRoot, f));
|
|
522
|
+
commandsRemoved++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const totalCommandsRemoved = plan.claude.commands.length;
|
|
527
|
+
removed += totalCommandsRemoved;
|
|
528
|
+
if (totalCommandsRemoved > 0) {
|
|
529
|
+
console.log(` ✓ removed ${totalCommandsRemoved} slash commands from .claude/commands/`);
|
|
507
530
|
}
|
|
508
531
|
|
|
509
532
|
// v2: .claude/agents/rihal-*.md
|
package/cli/update.js
CHANGED
|
@@ -142,9 +142,19 @@ function removeOldSkillFiles(cwd, editors) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// Remove vscode-style subdir .claude/commands/rihal/
|
|
146
|
+
const commandsSubdir = path.join(cwd, '.claude/commands/rihal');
|
|
147
|
+
if (fs.existsSync(commandsSubdir)) {
|
|
148
|
+
fs.rmSync(commandsSubdir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
// Remove claude-style root-level .claude/commands/rihal-*.md files
|
|
151
|
+
const commandsRoot = path.join(cwd, '.claude/commands');
|
|
152
|
+
if (fs.existsSync(commandsRoot)) {
|
|
153
|
+
for (const f of fs.readdirSync(commandsRoot)) {
|
|
154
|
+
if (f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))) {
|
|
155
|
+
fs.unlinkSync(path.join(commandsRoot, f));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
|
|
@@ -322,10 +332,15 @@ async function runUpdate(args, { packageRoot, packageJson }) {
|
|
|
322
332
|
// ------ Re-run unified installer ------
|
|
323
333
|
// Delegates to cli/install.js which handles all IDE-specific file shipping
|
|
324
334
|
// (agents, commands, skills, workflows, references, bin). install.js is
|
|
325
|
-
// the single source of truth —
|
|
335
|
+
// the single source of truth — call once with the full IDE array so that
|
|
336
|
+
// buildInstallPlan can deduplicate across IDEs (e.g. claude+vscode).
|
|
326
337
|
console.log();
|
|
327
|
-
|
|
328
|
-
|
|
338
|
+
const supportedIdes = editors.filter(ide => ['claude', 'cursor', 'gemini'].includes(ide));
|
|
339
|
+
if (supportedIdes.length > 0) {
|
|
340
|
+
if (editors.some(ide => !['claude', 'cursor', 'gemini'].includes(ide))) {
|
|
341
|
+
const unsupported = editors.filter(ide => !['claude', 'cursor', 'gemini'].includes(ide));
|
|
342
|
+
console.log(` ⚠ ${unsupported.join(', ')} refresh not yet supported by installer — skipping`);
|
|
343
|
+
}
|
|
329
344
|
install.install({
|
|
330
345
|
target: cwd,
|
|
331
346
|
force: true,
|
|
@@ -334,11 +349,11 @@ async function runUpdate(args, { packageRoot, packageJson }) {
|
|
|
334
349
|
projectName: config.project_name || require('path').basename(cwd),
|
|
335
350
|
language: config.language || 'English',
|
|
336
351
|
mode: config.mode || 'guided',
|
|
337
|
-
|
|
352
|
+
ides: supportedIdes,
|
|
338
353
|
modules: [],
|
|
339
354
|
help: false,
|
|
340
355
|
});
|
|
341
|
-
console.log(` ✓ ${
|
|
356
|
+
console.log(` ✓ [${supportedIdes.join(', ')}] → refreshed via install.js`);
|
|
342
357
|
}
|
|
343
358
|
|
|
344
359
|
// ------ Update installed_version in config.json (atomic) ------
|
package/dist/rcode.js
CHANGED
|
@@ -15650,6 +15650,19 @@ ${BLOCK}`);
|
|
|
15650
15650
|
}
|
|
15651
15651
|
}
|
|
15652
15652
|
}
|
|
15653
|
+
if (ide.includes("claude") && ide.includes("vscode")) {
|
|
15654
|
+
const claudeCommandRels = new Set(
|
|
15655
|
+
merged.filter((e) => e.ide === "claude" && e.rel.split(path2.sep).join("/").startsWith(".claude/commands/")).map((e) => path2.basename(e.rel, ".md").replace(/^rihal-/, ""))
|
|
15656
|
+
);
|
|
15657
|
+
return merged.filter((e) => {
|
|
15658
|
+
const rel = e.rel.split(path2.sep).join("/");
|
|
15659
|
+
if (e.ide === "vscode" && rel.startsWith(".claude/commands/rihal/")) {
|
|
15660
|
+
const baseName = path2.basename(e.rel, path2.extname(e.rel));
|
|
15661
|
+
return !claudeCommandRels.has(baseName);
|
|
15662
|
+
}
|
|
15663
|
+
return true;
|
|
15664
|
+
});
|
|
15665
|
+
}
|
|
15653
15666
|
return merged;
|
|
15654
15667
|
}
|
|
15655
15668
|
const plan = [];
|
|
@@ -16272,10 +16285,10 @@ ${BLOCK}`);
|
|
|
16272
16285
|
const globalClaudeCommands = path2.join(os.homedir(), ".claude", "commands");
|
|
16273
16286
|
const projectClaudeCommands = path2.join(opts.target, ".claude", "commands");
|
|
16274
16287
|
const isProjectInstall = opts.target !== os.homedir();
|
|
16275
|
-
if (isProjectInstall && !opts.
|
|
16288
|
+
if (isProjectInstall && !opts.forceOverwrite) {
|
|
16276
16289
|
try {
|
|
16277
|
-
const globalHasRihal = fs2.existsSync(globalClaudeCommands) && fs2.readdirSync(globalClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md"));
|
|
16278
|
-
const projectHasRihal = fs2.existsSync(projectClaudeCommands) && fs2.readdirSync(projectClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md"));
|
|
16290
|
+
const globalHasRihal = fs2.existsSync(globalClaudeCommands) && (fs2.readdirSync(globalClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md")) || fs2.existsSync(path2.join(globalClaudeCommands, "rihal")));
|
|
16291
|
+
const projectHasRihal = fs2.existsSync(projectClaudeCommands) && (fs2.readdirSync(projectClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md")) || fs2.existsSync(path2.join(projectClaudeCommands, "rihal")));
|
|
16279
16292
|
if (globalHasRihal && !projectHasRihal) {
|
|
16280
16293
|
const before = plan.length;
|
|
16281
16294
|
const filtered = plan.filter((e) => {
|
|
@@ -16294,6 +16307,10 @@ ${BLOCK}`);
|
|
|
16294
16307
|
for (const f of projectCommandFiles) {
|
|
16295
16308
|
fs2.unlinkSync(path2.join(projectClaudeCommands, f));
|
|
16296
16309
|
}
|
|
16310
|
+
const rihalSubdir = path2.join(projectClaudeCommands, "rihal");
|
|
16311
|
+
if (fs2.existsSync(rihalSubdir)) {
|
|
16312
|
+
fs2.rmSync(rihalSubdir, { recursive: true, force: true });
|
|
16313
|
+
}
|
|
16297
16314
|
const projectAgentsDir = path2.join(opts.target, ".claude", "agents");
|
|
16298
16315
|
if (fs2.existsSync(projectAgentsDir)) {
|
|
16299
16316
|
const agentFiles = fs2.readdirSync(projectAgentsDir).filter((f) => f.startsWith("rihal-") && f.endsWith(".md"));
|
|
@@ -17222,9 +17239,17 @@ var require_update = __commonJS({
|
|
|
17222
17239
|
}
|
|
17223
17240
|
}
|
|
17224
17241
|
}
|
|
17225
|
-
const
|
|
17226
|
-
if (fs2.existsSync(
|
|
17227
|
-
fs2.rmSync(
|
|
17242
|
+
const commandsSubdir = path2.join(cwd, ".claude/commands/rihal");
|
|
17243
|
+
if (fs2.existsSync(commandsSubdir)) {
|
|
17244
|
+
fs2.rmSync(commandsSubdir, { recursive: true, force: true });
|
|
17245
|
+
}
|
|
17246
|
+
const commandsRoot = path2.join(cwd, ".claude/commands");
|
|
17247
|
+
if (fs2.existsSync(commandsRoot)) {
|
|
17248
|
+
for (const f of fs2.readdirSync(commandsRoot)) {
|
|
17249
|
+
if (f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc"))) {
|
|
17250
|
+
fs2.unlinkSync(path2.join(commandsRoot, f));
|
|
17251
|
+
}
|
|
17252
|
+
}
|
|
17228
17253
|
}
|
|
17229
17254
|
}
|
|
17230
17255
|
if (editors.includes("cursor")) {
|
|
@@ -17380,8 +17405,12 @@ var require_update = __commonJS({
|
|
|
17380
17405
|
console.log(` \u{1F9F9} cleaned ${totalRemoved} old skill files`);
|
|
17381
17406
|
stripAgentsMdSection(cwd);
|
|
17382
17407
|
console.log();
|
|
17383
|
-
|
|
17384
|
-
|
|
17408
|
+
const supportedIdes = editors.filter((ide) => ["claude", "cursor", "gemini"].includes(ide));
|
|
17409
|
+
if (supportedIdes.length > 0) {
|
|
17410
|
+
if (editors.some((ide) => !["claude", "cursor", "gemini"].includes(ide))) {
|
|
17411
|
+
const unsupported = editors.filter((ide) => !["claude", "cursor", "gemini"].includes(ide));
|
|
17412
|
+
console.log(` \u26A0 ${unsupported.join(", ")} refresh not yet supported by installer \u2014 skipping`);
|
|
17413
|
+
}
|
|
17385
17414
|
install.install({
|
|
17386
17415
|
target: cwd,
|
|
17387
17416
|
force: true,
|
|
@@ -17390,11 +17419,11 @@ var require_update = __commonJS({
|
|
|
17390
17419
|
projectName: config.project_name || require("path").basename(cwd),
|
|
17391
17420
|
language: config.language || "English",
|
|
17392
17421
|
mode: config.mode || "guided",
|
|
17393
|
-
|
|
17422
|
+
ides: supportedIdes,
|
|
17394
17423
|
modules: [],
|
|
17395
17424
|
help: false
|
|
17396
17425
|
});
|
|
17397
|
-
console.log(` \u2713 ${
|
|
17426
|
+
console.log(` \u2713 [${supportedIdes.join(", ")}] \u2192 refreshed via install.js`);
|
|
17398
17427
|
}
|
|
17399
17428
|
config.installed_version = packageVersion;
|
|
17400
17429
|
writeJsonAtomic(configPath, config);
|
|
@@ -17507,9 +17536,14 @@ var require_uninstall = __commonJS({
|
|
|
17507
17536
|
if (fs2.existsSync(skillsDir)) {
|
|
17508
17537
|
plan.claude.skills = fs2.readdirSync(skillsDir).filter((name) => name.startsWith("rihal-") || isKnownSkillName(name));
|
|
17509
17538
|
}
|
|
17510
|
-
const
|
|
17511
|
-
if (fs2.existsSync(
|
|
17512
|
-
plan.claude.commands = fs2.readdirSync(
|
|
17539
|
+
const commandsSubdir = path2.join(cwd, ".claude/commands/rihal");
|
|
17540
|
+
if (fs2.existsSync(commandsSubdir)) {
|
|
17541
|
+
plan.claude.commands = fs2.readdirSync(commandsSubdir);
|
|
17542
|
+
}
|
|
17543
|
+
const commandsRoot = path2.join(cwd, ".claude/commands");
|
|
17544
|
+
if (fs2.existsSync(commandsRoot)) {
|
|
17545
|
+
const rootFiles = fs2.readdirSync(commandsRoot).filter((f) => f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc")));
|
|
17546
|
+
plan.claude.commands = [...plan.claude.commands, ...rootFiles];
|
|
17513
17547
|
}
|
|
17514
17548
|
const agentsDir = path2.join(cwd, ".claude/agents");
|
|
17515
17549
|
if (fs2.existsSync(agentsDir)) {
|
|
@@ -17782,8 +17816,21 @@ var require_uninstall = __commonJS({
|
|
|
17782
17816
|
const commandsDir = path2.join(cwd, ".claude/commands/rihal");
|
|
17783
17817
|
if (fs2.existsSync(commandsDir)) {
|
|
17784
17818
|
fs2.rmSync(commandsDir, { recursive: true, force: true });
|
|
17785
|
-
|
|
17786
|
-
|
|
17819
|
+
}
|
|
17820
|
+
const commandsRoot = path2.join(cwd, ".claude/commands");
|
|
17821
|
+
let commandsRemoved = 0;
|
|
17822
|
+
if (fs2.existsSync(commandsRoot)) {
|
|
17823
|
+
for (const f of fs2.readdirSync(commandsRoot)) {
|
|
17824
|
+
if (f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc"))) {
|
|
17825
|
+
fs2.unlinkSync(path2.join(commandsRoot, f));
|
|
17826
|
+
commandsRemoved++;
|
|
17827
|
+
}
|
|
17828
|
+
}
|
|
17829
|
+
}
|
|
17830
|
+
const totalCommandsRemoved = plan.claude.commands.length;
|
|
17831
|
+
removed += totalCommandsRemoved;
|
|
17832
|
+
if (totalCommandsRemoved > 0) {
|
|
17833
|
+
console.log(` \u2713 removed ${totalCommandsRemoved} slash commands from .claude/commands/`);
|
|
17787
17834
|
}
|
|
17788
17835
|
const agentsDir = path2.join(cwd, ".claude/agents");
|
|
17789
17836
|
const nAgents = removeMatching(
|
|
@@ -17929,6 +17976,340 @@ To reinstall later:`);
|
|
|
17929
17976
|
}
|
|
17930
17977
|
});
|
|
17931
17978
|
|
|
17979
|
+
// cli/nuke.js
|
|
17980
|
+
var require_nuke = __commonJS({
|
|
17981
|
+
"cli/nuke.js"(exports2, module2) {
|
|
17982
|
+
"use strict";
|
|
17983
|
+
var fs2 = require("fs");
|
|
17984
|
+
var os = require("os");
|
|
17985
|
+
var path2 = require("path");
|
|
17986
|
+
var { spawnSync } = require("child_process");
|
|
17987
|
+
function exists(p) {
|
|
17988
|
+
try {
|
|
17989
|
+
fs2.accessSync(p);
|
|
17990
|
+
return true;
|
|
17991
|
+
} catch {
|
|
17992
|
+
return false;
|
|
17993
|
+
}
|
|
17994
|
+
}
|
|
17995
|
+
function readDirSafe(p) {
|
|
17996
|
+
try {
|
|
17997
|
+
return fs2.readdirSync(p);
|
|
17998
|
+
} catch {
|
|
17999
|
+
return [];
|
|
18000
|
+
}
|
|
18001
|
+
}
|
|
18002
|
+
function getGlobalNodeModulesDirs() {
|
|
18003
|
+
const home = os.homedir();
|
|
18004
|
+
const candidates = [];
|
|
18005
|
+
try {
|
|
18006
|
+
const r = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
|
|
18007
|
+
if (r.status === 0 && r.stdout.trim()) candidates.push({ manager: "npm", dir: r.stdout.trim() });
|
|
18008
|
+
} catch {
|
|
18009
|
+
}
|
|
18010
|
+
try {
|
|
18011
|
+
const r = spawnSync("pnpm", ["root", "-g"], { encoding: "utf8" });
|
|
18012
|
+
if (r.status === 0 && r.stdout.trim()) candidates.push({ manager: "pnpm", dir: r.stdout.trim() });
|
|
18013
|
+
} catch {
|
|
18014
|
+
}
|
|
18015
|
+
try {
|
|
18016
|
+
const r = spawnSync("yarn", ["global", "dir"], { encoding: "utf8" });
|
|
18017
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
18018
|
+
candidates.push({ manager: "yarn", dir: path2.join(r.stdout.trim(), "node_modules") });
|
|
18019
|
+
}
|
|
18020
|
+
} catch {
|
|
18021
|
+
}
|
|
18022
|
+
try {
|
|
18023
|
+
const r = spawnSync("bun", ["pm", "bin", "-g"], { encoding: "utf8" });
|
|
18024
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
18025
|
+
candidates.push({ manager: "bun", dir: path2.join(home, ".bun", "install", "global", "node_modules") });
|
|
18026
|
+
}
|
|
18027
|
+
} catch {
|
|
18028
|
+
}
|
|
18029
|
+
const nvmRoot = path2.join(home, ".nvm", "versions", "node");
|
|
18030
|
+
if (exists(nvmRoot)) {
|
|
18031
|
+
for (const v of readDirSafe(nvmRoot)) {
|
|
18032
|
+
const dir = path2.join(nvmRoot, v, "lib", "node_modules");
|
|
18033
|
+
if (exists(dir) && !candidates.some((c) => c.dir === dir)) {
|
|
18034
|
+
candidates.push({ manager: `nvm/${v}`, dir });
|
|
18035
|
+
}
|
|
18036
|
+
}
|
|
18037
|
+
}
|
|
18038
|
+
return candidates;
|
|
18039
|
+
}
|
|
18040
|
+
function findRihalPackages(globalNodeModules) {
|
|
18041
|
+
const found = [];
|
|
18042
|
+
for (const scope of ["@hanzlaa", "@hanzlahabib"]) {
|
|
18043
|
+
const scopeDir = path2.join(globalNodeModules, scope);
|
|
18044
|
+
if (!exists(scopeDir)) continue;
|
|
18045
|
+
for (const pkg of readDirSafe(scopeDir)) {
|
|
18046
|
+
if (pkg === "rcode" || pkg === "rihal-code" || pkg.startsWith("rihal")) {
|
|
18047
|
+
found.push({ scope, pkg, dir: path2.join(scopeDir, pkg) });
|
|
18048
|
+
}
|
|
18049
|
+
}
|
|
18050
|
+
}
|
|
18051
|
+
return found;
|
|
18052
|
+
}
|
|
18053
|
+
function getGlobalBinDirs() {
|
|
18054
|
+
const home = os.homedir();
|
|
18055
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
18056
|
+
try {
|
|
18057
|
+
const r = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8" });
|
|
18058
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(path2.join(r.stdout.trim(), "bin"));
|
|
18059
|
+
} catch {
|
|
18060
|
+
}
|
|
18061
|
+
try {
|
|
18062
|
+
const r = spawnSync("pnpm", ["bin", "-g"], { encoding: "utf8" });
|
|
18063
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(r.stdout.trim());
|
|
18064
|
+
} catch {
|
|
18065
|
+
}
|
|
18066
|
+
try {
|
|
18067
|
+
const r = spawnSync("yarn", ["global", "bin"], { encoding: "utf8" });
|
|
18068
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(r.stdout.trim());
|
|
18069
|
+
} catch {
|
|
18070
|
+
}
|
|
18071
|
+
try {
|
|
18072
|
+
const r = spawnSync("bun", ["pm", "bin", "-g"], { encoding: "utf8" });
|
|
18073
|
+
if (r.status === 0 && r.stdout.trim()) dirs.add(r.stdout.trim());
|
|
18074
|
+
} catch {
|
|
18075
|
+
}
|
|
18076
|
+
dirs.add(path2.join(home, ".local", "share", "pnpm"));
|
|
18077
|
+
dirs.add(path2.join(home, ".local", "bin"));
|
|
18078
|
+
dirs.add(path2.join(home, ".bun", "bin"));
|
|
18079
|
+
const nvmRoot = path2.join(home, ".nvm", "versions", "node");
|
|
18080
|
+
if (exists(nvmRoot)) {
|
|
18081
|
+
for (const v of readDirSafe(nvmRoot)) {
|
|
18082
|
+
dirs.add(path2.join(nvmRoot, v, "bin"));
|
|
18083
|
+
}
|
|
18084
|
+
}
|
|
18085
|
+
return [...dirs].filter(exists);
|
|
18086
|
+
}
|
|
18087
|
+
var RCODE_BINS = ["rcode", "rihal", "rihal-code"];
|
|
18088
|
+
function findRihalBins(binDir) {
|
|
18089
|
+
const found = [];
|
|
18090
|
+
for (const name of RCODE_BINS) {
|
|
18091
|
+
const p = path2.join(binDir, name);
|
|
18092
|
+
if (exists(p)) {
|
|
18093
|
+
let target = null;
|
|
18094
|
+
try {
|
|
18095
|
+
target = fs2.readlinkSync(p);
|
|
18096
|
+
} catch {
|
|
18097
|
+
}
|
|
18098
|
+
if (!target || /rcode|rihal-code|@hanzlaa|@hanzlahabib/.test(target)) {
|
|
18099
|
+
found.push({ name, path: p, target });
|
|
18100
|
+
}
|
|
18101
|
+
}
|
|
18102
|
+
}
|
|
18103
|
+
return found;
|
|
18104
|
+
}
|
|
18105
|
+
function findClaudeArtifacts(claudeDir) {
|
|
18106
|
+
const found = [];
|
|
18107
|
+
if (!exists(claudeDir)) return found;
|
|
18108
|
+
const cmdRoot = path2.join(claudeDir, "commands");
|
|
18109
|
+
if (exists(cmdRoot)) {
|
|
18110
|
+
for (const f of readDirSafe(cmdRoot)) {
|
|
18111
|
+
if (f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc"))) {
|
|
18112
|
+
found.push({ kind: "command", path: path2.join(cmdRoot, f) });
|
|
18113
|
+
}
|
|
18114
|
+
}
|
|
18115
|
+
const rihalSubdir = path2.join(cmdRoot, "rihal");
|
|
18116
|
+
if (exists(rihalSubdir)) {
|
|
18117
|
+
found.push({ kind: "commands-dir", path: rihalSubdir });
|
|
18118
|
+
}
|
|
18119
|
+
}
|
|
18120
|
+
const agentsDir = path2.join(claudeDir, "agents");
|
|
18121
|
+
if (exists(agentsDir)) {
|
|
18122
|
+
for (const f of readDirSafe(agentsDir)) {
|
|
18123
|
+
if (f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc"))) {
|
|
18124
|
+
found.push({ kind: "agent", path: path2.join(agentsDir, f) });
|
|
18125
|
+
}
|
|
18126
|
+
}
|
|
18127
|
+
}
|
|
18128
|
+
const skillsDir = path2.join(claudeDir, "skills");
|
|
18129
|
+
if (exists(skillsDir)) {
|
|
18130
|
+
for (const d of readDirSafe(skillsDir)) {
|
|
18131
|
+
if (d.startsWith("rihal-")) {
|
|
18132
|
+
found.push({ kind: "skill-dir", path: path2.join(skillsDir, d) });
|
|
18133
|
+
}
|
|
18134
|
+
}
|
|
18135
|
+
}
|
|
18136
|
+
return found;
|
|
18137
|
+
}
|
|
18138
|
+
function buildPlan({ includePlanning }) {
|
|
18139
|
+
const home = os.homedir();
|
|
18140
|
+
const cwd = process.cwd();
|
|
18141
|
+
const plan = {
|
|
18142
|
+
packages: [],
|
|
18143
|
+
bins: [],
|
|
18144
|
+
globalClaude: [],
|
|
18145
|
+
globalRihal: null,
|
|
18146
|
+
projectClaude: [],
|
|
18147
|
+
projectRihal: null,
|
|
18148
|
+
projectPlanning: null
|
|
18149
|
+
};
|
|
18150
|
+
for (const { manager, dir } of getGlobalNodeModulesDirs()) {
|
|
18151
|
+
for (const pkg of findRihalPackages(dir)) {
|
|
18152
|
+
plan.packages.push({ manager, ...pkg });
|
|
18153
|
+
}
|
|
18154
|
+
}
|
|
18155
|
+
for (const binDir of getGlobalBinDirs()) {
|
|
18156
|
+
for (const bin of findRihalBins(binDir)) {
|
|
18157
|
+
plan.bins.push({ binDir, ...bin });
|
|
18158
|
+
}
|
|
18159
|
+
}
|
|
18160
|
+
plan.globalClaude = findClaudeArtifacts(path2.join(home, ".claude"));
|
|
18161
|
+
const globalRihal = path2.join(home, ".rihal");
|
|
18162
|
+
if (exists(globalRihal)) plan.globalRihal = globalRihal;
|
|
18163
|
+
plan.projectClaude = findClaudeArtifacts(path2.join(cwd, ".claude"));
|
|
18164
|
+
const projectRihal = path2.join(cwd, ".rihal");
|
|
18165
|
+
if (exists(projectRihal) && cwd !== home) plan.projectRihal = projectRihal;
|
|
18166
|
+
if (includePlanning) {
|
|
18167
|
+
const projectPlanning = path2.join(cwd, ".planning");
|
|
18168
|
+
if (exists(projectPlanning) && cwd !== home) plan.projectPlanning = projectPlanning;
|
|
18169
|
+
}
|
|
18170
|
+
return plan;
|
|
18171
|
+
}
|
|
18172
|
+
function printPlan(plan, { dryRun }) {
|
|
18173
|
+
const banner = dryRun ? "[DRY RUN \u2014 pass --yes to remove]" : "[REMOVING]";
|
|
18174
|
+
console.log(`
|
|
18175
|
+
\u{1F525} rcode nuke ${banner}
|
|
18176
|
+
`);
|
|
18177
|
+
let total = 0;
|
|
18178
|
+
const section = (title, items) => {
|
|
18179
|
+
if (!items || Array.isArray(items) && items.length === 0) return;
|
|
18180
|
+
console.log(`${title}`);
|
|
18181
|
+
if (Array.isArray(items)) {
|
|
18182
|
+
for (const item of items) {
|
|
18183
|
+
console.log(` \u2022 ${item.description || item.path || item.dir || JSON.stringify(item)}`);
|
|
18184
|
+
total++;
|
|
18185
|
+
}
|
|
18186
|
+
} else {
|
|
18187
|
+
console.log(` \u2022 ${items}`);
|
|
18188
|
+
total++;
|
|
18189
|
+
}
|
|
18190
|
+
console.log("");
|
|
18191
|
+
};
|
|
18192
|
+
section("\u{1F4E6} Global packages (node_modules):", plan.packages.map((p) => ({
|
|
18193
|
+
description: `[${p.manager}] ${p.scope}/${p.pkg} \u2192 ${p.dir}`
|
|
18194
|
+
})));
|
|
18195
|
+
section("\u{1F517} Global binaries:", plan.bins.map((b) => ({
|
|
18196
|
+
description: `${b.path}${b.target ? ` \u2192 ${b.target}` : ""}`
|
|
18197
|
+
})));
|
|
18198
|
+
section("\u{1F916} ~/.claude/ artifacts:", plan.globalClaude.map((a) => ({
|
|
18199
|
+
description: `[${a.kind}] ${a.path}`
|
|
18200
|
+
})));
|
|
18201
|
+
if (plan.globalRihal) section("\u{1F5C2}\uFE0F ~/.rihal/ (global state):", plan.globalRihal);
|
|
18202
|
+
section("\u{1F916} ./.claude/ artifacts (current project):", plan.projectClaude.map((a) => ({
|
|
18203
|
+
description: `[${a.kind}] ${a.path}`
|
|
18204
|
+
})));
|
|
18205
|
+
if (plan.projectRihal) section("\u{1F5C2}\uFE0F ./.rihal/ (project state):", plan.projectRihal);
|
|
18206
|
+
if (plan.projectPlanning) section("\u{1F4CB} ./.planning/ (your work \u2014 only with --include-planning):", plan.projectPlanning);
|
|
18207
|
+
if (total === 0) {
|
|
18208
|
+
console.log(" \u2713 nothing to remove \u2014 system is clean.\n");
|
|
18209
|
+
} else {
|
|
18210
|
+
console.log(` Total: ${total} item(s).
|
|
18211
|
+
`);
|
|
18212
|
+
}
|
|
18213
|
+
return total;
|
|
18214
|
+
}
|
|
18215
|
+
function rmrf(p) {
|
|
18216
|
+
try {
|
|
18217
|
+
fs2.rmSync(p, { recursive: true, force: true });
|
|
18218
|
+
return true;
|
|
18219
|
+
} catch (err) {
|
|
18220
|
+
console.warn(` \u26A0 failed to remove ${p}: ${err.message}`);
|
|
18221
|
+
return false;
|
|
18222
|
+
}
|
|
18223
|
+
}
|
|
18224
|
+
function uninstallPackage(manager, scope, pkg) {
|
|
18225
|
+
const fullName = `${scope}/${pkg}`;
|
|
18226
|
+
const cmd = manager === "pnpm" ? ["pnpm", ["remove", "-g", fullName]] : manager === "yarn" ? ["yarn", ["global", "remove", fullName]] : manager === "bun" ? ["bun", ["remove", "-g", fullName]] : ["npm", ["uninstall", "-g", fullName]];
|
|
18227
|
+
try {
|
|
18228
|
+
const r = spawnSync(cmd[0], cmd[1], { stdio: "pipe", encoding: "utf8" });
|
|
18229
|
+
return r.status === 0;
|
|
18230
|
+
} catch {
|
|
18231
|
+
return false;
|
|
18232
|
+
}
|
|
18233
|
+
}
|
|
18234
|
+
function executePlan(plan) {
|
|
18235
|
+
let removed = 0;
|
|
18236
|
+
for (const p of plan.packages) {
|
|
18237
|
+
const manager = p.manager.startsWith("nvm/") ? "npm" : p.manager;
|
|
18238
|
+
process.stdout.write(` ${manager} uninstall ${p.scope}/${p.pkg} ... `);
|
|
18239
|
+
const ok = uninstallPackage(manager, p.scope, p.pkg);
|
|
18240
|
+
console.log(ok ? "\u2713" : "\u26A0 failed via PM, falling back to rm");
|
|
18241
|
+
if (!ok) rmrf(p.dir);
|
|
18242
|
+
removed++;
|
|
18243
|
+
}
|
|
18244
|
+
for (const b of plan.bins) {
|
|
18245
|
+
if (exists(b.path)) {
|
|
18246
|
+
if (rmrf(b.path)) {
|
|
18247
|
+
console.log(` \u2713 removed bin ${b.path}`);
|
|
18248
|
+
removed++;
|
|
18249
|
+
}
|
|
18250
|
+
}
|
|
18251
|
+
}
|
|
18252
|
+
for (const a of plan.globalClaude) {
|
|
18253
|
+
if (rmrf(a.path)) {
|
|
18254
|
+
console.log(` \u2713 removed ${a.path}`);
|
|
18255
|
+
removed++;
|
|
18256
|
+
}
|
|
18257
|
+
}
|
|
18258
|
+
if (plan.globalRihal && rmrf(plan.globalRihal)) {
|
|
18259
|
+
console.log(` \u2713 removed ${plan.globalRihal}`);
|
|
18260
|
+
removed++;
|
|
18261
|
+
}
|
|
18262
|
+
for (const a of plan.projectClaude) {
|
|
18263
|
+
if (rmrf(a.path)) {
|
|
18264
|
+
console.log(` \u2713 removed ${a.path}`);
|
|
18265
|
+
removed++;
|
|
18266
|
+
}
|
|
18267
|
+
}
|
|
18268
|
+
if (plan.projectRihal && rmrf(plan.projectRihal)) {
|
|
18269
|
+
console.log(` \u2713 removed ${plan.projectRihal}`);
|
|
18270
|
+
removed++;
|
|
18271
|
+
}
|
|
18272
|
+
if (plan.projectPlanning && rmrf(plan.projectPlanning)) {
|
|
18273
|
+
console.log(` \u2713 removed ${plan.projectPlanning}`);
|
|
18274
|
+
removed++;
|
|
18275
|
+
}
|
|
18276
|
+
return removed;
|
|
18277
|
+
}
|
|
18278
|
+
module2.exports = function nuke(args = []) {
|
|
18279
|
+
const dryRun = !args.includes("--yes") && !args.includes("-y");
|
|
18280
|
+
const includePlanning = args.includes("--include-planning");
|
|
18281
|
+
const cwdPkgJson = path2.join(process.cwd(), "package.json");
|
|
18282
|
+
if (exists(cwdPkgJson)) {
|
|
18283
|
+
try {
|
|
18284
|
+
const pkg = JSON.parse(fs2.readFileSync(cwdPkgJson, "utf8"));
|
|
18285
|
+
if (pkg.name === "@hanzlaa/rcode" || pkg.name === "@hanzlahabib/rihal-code") {
|
|
18286
|
+
console.log("\n\u26A0 You are inside the rcode source repo.");
|
|
18287
|
+
console.log(" Nuke would remove this repo's .claude/, .rihal/, and possibly .planning/.");
|
|
18288
|
+
console.log(" That is your source code \u2014 almost certainly not what you want.");
|
|
18289
|
+
console.log(" Run nuke from a different directory, or pass --i-know-what-im-doing to override.\n");
|
|
18290
|
+
if (!args.includes("--i-know-what-im-doing")) return;
|
|
18291
|
+
}
|
|
18292
|
+
} catch {
|
|
18293
|
+
}
|
|
18294
|
+
}
|
|
18295
|
+
const plan = buildPlan({ includePlanning });
|
|
18296
|
+
const total = printPlan(plan, { dryRun });
|
|
18297
|
+
if (total === 0 || dryRun) {
|
|
18298
|
+
if (dryRun && total > 0) {
|
|
18299
|
+
console.log("To actually remove these, re-run with: rcode nuke --yes");
|
|
18300
|
+
console.log("To also remove .planning/ (your work): rcode nuke --yes --include-planning\n");
|
|
18301
|
+
}
|
|
18302
|
+
return;
|
|
18303
|
+
}
|
|
18304
|
+
console.log("Executing...\n");
|
|
18305
|
+
const removed = executePlan(plan);
|
|
18306
|
+
console.log(`
|
|
18307
|
+
\u2705 Done. Removed ${removed} item(s). Reinstall: npm install -g @hanzlaa/rcode
|
|
18308
|
+
`);
|
|
18309
|
+
};
|
|
18310
|
+
}
|
|
18311
|
+
});
|
|
18312
|
+
|
|
17932
18313
|
// cli/dashboard.js
|
|
17933
18314
|
var require_dashboard = __commonJS({
|
|
17934
18315
|
"cli/dashboard.js"(exports2, module2) {
|
|
@@ -18483,6 +18864,65 @@ var require_doctor = __commonJS({
|
|
|
18483
18864
|
}
|
|
18484
18865
|
return failing;
|
|
18485
18866
|
}
|
|
18867
|
+
function checkDuplicateInstalls() {
|
|
18868
|
+
const os = require("os");
|
|
18869
|
+
const home = os.homedir();
|
|
18870
|
+
const installs = [];
|
|
18871
|
+
const dirs = [];
|
|
18872
|
+
for (const cmd of [["npm", ["root", "-g"]], ["pnpm", ["root", "-g"]]]) {
|
|
18873
|
+
try {
|
|
18874
|
+
const r = spawnSync(cmd[0], cmd[1], { encoding: "utf8" });
|
|
18875
|
+
if (r.status === 0 && r.stdout.trim()) dirs.push({ manager: cmd[0], dir: r.stdout.trim() });
|
|
18876
|
+
} catch {
|
|
18877
|
+
}
|
|
18878
|
+
}
|
|
18879
|
+
const nvmRoot = path2.join(home, ".nvm", "versions", "node");
|
|
18880
|
+
if (fs2.existsSync(nvmRoot)) {
|
|
18881
|
+
for (const v of fs2.readdirSync(nvmRoot)) {
|
|
18882
|
+
const dir = path2.join(nvmRoot, v, "lib", "node_modules");
|
|
18883
|
+
if (fs2.existsSync(dir) && !dirs.some((d) => d.dir === dir)) {
|
|
18884
|
+
dirs.push({ manager: `nvm/${v}`, dir });
|
|
18885
|
+
}
|
|
18886
|
+
}
|
|
18887
|
+
}
|
|
18888
|
+
for (const { manager, dir } of dirs) {
|
|
18889
|
+
for (const scope of ["@hanzlaa", "@hanzlahabib"]) {
|
|
18890
|
+
const scopeDir = path2.join(dir, scope);
|
|
18891
|
+
if (!fs2.existsSync(scopeDir)) continue;
|
|
18892
|
+
for (const pkg of fs2.readdirSync(scopeDir)) {
|
|
18893
|
+
if (pkg === "rcode" || pkg === "rihal-code" || pkg.startsWith("rihal")) {
|
|
18894
|
+
let version = "unknown";
|
|
18895
|
+
try {
|
|
18896
|
+
const pkgJson = JSON.parse(fs2.readFileSync(path2.join(scopeDir, pkg, "package.json"), "utf8"));
|
|
18897
|
+
version = pkgJson.version;
|
|
18898
|
+
} catch {
|
|
18899
|
+
}
|
|
18900
|
+
installs.push({ manager, scope, pkg, version, dir: path2.join(scopeDir, pkg) });
|
|
18901
|
+
}
|
|
18902
|
+
}
|
|
18903
|
+
}
|
|
18904
|
+
}
|
|
18905
|
+
return { count: installs.length, installs };
|
|
18906
|
+
}
|
|
18907
|
+
function printDuplicateChecks(result) {
|
|
18908
|
+
if (result.count === 0) {
|
|
18909
|
+
console.log(` \u2713 No global rcode installs found`);
|
|
18910
|
+
return 0;
|
|
18911
|
+
}
|
|
18912
|
+
if (result.count === 1) {
|
|
18913
|
+
const i = result.installs[0];
|
|
18914
|
+
console.log(` \u2713 Single global install: [${i.manager}] ${i.scope}/${i.pkg}@${i.version}`);
|
|
18915
|
+
return 0;
|
|
18916
|
+
}
|
|
18917
|
+
console.log(` \u2717 ${result.count} global installs detected \u2014 this causes duplicate slash commands!`);
|
|
18918
|
+
for (const i of result.installs) {
|
|
18919
|
+
console.log(` [${i.manager}] ${i.scope}/${i.pkg}@${i.version}`);
|
|
18920
|
+
console.log(` ${i.dir}`);
|
|
18921
|
+
}
|
|
18922
|
+
console.log(`
|
|
18923
|
+
Fix: rcode nuke --yes && npm install -g @hanzlaa/rcode`);
|
|
18924
|
+
return 1;
|
|
18925
|
+
}
|
|
18486
18926
|
module2.exports = function doctor(args, { packageRoot }) {
|
|
18487
18927
|
const cwd = process.cwd();
|
|
18488
18928
|
console.log(`
|
|
@@ -18492,9 +18932,13 @@ var require_doctor = __commonJS({
|
|
|
18492
18932
|
const checks = runPreflight(cwd, packageRoot);
|
|
18493
18933
|
const preflightFailures = printChecks(checks);
|
|
18494
18934
|
console.log(`
|
|
18935
|
+
Duplicate installations:`);
|
|
18936
|
+
const dupResult = checkDuplicateInstalls();
|
|
18937
|
+
const duplicateFailures = printDuplicateChecks(dupResult);
|
|
18938
|
+
console.log(`
|
|
18495
18939
|
Package compliance:`);
|
|
18496
18940
|
const complianceFailures = runCompliance(packageRoot);
|
|
18497
|
-
const totalFailures = preflightFailures + complianceFailures;
|
|
18941
|
+
const totalFailures = preflightFailures + complianceFailures + duplicateFailures;
|
|
18498
18942
|
console.log();
|
|
18499
18943
|
if (totalFailures === 0) {
|
|
18500
18944
|
console.log(`\u2705 All checks passed.`);
|
|
@@ -20436,6 +20880,8 @@ var COMMANDS = {
|
|
|
20436
20880
|
uninstall: require_uninstall(),
|
|
20437
20881
|
remove: require_uninstall(),
|
|
20438
20882
|
// alias
|
|
20883
|
+
nuke: require_nuke(),
|
|
20884
|
+
// full cleanup across all package managers + global state
|
|
20439
20885
|
dashboard: require_dashboard(),
|
|
20440
20886
|
serve: require_dashboard(),
|
|
20441
20887
|
digest: require_digest(),
|
|
@@ -20467,6 +20913,10 @@ Usage:
|
|
|
20467
20913
|
update Refresh skill files (backs up .rihal/ state first)
|
|
20468
20914
|
uninstall Remove Rihal Code from the current project
|
|
20469
20915
|
remove Alias for uninstall
|
|
20916
|
+
nuke Wipe ALL rihal/rcode installs everywhere (global packages,
|
|
20917
|
+
binaries, ~/.claude/* rihal artifacts, ~/.rihal/, project artifacts)
|
|
20918
|
+
Default = dry-run. Pass --yes to remove. Pass --include-planning
|
|
20919
|
+
to also remove .planning/ in CWD.
|
|
20470
20920
|
config Get/set project configuration (project_name, user_name, etc.)
|
|
20471
20921
|
context Memory bank freshness (--check | --refresh | --install-hook)
|
|
20472
20922
|
github-sync Sync .rihal/ phases/epics/stories to GitHub (dry-run default)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.17",
|
|
4
4
|
"description": "rcode — the memory bank for AI-driven SaaS teams. Persistent project context, distinctive engineering personas, and phase-based workflows. Built by Rihal. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|