@hanzlaa/rcode 3.4.13 → 3.4.15

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/install.js CHANGED
@@ -1724,6 +1724,61 @@ async function install(opts) {
1724
1724
  return 0;
1725
1725
  }
1726
1726
 
1727
+ // Duplicate-prevention: if rihal commands already exist globally in ~/.claude/commands/,
1728
+ // skip writing agents/commands to the project's .claude/ directory. Without this,
1729
+ // running `npx rcode install` in the home dir AND then in a project creates two sets
1730
+ // of identical files — Claude Code shows both as duplicate slash commands.
1731
+ const globalClaudeCommands = path.join(os.homedir(), '.claude', 'commands');
1732
+ const projectClaudeCommands = path.join(opts.target, '.claude', 'commands');
1733
+ const isProjectInstall = opts.target !== os.homedir();
1734
+ if (isProjectInstall && !opts.force && !opts.forceOverwrite) {
1735
+ try {
1736
+ const globalHasRihal = fs.existsSync(globalClaudeCommands) &&
1737
+ fs.readdirSync(globalClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md'));
1738
+ const projectHasRihal = fs.existsSync(projectClaudeCommands) &&
1739
+ fs.readdirSync(projectClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md'));
1740
+ if (globalHasRihal && !projectHasRihal) {
1741
+ // Global commands exist, project has none yet — filter them out of the plan
1742
+ // so we don't create duplicates. Project gets .rihal/ state only.
1743
+ const before = plan.length;
1744
+ const filtered = plan.filter(e => {
1745
+ const rel = e.rel.split(path.sep).join('/');
1746
+ return !rel.startsWith('.claude/commands/') && !rel.startsWith('.claude/agents/');
1747
+ });
1748
+ if (filtered.length < before) {
1749
+ plan.length = 0;
1750
+ filtered.forEach(e => plan.push(e));
1751
+ console.log(' ' + dim('Global rihal commands detected in ~/.claude/ — skipping project-level agent/command install to avoid duplicates.'));
1752
+ console.log(' ' + dim('Use --force-overwrite to install locally anyway.'));
1753
+ }
1754
+ } else if (globalHasRihal && projectHasRihal) {
1755
+ // Both exist — project commands are duplicates. Remove project-level ones.
1756
+ try {
1757
+ const projectCommandFiles = fs.readdirSync(projectClaudeCommands)
1758
+ .filter(f => f.startsWith('rihal-') && f.endsWith('.md'));
1759
+ for (const f of projectCommandFiles) {
1760
+ fs.unlinkSync(path.join(projectClaudeCommands, f));
1761
+ }
1762
+ const projectAgentsDir = path.join(opts.target, '.claude', 'agents');
1763
+ if (fs.existsSync(projectAgentsDir)) {
1764
+ const agentFiles = fs.readdirSync(projectAgentsDir)
1765
+ .filter(f => f.startsWith('rihal-') && f.endsWith('.md'));
1766
+ for (const f of agentFiles) {
1767
+ fs.unlinkSync(path.join(projectAgentsDir, f));
1768
+ }
1769
+ }
1770
+ console.log(' ' + dim('Removed duplicate project-level rihal commands (global ones in ~/.claude/ take precedence).'));
1771
+ } catch { /* non-fatal */ }
1772
+ const filtered = plan.filter(e => {
1773
+ const rel = e.rel.split(path.sep).join('/');
1774
+ return !rel.startsWith('.claude/commands/') && !rel.startsWith('.claude/agents/');
1775
+ });
1776
+ plan.length = 0;
1777
+ filtered.forEach(e => plan.push(e));
1778
+ }
1779
+ } catch { /* non-fatal — skip detection on permission errors */ }
1780
+ }
1781
+
1727
1782
  // Write .rihal/_config/manifest.yaml + agent-manifest.csv + files-manifest.csv
1728
1783
  const configDir = path.join(opts.target, '.rihal', '_config');
1729
1784
  ensureDir(configDir);
@@ -1920,10 +1975,11 @@ async function install(opts) {
1920
1975
  console.log('');
1921
1976
  }
1922
1977
 
1923
- // Count installed agents + commands dynamically (#190). Reads from the
1924
- // IDE-specific install paths so cursor/gemini/vscode/antigravity don't
1925
- // false-fail the health check.
1926
- const primaryIde = opts.ides[0];
1978
+ // Count installed agents + commands dynamically (#190).
1979
+ // Prefer the 'claude' IDE paths for counting when claude is in the selected list —
1980
+ // that's what actually matters for Claude Code slash command availability.
1981
+ // Fall back to the first selected IDE only when claude isn't included.
1982
+ const primaryIde = opts.ides.includes('claude') ? 'claude' : opts.ides[0];
1927
1983
  const idePaths = getPathsForIde(primaryIde, opts.target);
1928
1984
  const agentsDir = idePaths.agentsDir;
1929
1985
  const commandsDir = idePaths.commandsDir;
@@ -1933,12 +1989,11 @@ async function install(opts) {
1933
1989
  agentCount = fs.readdirSync(agentsDir).filter(f => (f.startsWith('rihal-') || f.startsWith('rcode-')) && (f.endsWith('.md') || f.endsWith('.mdc'))).length;
1934
1990
  }
1935
1991
  if (fs.existsSync(commandsDir)) {
1936
- commandCount = fs.readdirSync(commandsDir).filter(f => f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))).length;
1937
- }
1938
- // Clean up legacy .claude/commands/rihal/ colon-namespace directory if it exists
1939
- const legacyColonDir = path.join(opts.target, '.claude', 'commands', 'rihal');
1940
- if (primaryIde === 'claude' && fs.existsSync(legacyColonDir)) {
1941
- fs.rmSync(legacyColonDir, { recursive: true, force: true });
1992
+ // claude IDE names commands rihal-*.md; other IDEs use plain {name}.md inside a rihal/ subdir
1993
+ const commandFilter = primaryIde === 'claude'
1994
+ ? f => f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))
1995
+ : f => f.endsWith('.md') || f.endsWith('.mdc');
1996
+ commandCount = fs.readdirSync(commandsDir).filter(commandFilter).length;
1942
1997
  }
1943
1998
  } catch {}
1944
1999
 
package/dist/rcode.js CHANGED
@@ -16269,6 +16269,51 @@ ${BLOCK}`);
16269
16269
  console.log(` ${dim(`${skillsInstalled2} skills installed globally`)}`);
16270
16270
  return 0;
16271
16271
  }
16272
+ const globalClaudeCommands = path2.join(os.homedir(), ".claude", "commands");
16273
+ const projectClaudeCommands = path2.join(opts.target, ".claude", "commands");
16274
+ const isProjectInstall = opts.target !== os.homedir();
16275
+ if (isProjectInstall && !opts.force && !opts.forceOverwrite) {
16276
+ 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"));
16279
+ if (globalHasRihal && !projectHasRihal) {
16280
+ const before = plan.length;
16281
+ const filtered = plan.filter((e) => {
16282
+ const rel = e.rel.split(path2.sep).join("/");
16283
+ return !rel.startsWith(".claude/commands/") && !rel.startsWith(".claude/agents/");
16284
+ });
16285
+ if (filtered.length < before) {
16286
+ plan.length = 0;
16287
+ filtered.forEach((e) => plan.push(e));
16288
+ console.log(" " + dim("Global rihal commands detected in ~/.claude/ \u2014 skipping project-level agent/command install to avoid duplicates."));
16289
+ console.log(" " + dim("Use --force-overwrite to install locally anyway."));
16290
+ }
16291
+ } else if (globalHasRihal && projectHasRihal) {
16292
+ try {
16293
+ const projectCommandFiles = fs2.readdirSync(projectClaudeCommands).filter((f) => f.startsWith("rihal-") && f.endsWith(".md"));
16294
+ for (const f of projectCommandFiles) {
16295
+ fs2.unlinkSync(path2.join(projectClaudeCommands, f));
16296
+ }
16297
+ const projectAgentsDir = path2.join(opts.target, ".claude", "agents");
16298
+ if (fs2.existsSync(projectAgentsDir)) {
16299
+ const agentFiles = fs2.readdirSync(projectAgentsDir).filter((f) => f.startsWith("rihal-") && f.endsWith(".md"));
16300
+ for (const f of agentFiles) {
16301
+ fs2.unlinkSync(path2.join(projectAgentsDir, f));
16302
+ }
16303
+ }
16304
+ console.log(" " + dim("Removed duplicate project-level rihal commands (global ones in ~/.claude/ take precedence)."));
16305
+ } catch {
16306
+ }
16307
+ const filtered = plan.filter((e) => {
16308
+ const rel = e.rel.split(path2.sep).join("/");
16309
+ return !rel.startsWith(".claude/commands/") && !rel.startsWith(".claude/agents/");
16310
+ });
16311
+ plan.length = 0;
16312
+ filtered.forEach((e) => plan.push(e));
16313
+ }
16314
+ } catch {
16315
+ }
16316
+ }
16272
16317
  const configDir = path2.join(opts.target, ".rihal", "_config");
16273
16318
  ensureDir(configDir);
16274
16319
  fs2.writeFileSync(path2.join(configDir, "manifest.yaml"), generateInstallManifest(opts));
@@ -16415,7 +16460,7 @@ ${BLOCK}`);
16415
16460
  console.log(dim(" To overwrite: re-run with --force-overwrite | To see full diffs: --show-diff"));
16416
16461
  console.log("");
16417
16462
  }
16418
- const primaryIde = opts.ides[0];
16463
+ const primaryIde = opts.ides.includes("claude") ? "claude" : opts.ides[0];
16419
16464
  const idePaths = getPathsForIde(primaryIde, opts.target);
16420
16465
  const agentsDir = idePaths.agentsDir;
16421
16466
  const commandsDir = idePaths.commandsDir;
@@ -16425,11 +16470,8 @@ ${BLOCK}`);
16425
16470
  agentCount = fs2.readdirSync(agentsDir).filter((f) => (f.startsWith("rihal-") || f.startsWith("rcode-")) && (f.endsWith(".md") || f.endsWith(".mdc"))).length;
16426
16471
  }
16427
16472
  if (fs2.existsSync(commandsDir)) {
16428
- commandCount = fs2.readdirSync(commandsDir).filter((f) => f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc"))).length;
16429
- }
16430
- const legacyColonDir = path2.join(opts.target, ".claude", "commands", "rihal");
16431
- if (primaryIde === "claude" && fs2.existsSync(legacyColonDir)) {
16432
- fs2.rmSync(legacyColonDir, { recursive: true, force: true });
16473
+ const commandFilter = primaryIde === "claude" ? (f) => f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc")) : (f) => f.endsWith(".md") || f.endsWith(".mdc");
16474
+ commandCount = fs2.readdirSync(commandsDir).filter(commandFilter).length;
16433
16475
  }
16434
16476
  } catch {
16435
16477
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "3.4.13",
3
+ "version": "3.4.15",
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": {