@hanzlaa/rcode 2.4.0 → 2.5.0

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
@@ -116,6 +116,7 @@ function parseArgs(argv) {
116
116
  language: 'English',
117
117
  mode: 'guided',
118
118
  ide: 'claude', // claude, cursor, gemini (copilot = TODO)
119
+ ideProvided: false, // true when --ide is passed explicitly — skip interactive prompt
119
120
  help: false,
120
121
  modules: [], // --module core --module execution or empty = all
121
122
  // #189 — planning commit policy. null = ask interactively (or default true under --yes).
@@ -143,7 +144,7 @@ function parseArgs(argv) {
143
144
  else if (arg === '--project') opts.projectName = argv[++i];
144
145
  else if (arg === '--language') opts.language = argv[++i];
145
146
  else if (arg === '--mode') opts.mode = argv[++i];
146
- else if (arg === '--ide') opts.ide = argv[++i];
147
+ else if (arg === '--ide') { opts.ide = argv[++i]; opts.ideProvided = true; }
147
148
  else if (arg === '--module') opts.modules.push(argv[++i]);
148
149
  else if (arg === '--commit-planning') opts.commitPlanning = true;
149
150
  else if (arg === '--no-commit-planning' || arg === '--ignore-planning') opts.commitPlanning = false;
@@ -163,6 +164,102 @@ function parseArgs(argv) {
163
164
  return opts;
164
165
  }
165
166
 
167
+ /**
168
+ * Print the Rihal Memory Bank installer header. Box-drawn banner shown once
169
+ * at the top of every interactive install run.
170
+ */
171
+ function printInstallHeader(targetVersion) {
172
+ const v = targetVersion || readPackageVersion();
173
+ const lines = [
174
+ '',
175
+ pc.cyan('╭───────────────────────────────────────────────────────────╮'),
176
+ pc.cyan('│') + ' ' + pc.cyan('│'),
177
+ pc.cyan('│') + ' ' + pc.bold(pc.yellow('🕌 Rihal Memory Bank')) + ' ' + dim('— installer') + ' ' + pc.cyan('│'),
178
+ pc.cyan('│') + ' ' + dim('A persistent context-brain for your editor') + ' ' + pc.cyan('│'),
179
+ pc.cyan('│') + ' ' + pc.cyan('│'),
180
+ pc.cyan('│') + ' ' + dim('version ') + pc.green('v' + v) + ' ' + pc.cyan('│'),
181
+ pc.cyan('│') + ' ' + dim('docs ') + 'github.com/hanzla-habib/rihal-code ' + pc.cyan('│'),
182
+ pc.cyan('│') + ' ' + pc.cyan('│'),
183
+ pc.cyan('╰───────────────────────────────────────────────────────────╯'),
184
+ '',
185
+ ];
186
+ console.log(lines.join('\n'));
187
+ }
188
+
189
+ /**
190
+ * Detect which IDEs the user likely uses. Soft signals only — never rejects,
191
+ * just biases the default selection in the interactive prompt.
192
+ * Returns a set like { claude: true, cursor: false, gemini: false }.
193
+ */
194
+ function detectIdeSignals(target) {
195
+ const signals = { claude: false, cursor: false, gemini: false };
196
+ // 1. Project-local install dirs (strongest signal — they already use one)
197
+ if (fs.existsSync(path.join(target, '.claude'))) signals.claude = true;
198
+ if (fs.existsSync(path.join(target, '.cursor'))) signals.cursor = true;
199
+ if (fs.existsSync(path.join(target, '.gemini'))) signals.gemini = true;
200
+ // 2. User-level config dirs
201
+ const home = os.homedir();
202
+ if (fs.existsSync(path.join(home, '.claude'))) signals.claude = true;
203
+ if (fs.existsSync(path.join(home, '.cursor'))) signals.cursor = true;
204
+ if (fs.existsSync(path.join(home, '.config', 'Cursor'))) signals.cursor = true;
205
+ if (fs.existsSync(path.join(home, '.gemini'))) signals.gemini = true;
206
+ // 3. Env vars commonly set by editor terminals
207
+ if (process.env.CURSOR_TRACE_ID || /cursor/i.test(process.env.TERM_PROGRAM || '')) signals.cursor = true;
208
+ if (process.env.CLAUDECODE === '1' || process.env.CLAUDE_CODE_ENTRYPOINT) signals.claude = true;
209
+ return signals;
210
+ }
211
+
212
+ /**
213
+ * Resolve target IDE — explicit --ide flag wins, then interactive prompt
214
+ * (when TTY + not --yes + not --ideProvided), else default to 'claude'.
215
+ *
216
+ * Closes the gap where users got auto-installed to claude even when they
217
+ * actually wanted cursor or gemini.
218
+ */
219
+ async function resolveIde(opts) {
220
+ if (opts.ideProvided) return opts.ide; // user passed --ide, respect it
221
+ if (opts.yes || !process.stdin.isTTY) return opts.ide || 'claude';
222
+
223
+ const signals = detectIdeSignals(opts.target);
224
+ const detected = ['claude', 'cursor', 'gemini'].filter(k => signals[k]);
225
+
226
+ // Build the menu — detected IDEs marked with a hint
227
+ const choices = [
228
+ { key: '1', value: 'claude', label: 'Claude Code', hint: signals.claude ? dim('(detected)') : '' },
229
+ { key: '2', value: 'cursor', label: 'Cursor', hint: signals.cursor ? dim('(detected)') : '' },
230
+ { key: '3', value: 'gemini', label: 'Gemini CLI', hint: signals.gemini ? dim('(detected)') : dim('(beta — limited)') },
231
+ ];
232
+
233
+ // Pick a default: prefer the single detected IDE; otherwise claude
234
+ let defaultValue = 'claude';
235
+ if (detected.length === 1) defaultValue = detected[0];
236
+
237
+ const readline = require('readline');
238
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
239
+ const prompt = (q) => new Promise(r => rl.question(q, a => r(a)));
240
+
241
+ console.log(pc.bold('🎯 Which editor will you use Rihal with?'));
242
+ console.log('');
243
+ for (const c of choices) {
244
+ const marker = c.value === defaultValue ? pc.green('●') : dim('○');
245
+ const label = c.value === defaultValue ? pc.bold(c.label) : c.label;
246
+ console.log(` ${marker} ${pc.cyan('[' + c.key + ']')} ${label} ${c.hint}`);
247
+ }
248
+ console.log('');
249
+ const defaultKey = choices.find(c => c.value === defaultValue).key;
250
+ const answer = (await prompt(` Pick an editor [${defaultKey}]: `)).trim().toLowerCase();
251
+ rl.close();
252
+
253
+ if (!answer) return defaultValue;
254
+ // Accept either the number key or the name
255
+ const byKey = choices.find(c => c.key === answer);
256
+ if (byKey) return byKey.value;
257
+ const byName = choices.find(c => c.value === answer || c.label.toLowerCase().startsWith(answer));
258
+ if (byName) return byName.value;
259
+ console.log(dim(` Unrecognised choice "${answer}" — falling back to ${defaultValue}.`));
260
+ return defaultValue;
261
+ }
262
+
166
263
  /**
167
264
  * Resolve commit-planning preference — CLI flag wins, then interactive
168
265
  * prompt (when TTY + not --yes), else GSD-style default: true.
@@ -1018,10 +1115,18 @@ function convertToCursorMdc(sourceText) {
1018
1115
  async function install(opts) {
1019
1116
  if (opts.help) { printHelp(); return 0; }
1020
1117
 
1118
+ const pkgVersion = readPackageVersion();
1119
+
1120
+ // Header banner — only shown for interactive runs to keep CI/non-TTY logs terse.
1121
+ const isInteractive = process.stdin.isTTY && !opts.yes;
1122
+ if (isInteractive) printInstallHeader(pkgVersion);
1123
+
1124
+ // Resolve target IDE (interactive prompt unless --ide flag, --yes, or non-TTY).
1125
+ opts.ide = await resolveIde(opts);
1126
+
1021
1127
  // Resolve commit-planning preference (interactive prompt or flag) — #189.
1022
1128
  opts.commitPlanning = await resolveCommitPlanning(opts);
1023
1129
 
1024
- const pkgVersion = readPackageVersion();
1025
1130
  console.log(`\n🕌 ${bold('Rihal Code')} ${pc.cyan('v' + pkgVersion)} ${dim('→')} ${opts.target}`);
1026
1131
 
1027
1132
  // Detect an existing install and surface it (#195).
package/dist/rcode.js CHANGED
@@ -15830,6 +15830,8 @@ var require_install = __commonJS({
15830
15830
  mode: "guided",
15831
15831
  ide: "claude",
15832
15832
  // claude, cursor, gemini (copilot = TODO)
15833
+ ideProvided: false,
15834
+ // true when --ide is passed explicitly — skip interactive prompt
15833
15835
  help: false,
15834
15836
  modules: [],
15835
15837
  // --module core --module execution or empty = all
@@ -15858,8 +15860,10 @@ var require_install = __commonJS({
15858
15860
  else if (arg === "--project") opts.projectName = argv[++i];
15859
15861
  else if (arg === "--language") opts.language = argv[++i];
15860
15862
  else if (arg === "--mode") opts.mode = argv[++i];
15861
- else if (arg === "--ide") opts.ide = argv[++i];
15862
- else if (arg === "--module") opts.modules.push(argv[++i]);
15863
+ else if (arg === "--ide") {
15864
+ opts.ide = argv[++i];
15865
+ opts.ideProvided = true;
15866
+ } else if (arg === "--module") opts.modules.push(argv[++i]);
15863
15867
  else if (arg === "--commit-planning") opts.commitPlanning = true;
15864
15868
  else if (arg === "--no-commit-planning" || arg === "--ignore-planning") opts.commitPlanning = false;
15865
15869
  else if (arg === "--non-destructive") opts.nonDestructive = true;
@@ -15877,6 +15881,71 @@ var require_install = __commonJS({
15877
15881
  if (!opts.projectName) opts.projectName = path2.basename(opts.target);
15878
15882
  return opts;
15879
15883
  }
15884
+ function printInstallHeader(targetVersion) {
15885
+ const v = targetVersion || readPackageVersion();
15886
+ const lines = [
15887
+ "",
15888
+ pc.cyan("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"),
15889
+ pc.cyan("\u2502") + " " + pc.cyan("\u2502"),
15890
+ pc.cyan("\u2502") + " " + pc.bold(pc.yellow("\u{1F54C} Rihal Memory Bank")) + " " + dim("\u2014 installer") + " " + pc.cyan("\u2502"),
15891
+ pc.cyan("\u2502") + " " + dim("A persistent context-brain for your editor") + " " + pc.cyan("\u2502"),
15892
+ pc.cyan("\u2502") + " " + pc.cyan("\u2502"),
15893
+ pc.cyan("\u2502") + " " + dim("version ") + pc.green("v" + v) + " " + pc.cyan("\u2502"),
15894
+ pc.cyan("\u2502") + " " + dim("docs ") + "github.com/hanzla-habib/rihal-code " + pc.cyan("\u2502"),
15895
+ pc.cyan("\u2502") + " " + pc.cyan("\u2502"),
15896
+ pc.cyan("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"),
15897
+ ""
15898
+ ];
15899
+ console.log(lines.join("\n"));
15900
+ }
15901
+ function detectIdeSignals(target) {
15902
+ const signals = { claude: false, cursor: false, gemini: false };
15903
+ if (fs2.existsSync(path2.join(target, ".claude"))) signals.claude = true;
15904
+ if (fs2.existsSync(path2.join(target, ".cursor"))) signals.cursor = true;
15905
+ if (fs2.existsSync(path2.join(target, ".gemini"))) signals.gemini = true;
15906
+ const home = os.homedir();
15907
+ if (fs2.existsSync(path2.join(home, ".claude"))) signals.claude = true;
15908
+ if (fs2.existsSync(path2.join(home, ".cursor"))) signals.cursor = true;
15909
+ if (fs2.existsSync(path2.join(home, ".config", "Cursor"))) signals.cursor = true;
15910
+ if (fs2.existsSync(path2.join(home, ".gemini"))) signals.gemini = true;
15911
+ if (process.env.CURSOR_TRACE_ID || /cursor/i.test(process.env.TERM_PROGRAM || "")) signals.cursor = true;
15912
+ if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_ENTRYPOINT) signals.claude = true;
15913
+ return signals;
15914
+ }
15915
+ async function resolveIde(opts) {
15916
+ if (opts.ideProvided) return opts.ide;
15917
+ if (opts.yes || !process.stdin.isTTY) return opts.ide || "claude";
15918
+ const signals = detectIdeSignals(opts.target);
15919
+ const detected = ["claude", "cursor", "gemini"].filter((k) => signals[k]);
15920
+ const choices = [
15921
+ { key: "1", value: "claude", label: "Claude Code", hint: signals.claude ? dim("(detected)") : "" },
15922
+ { key: "2", value: "cursor", label: "Cursor", hint: signals.cursor ? dim("(detected)") : "" },
15923
+ { key: "3", value: "gemini", label: "Gemini CLI", hint: signals.gemini ? dim("(detected)") : dim("(beta \u2014 limited)") }
15924
+ ];
15925
+ let defaultValue = "claude";
15926
+ if (detected.length === 1) defaultValue = detected[0];
15927
+ const readline = require("readline");
15928
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
15929
+ const prompt = (q) => new Promise((r) => rl.question(q, (a) => r(a)));
15930
+ console.log(pc.bold("\u{1F3AF} Which editor will you use Rihal with?"));
15931
+ console.log("");
15932
+ for (const c of choices) {
15933
+ const marker = c.value === defaultValue ? pc.green("\u25CF") : dim("\u25CB");
15934
+ const label = c.value === defaultValue ? pc.bold(c.label) : c.label;
15935
+ console.log(` ${marker} ${pc.cyan("[" + c.key + "]")} ${label} ${c.hint}`);
15936
+ }
15937
+ console.log("");
15938
+ const defaultKey = choices.find((c) => c.value === defaultValue).key;
15939
+ const answer = (await prompt(` Pick an editor [${defaultKey}]: `)).trim().toLowerCase();
15940
+ rl.close();
15941
+ if (!answer) return defaultValue;
15942
+ const byKey = choices.find((c) => c.key === answer);
15943
+ if (byKey) return byKey.value;
15944
+ const byName = choices.find((c) => c.value === answer || c.label.toLowerCase().startsWith(answer));
15945
+ if (byName) return byName.value;
15946
+ console.log(dim(` Unrecognised choice "${answer}" \u2014 falling back to ${defaultValue}.`));
15947
+ return defaultValue;
15948
+ }
15880
15949
  async function resolveCommitPlanning(opts) {
15881
15950
  if (opts.commitPlanning !== null) return opts.commitPlanning;
15882
15951
  if (opts.yes || !process.stdin.isTTY) return true;
@@ -16555,8 +16624,11 @@ Say "plan a sprint" or run \`/rihal:sprint-planning\` to break Phase 01 into sto
16555
16624
  printHelp2();
16556
16625
  return 0;
16557
16626
  }
16558
- opts.commitPlanning = await resolveCommitPlanning(opts);
16559
16627
  const pkgVersion = readPackageVersion();
16628
+ const isInteractive = process.stdin.isTTY && !opts.yes;
16629
+ if (isInteractive) printInstallHeader(pkgVersion);
16630
+ opts.ide = await resolveIde(opts);
16631
+ opts.commitPlanning = await resolveCommitPlanning(opts);
16560
16632
  console.log(`
16561
16633
  \u{1F54C} ${bold("Rihal Code")} ${pc.cyan("v" + pkgVersion)} ${dim("\u2192")} ${opts.target}`);
16562
16634
  const existingManifestPath = path2.join(opts.target, ".rihal", "_config", "manifest.yaml");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Rihal Code (rcode) — installable context-brain for Rihalians. 43 agents, 99 slash commands, 56 skills, pullable Rihal standards. Unified install for Claude Code, Cursor, and Gemini.",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -142,9 +142,12 @@ git remote -v 2>/dev/null | head -2
142
142
  find . -maxdepth 3 -type d ! -path "./node_modules*" ! -path "./.git*" ! -path "./.rihal*" ! -path "./.claude*" ! -path "./.planning*" 2>/dev/null | head -20
143
143
  ```
144
144
 
145
- Write `.rihal/RIHLA.md` following this template (don't over-interpret — just record what's seen):
145
+ Write `.rihal/RIHLA.md` following this template (don't over-interpret — just record what's seen).
146
+
147
+ **Naming note (do NOT remove from the template):** the file is `RIHLA.md`, not `RIHAL.md`. This is intentional — same Arabic root, different word. **Rihal (رحّال)** = the traveler/tool. **Rihla (رحلة)** = the journey/voyage. The product is *Rihal* (the tool you use); the per-project artifact is *Rihla* (your project's journey). The HTML comment in the template below preserves this reminder for anyone who later wonders if it's a typo.
146
148
 
147
149
  ```markdown
150
+ <!-- RIHLA (رحلة) = "the journey". Not a typo of RIHAL (رحّال) = "the traveler" / the tool itself. Same root, different word. This file documents your project's journey; Rihal is the tool that walks it with you. -->
148
151
  # RIHLA — Project journey baseline
149
152
 
150
153
  **Written by:** /rihal:init