rafaygen-cli 1.3.1 → 1.3.3

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/src/ui.js CHANGED
@@ -2,86 +2,180 @@ import boxen from "boxen";
2
2
  import chalk from "chalk";
3
3
  import { highlight } from "cli-highlight";
4
4
  import * as Diff from "diff";
5
- import fs from "fs";
6
- import path from "path";
7
5
 
8
- // Formats a message in a nice outlined box
6
+ // ─────────────────────────────────────────────────────────────
7
+ // 1. renderBox — general-purpose boxen container with round border
8
+ // ─────────────────────────────────────────────────────────────
9
9
  export function renderBox(title, content, color = "cyan") {
10
- const c = chalk[color] || chalk.cyan;
10
+ const borderColor =
11
+ typeof chalk[color] === "function" ? color : "cyan";
12
+
13
+ const titleStr = title
14
+ ? chalk[borderColor]
15
+ ? chalk[borderColor].bold(` ${title} `)
16
+ : chalk.cyan.bold(` ${title} `)
17
+ : undefined;
18
+
11
19
  console.log(
12
20
  boxen(content, {
13
- title: c.bold(title),
21
+ title: titleStr,
14
22
  titleAlignment: "left",
15
23
  padding: 1,
16
24
  margin: { top: 1, bottom: 1 },
17
25
  borderStyle: "round",
18
- borderColor: color,
26
+ borderColor,
19
27
  })
20
28
  );
21
29
  }
22
30
 
23
- // Renders syntax highlighted code in a box
24
- export function renderCodeBox(filename, code, language = "typescript") {
25
- const highlighted = highlight(code, {
26
- language,
27
- ignoreIllegals: true,
28
- });
29
- renderBox(` File: ${filename} `, highlighted, "blue");
31
+ // ─────────────────────────────────────────────────────────────
32
+ // 2. renderCodeBox — syntax-highlighted code inside a box
33
+ // ─────────────────────────────────────────────────────────────
34
+ export function renderCodeBox(filename, code, language = "javascript") {
35
+ let highlighted;
36
+ try {
37
+ highlighted = highlight(code, { language, ignoreIllegals: true });
38
+ } catch {
39
+ // Fall back to unhighlighted code if language is unsupported
40
+ highlighted = code;
41
+ }
42
+
43
+ const header = chalk.blue.dim("─".repeat(40));
44
+ const fileLabel = chalk.blue.bold("📄 ") + chalk.blueBright.underline(filename);
45
+ const langLabel = chalk.blue.dim(` (${language})`);
46
+ const body = `${fileLabel}${langLabel}\n${header}\n${highlighted}`;
47
+
48
+ console.log(
49
+ boxen(body, {
50
+ title: chalk.blue.bold(" Code "),
51
+ titleAlignment: "left",
52
+ padding: 1,
53
+ margin: { top: 1, bottom: 1 },
54
+ borderStyle: "round",
55
+ borderColor: "blue",
56
+ })
57
+ );
30
58
  }
31
59
 
32
- // Renders a git-like diff for code modifications
60
+ // ─────────────────────────────────────────────────────────────
61
+ // 3. renderDiffBox — git-style +/- diff display with colors
62
+ // ─────────────────────────────────────────────────────────────
33
63
  export function renderDiffBox(filename, original, modified) {
34
64
  const diffResult = Diff.diffLines(original, modified);
65
+
66
+ let added = 0;
67
+ let removed = 0;
35
68
  let output = "";
36
-
37
- diffResult.forEach((part) => {
38
- // Add prefix and color based on diff status
39
- let prefix = " ";
40
- let colorize = chalk.dim;
41
-
69
+
70
+ for (const part of diffResult) {
71
+ const lines = part.value.replace(/\n$/, "").split("\n");
72
+
42
73
  if (part.added) {
43
- prefix = chalk.green("+ ");
44
- colorize = chalk.green;
74
+ added += lines.length;
75
+ for (const line of lines) {
76
+ output += chalk.green.bold("+ ") + chalk.green(line) + "\n";
77
+ }
45
78
  } else if (part.removed) {
46
- prefix = chalk.red("- ");
47
- colorize = chalk.red;
79
+ removed += lines.length;
80
+ for (const line of lines) {
81
+ output += chalk.red.bold("- ") + chalk.red(line) + "\n";
82
+ }
83
+ } else {
84
+ for (const line of lines) {
85
+ output += chalk.dim(" " + line) + "\n";
86
+ }
48
87
  }
88
+ }
49
89
 
50
- // Split part into lines and format
51
- const lines = part.value.replace(/\n$/, "").split("\n");
52
- lines.forEach((line) => {
53
- output += colorize(`${prefix}${line}`) + "\n";
54
- });
55
- });
90
+ const stats =
91
+ chalk.green.bold(`+${added} `) +
92
+ chalk.red.bold(`-${removed} `) +
93
+ chalk.dim("lines changed");
56
94
 
57
- renderBox(` Diff: ${filename} `, output.trimEnd(), "yellow");
95
+ const fileLabel = chalk.yellow.bold("📝 ") + chalk.yellowBright.underline(filename);
96
+ const body = `${fileLabel} ${stats}\n${chalk.yellow.dim("─".repeat(40))}\n${output.trimEnd()}`;
97
+
98
+ console.log(
99
+ boxen(body, {
100
+ title: chalk.yellow.bold(" Diff "),
101
+ titleAlignment: "left",
102
+ padding: 1,
103
+ margin: { top: 1, bottom: 1 },
104
+ borderStyle: "round",
105
+ borderColor: "yellow",
106
+ })
107
+ );
58
108
  }
59
109
 
110
+ // ─────────────────────────────────────────────────────────────
111
+ // 4. printError — red ✖ Error prefix
112
+ // ─────────────────────────────────────────────────────────────
60
113
  export function printError(msg) {
61
- console.log(chalk.red.bold("\n✖ Error: ") + chalk.red(msg) + "\n");
114
+ console.log(
115
+ chalk.red.bold("\n ✖ Error: ") + chalk.red(msg) + "\n"
116
+ );
62
117
  }
63
118
 
119
+ // ─────────────────────────────────────────────────────────────
120
+ // 5. printSuccess — green ✔ Success prefix
121
+ // ─────────────────────────────────────────────────────────────
64
122
  export function printSuccess(msg) {
65
- console.log(chalk.green.bold("\n✔ Success: ") + chalk.green(msg) + "\n");
123
+ console.log(
124
+ chalk.green.bold("\n ✔ Success: ") + chalk.green(msg) + "\n"
125
+ );
66
126
  }
67
127
 
128
+ // ─────────────────────────────────────────────────────────────
129
+ // 6. printStep — cyan ❯ prefix
130
+ // ─────────────────────────────────────────────────────────────
68
131
  export function printStep(msg) {
69
- console.log(chalk.cyan.bold("❯ ") + msg);
132
+ console.log(chalk.cyan.bold(" ❯ ") + chalk.white(msg));
70
133
  }
71
134
 
135
+ // ─────────────────────────────────────────────────────────────
136
+ // 7. printWarning — yellow ⚠ Warning prefix
137
+ // ─────────────────────────────────────────────────────────────
138
+ export function printWarning(msg) {
139
+ console.log(
140
+ chalk.yellow.bold("\n ⚠ Warning: ") + chalk.yellow(msg) + "\n"
141
+ );
142
+ }
143
+
144
+ // ─────────────────────────────────────────────────────────────
145
+ // 8. printInfo — blue ℹ Info prefix
146
+ // ─────────────────────────────────────────────────────────────
147
+ export function printInfo(msg) {
148
+ console.log(
149
+ chalk.blue.bold(" ℹ ") + chalk.blueBright(msg)
150
+ );
151
+ }
152
+
153
+ // ─────────────────────────────────────────────────────────────
154
+ // 9. printAsciiLogo — the RafayGen ASCII art logo
155
+ // ─────────────────────────────────────────────────────────────
72
156
  export function printAsciiLogo() {
73
- console.log(chalk.cyan.bold(`
74
- ____ __ _____
75
- | _ \\ / _| / ____|
76
- | |_) | __ _| |_ __ _ _ _| | __ ___ _ __
77
- | _ < / _\` | _/ _\` | | | | |_ / _ \\ '_ \\
78
- | |_) | (_| | || (_| | |_| |__| | __/ | | |
79
- |____/ \\__,_|_| \\__,_|\\__, \\_____\\___|_| |_|
80
- __/ |
81
- |___/
82
- `));
157
+ const logo = `
158
+ ${chalk.cyan.bold("╔══════════════════════════════════════════════════╗")}
159
+ ${chalk.cyan.bold("║")} ${chalk.cyan.bold("║")}
160
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold(" ____ __ _____ ")} ${chalk.cyan.bold("║")}
161
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold("| _ \\ / _| / ____| ")} ${chalk.cyan.bold("║")}
162
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold("| |_) | __ _| |_ __ _ _ _| | __ ___ _ __ ")} ${chalk.cyan.bold("║")}
163
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold("| _ < / _` | _/ _` | | | | |_ / _ \\ '_ \\ ")} ${chalk.cyan.bold("║")}
164
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold("| |_) | (_| | || (_| | |_| |__| | __/ | | |")} ${chalk.cyan.bold("║")}
165
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold("|____/ \\__,_|_| \\__,_|\\__, \\_____\\___|_| |_|")} ${chalk.cyan.bold("║")}
166
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold(" __/ | ")} ${chalk.cyan.bold("║")}
167
+ ${chalk.cyan.bold("║")} ${chalk.cyanBright.bold(" |___/ ")} ${chalk.cyan.bold("║")}
168
+ ${chalk.cyan.bold("║")} ${chalk.cyan.bold("║")}
169
+ ${chalk.cyan.bold("║")} ${chalk.dim.white("AI-Powered Code Generation & Dev Agent")} ${chalk.cyan.bold("║")}
170
+ ${chalk.cyan.bold("║")} ${chalk.cyan.bold("║")}
171
+ ${chalk.cyan.bold("╚══════════════════════════════════════════════════╝")}
172
+ `;
173
+ console.log(logo);
83
174
  }
84
175
 
176
+ // ─────────────────────────────────────────────────────────────
177
+ // 10. printRandomWelcome — random welcome message from array
178
+ // ─────────────────────────────────────────────────────────────
85
179
  export function printRandomWelcome() {
86
180
  const messages = [
87
181
  "Welcome to RafayGen. Ready to architect something beautiful?",
@@ -90,12 +184,286 @@ export function printRandomWelcome() {
90
184
  "System online. Awaiting your creative vision.",
91
185
  "RafayGen connected. What are we building today?",
92
186
  "Agent is active. Drop your prompt below.",
187
+ "Neural pathways connected. Let's create something extraordinary.",
188
+ "All systems nominal. Your AI coding partner is ready.",
189
+ ];
190
+
191
+ const colors = [
192
+ "green",
193
+ "cyan",
194
+ "magenta",
195
+ "blueBright",
196
+ "greenBright",
197
+ "yellowBright",
198
+ "cyanBright",
199
+ "magentaBright",
93
200
  ];
94
-
95
- const colors = ["green", "cyan", "magenta", "blueBright", "greenBright", "yellowBright"];
96
-
97
- const randMsg = messages[Math.floor(Math.random() * messages.length)];
98
- const randColor = colors[Math.floor(Math.random() * colors.length)];
99
-
100
- console.log(chalk[randColor].bold(`\\n✨ ${randMsg}\\n`));
201
+
202
+ const idx = Math.floor(Math.random() * messages.length);
203
+ const colorIdx = Math.floor(Math.random() * colors.length);
204
+ const colorFn = chalk[colors[colorIdx]] || chalk.cyan;
205
+
206
+ console.log(colorFn.bold(`\n ✨ ${messages[idx]}\n`));
207
+ }
208
+
209
+ // ─────────────────────────────────────────────────────────────
210
+ // 11. printModelBadge — shows current active model in a badge
211
+ // ─────────────────────────────────────────────────────────────
212
+ export function printModelBadge(model) {
213
+ if (!model) return;
214
+
215
+ const badge =
216
+ chalk.bgMagenta.white.bold(" MODEL ") +
217
+ chalk.bgBlack.magentaBright.bold(` ${model} `);
218
+
219
+ console.log(`\n ${badge}\n`);
220
+ }
221
+
222
+ // ─────────────────────────────────────────────────────────────
223
+ // 12. printSessionStatus — full session status display
224
+ // ─────────────────────────────────────────────────────────────
225
+ export function printSessionStatus(state = {}) {
226
+ const {
227
+ model = "unknown",
228
+ sandbox = false,
229
+ approval = "auto",
230
+ cwd = process.cwd(),
231
+ reasoning = false,
232
+ attachedFiles = 0,
233
+ sessionId = null,
234
+ } = state;
235
+
236
+ const statusIndicator = chalk.green.bold("●");
237
+ const lines = [];
238
+
239
+ lines.push(
240
+ chalk.bold.white(" Session Status") + chalk.dim(" ─────────────────────────")
241
+ );
242
+ lines.push("");
243
+
244
+ // Model
245
+ lines.push(
246
+ ` ${chalk.dim("Model:")} ${chalk.magentaBright.bold(model)}`
247
+ );
248
+
249
+ // Session ID
250
+ if (sessionId) {
251
+ lines.push(
252
+ ` ${chalk.dim("Session:")} ${chalk.white(sessionId)}`
253
+ );
254
+ }
255
+
256
+ // Sandbox
257
+ const sandboxBadge = sandbox
258
+ ? chalk.bgGreen.black.bold(" ON ") + chalk.green(" Sandboxed")
259
+ : chalk.bgRed.white.bold(" OFF ") + chalk.red(" Direct");
260
+ lines.push(` ${chalk.dim("Sandbox:")} ${sandboxBadge}`);
261
+
262
+ // Approval mode
263
+ const approvalBadge =
264
+ approval === "auto"
265
+ ? chalk.bgYellow.black.bold(` ${approval.toUpperCase()} `)
266
+ : chalk.bgBlue.white.bold(` ${approval.toUpperCase()} `);
267
+ lines.push(` ${chalk.dim("Approval:")} ${approvalBadge}`);
268
+
269
+ // CWD
270
+ lines.push(
271
+ ` ${chalk.dim("Working Dir:")} ${chalk.blueBright(cwd)}`
272
+ );
273
+
274
+ // Reasoning
275
+ const reasonBadge = reasoning
276
+ ? chalk.green.bold("✔ enabled")
277
+ : chalk.dim("✘ disabled");
278
+ lines.push(` ${chalk.dim("Reasoning:")} ${reasonBadge}`);
279
+
280
+ // Attached files
281
+ lines.push(
282
+ ` ${chalk.dim("Attached:")} ${chalk.white.bold(String(attachedFiles))} file${attachedFiles !== 1 ? "s" : ""}`
283
+ );
284
+
285
+ lines.push("");
286
+ lines.push(` ${statusIndicator} ${chalk.green("Active")}`);
287
+
288
+ console.log(
289
+ boxen(lines.join("\n"), {
290
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
291
+ margin: { top: 1, bottom: 1 },
292
+ borderStyle: "round",
293
+ borderColor: "cyan",
294
+ title: chalk.cyan.bold(" ⚡ Session "),
295
+ titleAlignment: "left",
296
+ })
297
+ );
298
+ }
299
+
300
+ // ─────────────────────────────────────────────────────────────
301
+ // 13. renderMarkdown — basic markdown → terminal rendering
302
+ // ─────────────────────────────────────────────────────────────
303
+ export function renderMarkdown(text) {
304
+ if (!text) return "";
305
+
306
+ let output = text;
307
+
308
+ // ── Fenced code blocks: ```lang\ncode\n``` ──────────────
309
+ output = output.replace(
310
+ /```(\w+)?\n([\s\S]*?)```/g,
311
+ (_match, lang, code) => {
312
+ let highlighted;
313
+ try {
314
+ highlighted = highlight(code.trimEnd(), {
315
+ language: lang || "plaintext",
316
+ ignoreIllegals: true,
317
+ });
318
+ } catch {
319
+ highlighted = code.trimEnd();
320
+ }
321
+ const border = chalk.dim("│ ");
322
+ const styledCode = highlighted
323
+ .split("\n")
324
+ .map((l) => ` ${border}${l}`)
325
+ .join("\n");
326
+ const label = lang ? chalk.dim.italic(` ${lang} `) : "";
327
+ return `\n ${chalk.dim("┌──")}${label}${chalk.dim("──")}\n${styledCode}\n ${chalk.dim("└──────")}\n`;
328
+ }
329
+ );
330
+
331
+ // ── Inline code: `code` ────────────────────────────────
332
+ output = output.replace(
333
+ /`([^`]+)`/g,
334
+ (_match, code) => chalk.bgGray.white(` ${code} `)
335
+ );
336
+
337
+ // ── Headers: # ## ### ──────────────────────────────────
338
+ output = output.replace(
339
+ /^### (.+)$/gm,
340
+ (_match, heading) => chalk.cyan.bold(` ${heading}`)
341
+ );
342
+ output = output.replace(
343
+ /^## (.+)$/gm,
344
+ (_match, heading) => chalk.cyan.bold.underline(` ${heading}`)
345
+ );
346
+ output = output.replace(
347
+ /^# (.+)$/gm,
348
+ (_match, heading) => "\n" + chalk.cyanBright.bold.underline(`${heading}`) + "\n"
349
+ );
350
+
351
+ // ── Bold: **text** ─────────────────────────────────────
352
+ output = output.replace(
353
+ /\*\*([^*]+)\*\*/g,
354
+ (_match, bold) => chalk.bold(bold)
355
+ );
356
+
357
+ // ── Italic: *text* or _text_ ───────────────────────────
358
+ output = output.replace(
359
+ /(?<!\*)\*([^*]+)\*(?!\*)/g,
360
+ (_match, it) => chalk.italic(it)
361
+ );
362
+ output = output.replace(
363
+ /(?<!_)_([^_]+)_(?!_)/g,
364
+ (_match, it) => chalk.italic(it)
365
+ );
366
+
367
+ // ── Unordered list: - item or * item ───────────────────
368
+ output = output.replace(
369
+ /^(\s*)[-*] (.+)$/gm,
370
+ (_match, indent, item) => `${indent} ${chalk.cyan("•")} ${item}`
371
+ );
372
+
373
+ // ── Ordered list: 1. item ──────────────────────────────
374
+ output = output.replace(
375
+ /^(\s*)(\d+)\. (.+)$/gm,
376
+ (_match, indent, num, item) =>
377
+ `${indent} ${chalk.cyan(num + ".")} ${item}`
378
+ );
379
+
380
+ // ── Blockquote: > text ─────────────────────────────────
381
+ output = output.replace(
382
+ /^> (.+)$/gm,
383
+ (_match, quote) => chalk.dim(` ${chalk.green("▎")} ${chalk.italic(quote)}`)
384
+ );
385
+
386
+ // ── Horizontal rule: --- or *** ────────────────────────
387
+ output = output.replace(
388
+ /^(---|===|\*\*\*)$/gm,
389
+ () => chalk.dim(" " + "─".repeat(50))
390
+ );
391
+
392
+ // ── Links: [text](url) ────────────────────────────────
393
+ output = output.replace(
394
+ /\[([^\]]+)\]\(([^)]+)\)/g,
395
+ (_match, label, url) => chalk.blue.underline(label) + chalk.dim(` (${url})`)
396
+ );
397
+
398
+ console.log(output);
399
+ return output;
400
+ }
401
+
402
+ // ─────────────────────────────────────────────────────────────
403
+ // 14. printAgentThinking — renders thinking/reasoning text
404
+ // ─────────────────────────────────────────────────────────────
405
+ export function printAgentThinking(text) {
406
+ if (!text) return;
407
+
408
+ const lines = text.split("\n");
409
+ const prefix = chalk.dim.italic(" 💭 ");
410
+ const border = chalk.dim.italic(" │ ");
411
+
412
+ console.log("");
413
+ console.log(prefix + chalk.dim.italic("Thinking..."));
414
+ for (const line of lines) {
415
+ console.log(border + chalk.dim.italic(line));
416
+ }
417
+ console.log(chalk.dim.italic(" └──"));
418
+ console.log("");
419
+ }
420
+
421
+ // ─────────────────────────────────────────────────────────────
422
+ // 15. printToolExecution — renders a tool call in styled box
423
+ // ─────────────────────────────────────────────────────────────
424
+ export function printToolExecution(toolName, args = {}) {
425
+ const header =
426
+ chalk.bgYellow.black.bold(" TOOL ") +
427
+ " " +
428
+ chalk.yellowBright.bold(toolName);
429
+
430
+ const argLines = [];
431
+ const entries = Object.entries(args);
432
+
433
+ if (entries.length > 0) {
434
+ for (const [key, value] of entries) {
435
+ let displayValue;
436
+ if (typeof value === "string") {
437
+ // Truncate very long strings
438
+ displayValue =
439
+ value.length > 120
440
+ ? chalk.white(`"${value.slice(0, 117)}..."`)
441
+ : chalk.white(`"${value}"`);
442
+ } else if (typeof value === "object" && value !== null) {
443
+ const jsonStr = JSON.stringify(value);
444
+ displayValue =
445
+ jsonStr.length > 120
446
+ ? chalk.dim(jsonStr.slice(0, 117) + "...")
447
+ : chalk.dim(jsonStr);
448
+ } else {
449
+ displayValue = chalk.yellowBright(String(value));
450
+ }
451
+
452
+ argLines.push(` ${chalk.cyan(key)}${chalk.dim(":")} ${displayValue}`);
453
+ }
454
+ } else {
455
+ argLines.push(chalk.dim(" (no arguments)"));
456
+ }
457
+
458
+ const body = `${header}\n\n${argLines.join("\n")}`;
459
+
460
+ console.log(
461
+ boxen(body, {
462
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
463
+ margin: { top: 0, bottom: 0, left: 2, right: 0 },
464
+ borderStyle: "round",
465
+ borderColor: "yellow",
466
+ dimBorder: true,
467
+ })
468
+ );
101
469
  }