gent-cli 13.0.0 β†’ 14.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gent-cli",
3
- "version": "13.0.0",
3
+ "version": "14.0.0",
4
4
  "description": "A modern, Git-like version control CLI with cloud sync, AI-powered superpowers (ask/review/docs/changelog), and zero-friction setup (gent setup/doctor/config).",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -8,12 +8,14 @@ const inquirer = require('inquirer');
8
8
  const ora = require('ora');
9
9
  const boxen = require('boxen');
10
10
  const authService = require('../services/auth-service');
11
+ const pet = require('./pet');
11
12
 
12
13
  /**
13
14
  * Login user
14
15
  * @param {Object} options - Command options
15
16
  */
16
17
  async function login(options) {
18
+ pet.banner('Knock knock β€” let\'s sign you in.', 'Enter your details below.');
17
19
  console.log(chalk.cyan('\nπŸ” Login to your Gent account\n'));
18
20
 
19
21
  try {
@@ -75,6 +77,8 @@ ${chalk.bold('Commands:')}
75
77
  borderColor: 'cyan'
76
78
  }));
77
79
 
80
+ await pet.celebrate('auth');
81
+
78
82
  } catch (error) {
79
83
  console.error(chalk.red('\nβœ— Login failed'));
80
84
  console.error(chalk.red(`Error: ${error.message}\n`));
@@ -379,25 +379,35 @@ const SCENES = {
379
379
  const HIDE = '\x1b[?25l';
380
380
  const SHOW = '\x1b[?25h';
381
381
  const CLEAR_LINE = '\x1b[2K';
382
+ const HOME = '\x1b[H';
383
+ const ENTER_ALT = '\x1b[?1049h'; // switch to the alternate screen buffer
384
+ const LEAVE_ALT = '\x1b[?1049l'; // …and restore the user's scrollback on exit
382
385
  const up = (n) => `\x1b[${n}A`;
383
386
 
384
387
  /**
385
- * Play a scene. Redraws IN PLACE (no full-screen clear, no scrollback spam).
386
- * Resolves when finished.
388
+ * Play a scene. Resolves when finished.
389
+ *
390
+ * Two redraw strategies:
391
+ * - altScreen:true β†’ take over the whole screen (alternate buffer), redraw
392
+ * from HOME each frame, restore on exit. Zero scrollback drift. Used by the
393
+ * standalone `gent pet` command.
394
+ * - altScreen:false β†’ inline redraw below existing output via cursor-up, so a
395
+ * command's own text stays visible above. Used by post-command celebrations.
387
396
  *
388
397
  * @param {string} sceneName
389
398
  * @param {object} opts
390
- * @param {boolean} opts.loop keep looping until Ctrl+C (default: play once)
391
- * @param {boolean} opts.footer show the "Ctrl+C to leave / try …" hint line
392
- * @param {boolean} opts.goodbye print a farewell line when done
399
+ * @param {boolean} opts.loop keep looping until Ctrl+C (default: play once)
400
+ * @param {boolean} opts.footer show the hint line under the stage
401
+ * @param {boolean} opts.goodbye print a farewell line when done
402
+ * @param {boolean} opts.altScreen use the alternate screen buffer
393
403
  * @returns {Promise<void>}
394
404
  */
395
- function play(sceneName, { loop = false, footer = true, goodbye = false } = {}) {
405
+ function play(sceneName, { loop = false, footer = true, goodbye = false, altScreen = false, maxTicks = Infinity } = {}) {
396
406
  return new Promise((resolve) => {
397
407
  const scene = SCENES[sceneName] || SCENES.idle;
398
408
  const cv = new Canvas(CV_W, CV_H);
399
409
  const state = { count: 0, tip: TIPS[Math.floor(Math.random() * TIPS.length)] };
400
- const totalTicks = loop ? Infinity : scene.cycle + 1; // one clean cycle
410
+ const totalTicks = loop ? Infinity : Math.min(scene.cycle + 1, maxTicks); // one clean cycle, capped
401
411
  let t = 0;
402
412
  let printed = false;
403
413
  let done = false;
@@ -408,7 +418,6 @@ function play(sceneName, { loop = false, footer = true, goodbye = false } = {})
408
418
  chalk.gray(' Β· more scenes: ') + C.say('gent pet push|pull|merge')
409
419
  : '';
410
420
 
411
- // Draw one frame, moving the cursor back over the previous frame.
412
421
  const paint = () => {
413
422
  cv.clear();
414
423
  scene.fn(cv, t, state);
@@ -418,8 +427,12 @@ function play(sceneName, { loop = false, footer = true, goodbye = false } = {})
418
427
  const lines = cv.render().split('\n');
419
428
  lines.push(footerLine());
420
429
  const block = lines.map(l => CLEAR_LINE + l).join('\n');
421
- if (printed) process.stdout.write(up(lines.length));
422
- process.stdout.write(block + '\n');
430
+ if (altScreen) {
431
+ process.stdout.write(HOME + block);
432
+ } else {
433
+ if (printed) process.stdout.write(up(lines.length));
434
+ process.stdout.write(block + '\n');
435
+ }
423
436
  printed = true;
424
437
  };
425
438
 
@@ -428,7 +441,7 @@ function play(sceneName, { loop = false, footer = true, goodbye = false } = {})
428
441
  done = true;
429
442
  clearInterval(timer);
430
443
  process.removeListener('SIGINT', onSig);
431
- process.stdout.write(SHOW);
444
+ process.stdout.write(SHOW + (altScreen ? LEAVE_ALT : ''));
432
445
  if (goodbye) {
433
446
  console.log(C.body(' Genti waves.') + chalk.gray(' Come back with ') + C.say('gent pet') + chalk.gray('.'));
434
447
  }
@@ -437,7 +450,7 @@ function play(sceneName, { loop = false, footer = true, goodbye = false } = {})
437
450
 
438
451
  const onSig = () => finish();
439
452
 
440
- process.stdout.write(HIDE);
453
+ process.stdout.write((altScreen ? ENTER_ALT + HOME : '') + HIDE);
441
454
  paint();
442
455
  const timer = setInterval(() => {
443
456
  t++;
@@ -448,6 +461,22 @@ function play(sceneName, { loop = false, footer = true, goodbye = false } = {})
448
461
  });
449
462
  }
450
463
 
464
+ /**
465
+ * A compact STATIC Genti header β€” safe to print above an interactive prompt
466
+ * (inquirer, etc.) because it never animates or moves the cursor.
467
+ * @param {string} greeting bold line beside the mascot
468
+ * @param {string} [sub] dimmer line under the greeting
469
+ */
470
+ function banner(greeting, sub) {
471
+ if (process.env.GENT_NO_PET || !process.stdout.isTTY) return;
472
+ const cv = new Canvas(52, 9);
473
+ cv.sprite(mascot({}), 1, 0);
474
+ cv.sprite(legs('stand'), 1, 7);
475
+ cv.text(18, 2, greeting, C.body);
476
+ if (sub) cv.text(18, 4, sub, C.say);
477
+ console.log('\n' + cv.render());
478
+ }
479
+
451
480
  // Static single frame for non-TTY (piped) output.
452
481
  function still(sceneName) {
453
482
  const cv = new Canvas(CV_W, CV_H);
@@ -476,8 +505,9 @@ async function petCommand(scene, options = {}) {
476
505
  }
477
506
 
478
507
  if (!process.stdout.isTTY) return still(name);
479
- // Default: play once. `--loop` keeps it running until Ctrl+C.
480
- await play(name, { loop: !!options.loop, footer: true, goodbye: true });
508
+ // Default: play once, on the alternate screen so nothing pollutes scrollback.
509
+ // `--loop` keeps it running until Ctrl+C.
510
+ await play(name, { loop: !!options.loop, footer: true, goodbye: true, altScreen: true });
481
511
  }
482
512
 
483
513
  /**
@@ -492,9 +522,12 @@ async function celebrate(scene) {
492
522
  if (process.env.GENT_NO_PET || process.env.CI) return;
493
523
  if (!SCENES[scene]) return;
494
524
  console.log(); // one blank line between command output and Genti
495
- await play(scene, { loop: false, footer: false, goodbye: false });
525
+ // Auth flows just need a quick friendly wave; action scenes tell a fuller story.
526
+ const maxTicks = (scene === 'auth' || scene === 'login') ? 34 : Infinity;
527
+ await play(scene, { loop: false, footer: false, goodbye: false, maxTicks });
496
528
  } catch (_) { /* ignore β€” decoration only */ }
497
529
  }
498
530
 
499
531
  module.exports = petCommand;
500
532
  module.exports.celebrate = celebrate;
533
+ module.exports.banner = banner;
@@ -8,12 +8,14 @@ const inquirer = require('inquirer');
8
8
  const ora = require('ora');
9
9
  const boxen = require('boxen');
10
10
  const authService = require('../services/auth-service');
11
+ const pet = require('./pet');
11
12
 
12
13
  /**
13
14
  * Register a new user
14
15
  * @param {Object} options - Command options
15
16
  */
16
17
  async function register(options) {
18
+ pet.banner('New here? Let\'s get you set up.', 'Create your account below.');
17
19
  console.log(chalk.cyan('\nπŸš€ Create your Gent account\n'));
18
20
 
19
21
  try {
@@ -120,6 +122,8 @@ ${chalk.bold('Next steps:')}
120
122
  borderColor: 'green'
121
123
  }));
122
124
 
125
+ await pet.celebrate('auth');
126
+
123
127
  } catch (error) {
124
128
  console.error(chalk.red('\nβœ— Registration failed'));
125
129
  console.error(chalk.red(`Error: ${error.message}\n`));