@hypersocial/cli-games 0.2.2 → 0.2.4

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/dist/index.d.ts CHANGED
@@ -591,7 +591,13 @@ interface GamesMenuController {
591
591
  }
592
592
  interface GamesMenuOptions {
593
593
  onGameSelect?: (gameId: string) => void;
594
+ onActionSelect?: (actionId: string) => void;
594
595
  onQuit?: () => void;
596
+ extraActions?: Array<{
597
+ id: string;
598
+ name: string;
599
+ description: string;
600
+ }>;
595
601
  }
596
602
  /**
597
603
  * Show interactive games menu
package/dist/index.js CHANGED
@@ -14157,7 +14157,7 @@ function getRandomCharacter() {
14157
14157
  // src/games/hyper-fighter/index.ts
14158
14158
  var TICK_MS = 50;
14159
14159
  var MIN_COLS = 60;
14160
- var MIN_ROWS = 36;
14160
+ var MIN_ROWS = 30;
14161
14161
  var VS_COL_WIDTH = 12;
14162
14162
  var SIDE_PANEL_WIDTH = 14;
14163
14163
  var HEADER_HEIGHT = 2;
@@ -15567,9 +15567,13 @@ function runHyperFighterGame(terminal) {
15567
15567
  // src/games/gamesMenu.ts
15568
15568
  function showGamesMenu(terminal, optionsOrCallback) {
15569
15569
  const options = typeof optionsOrCallback === "function" ? { onGameSelect: optionsOrCallback } : optionsOrCallback || {};
15570
- const { onGameSelect, onQuit } = options;
15570
+ const { onGameSelect, onActionSelect, onQuit, extraActions = [] } = options;
15571
15571
  const themeColor = getCurrentThemeColor();
15572
15572
  const lightTheme = isLightTheme2();
15573
+ const menuEntries = [
15574
+ ...games.map((game) => ({ ...game, kind: "game" })),
15575
+ ...extraActions.map((action) => ({ ...action, kind: "action" }))
15576
+ ];
15573
15577
  let running = true;
15574
15578
  let selectedIndex = 0;
15575
15579
  let scrollOffset = 0;
@@ -15612,50 +15616,56 @@ function showGamesMenu(terminal, optionsOrCallback) {
15612
15616
  function renderSingleColumn(baseOutput, cols, rows, boxWidth, listStartY, maxVisibleGames) {
15613
15617
  let output = baseOutput;
15614
15618
  const boxX = Math.floor((cols - boxWidth) / 2);
15615
- const visibleGames = Math.min(maxVisibleGames, games.length);
15619
+ const visibleEntries = Math.min(maxVisibleGames, menuEntries.length);
15616
15620
  if (selectedIndex < scrollOffset) {
15617
15621
  scrollOffset = selectedIndex;
15618
- } else if (selectedIndex >= scrollOffset + visibleGames) {
15619
- scrollOffset = selectedIndex - visibleGames + 1;
15622
+ } else if (selectedIndex >= scrollOffset + visibleEntries) {
15623
+ scrollOffset = selectedIndex - visibleEntries + 1;
15620
15624
  }
15621
- scrollOffset = Math.max(0, Math.min(scrollOffset, games.length - visibleGames));
15625
+ scrollOffset = Math.max(0, Math.min(scrollOffset, menuEntries.length - visibleEntries));
15622
15626
  const hasScrollUp = scrollOffset > 0;
15623
- const hasScrollDown = scrollOffset + visibleGames < games.length;
15627
+ const hasScrollDown = scrollOffset + visibleEntries < menuEntries.length;
15624
15628
  const topScrollIndicator = hasScrollUp ? " \u25B2 more " : "\u2550".repeat(8);
15625
15629
  const topBorderWidth = boxWidth - 2 - topScrollIndicator.length;
15626
15630
  const topLeftPad = Math.floor(topBorderWidth / 2);
15627
15631
  const topRightPad = topBorderWidth - topLeftPad;
15628
15632
  output += `\x1B[${listStartY};${boxX}H${themeColor}\u2554${"\u2550".repeat(topLeftPad)}${hasScrollUp ? "\x1B[33m" : ""}${topScrollIndicator}${hasScrollUp ? themeColor : ""}${"\u2550".repeat(topRightPad)}\u2557\x1B[0m`;
15629
- for (let vi = 0; vi < visibleGames; vi++) {
15633
+ for (let vi = 0; vi < visibleEntries; vi++) {
15630
15634
  const i = scrollOffset + vi;
15631
- const game = games[i];
15635
+ const entry = menuEntries[i];
15632
15636
  const y = listStartY + 1 + vi * 2;
15633
15637
  const isSelected = i === selectedIndex;
15634
- output += renderGameEntry(game, i, isSelected, y, boxX, boxWidth);
15638
+ output += renderEntry(entry, i, isSelected, y, boxX, boxWidth);
15635
15639
  }
15636
- const bottomY = listStartY + 1 + visibleGames * 2;
15640
+ const bottomY = listStartY + 1 + visibleEntries * 2;
15637
15641
  const bottomScrollIndicator = hasScrollDown ? " \u25BC more " : "\u2550".repeat(8);
15638
15642
  const bottomBorderWidth = boxWidth - 2 - bottomScrollIndicator.length;
15639
15643
  const bottomLeftPad = Math.floor(bottomBorderWidth / 2);
15640
15644
  const bottomRightPad = bottomBorderWidth - bottomLeftPad;
15641
15645
  output += `\x1B[${bottomY};${boxX}H${themeColor}\u255A${"\u2550".repeat(bottomLeftPad)}${hasScrollDown ? "\x1B[33m" : ""}${bottomScrollIndicator}${hasScrollDown ? themeColor : ""}${"\u2550".repeat(bottomRightPad)}\u255D\x1B[0m`;
15646
+ if (onActionSelect) {
15647
+ const actionHint = "Press V to Vibe Code Your Own Game";
15648
+ const hintX = Math.floor((cols - actionHint.length) / 2);
15649
+ const ctaStyle = lightTheme ? "\x1B[1;30;106m" : "\x1B[1;30;103m";
15650
+ output += `\x1B[${rows};${hintX}H${ctaStyle}${actionHint}\x1B[0m`;
15651
+ }
15642
15652
  const controls = `\u2191\u2193 Navigate | ENTER Select | 1-9 Quick | Q Quit`;
15643
15653
  output += `\x1B[${rows - 1};${Math.floor((cols - controls.length) / 2)}H\x1B[2m${controls}\x1B[0m`;
15644
15654
  terminal.write(output);
15645
15655
  }
15646
15656
  function renderTwoColumns(baseOutput, cols, rows, boxWidth, columnGap, listStartY, maxVisibleGames) {
15647
15657
  let output = baseOutput;
15648
- const gamesPerColumn = Math.min(maxVisibleGames, Math.ceil(games.length / 2));
15658
+ const entriesPerColumn = Math.min(maxVisibleGames, Math.ceil(menuEntries.length / 2));
15649
15659
  const selectedRow = Math.floor(selectedIndex / 2);
15650
15660
  if (selectedRow < scrollOffset) {
15651
15661
  scrollOffset = selectedRow;
15652
- } else if (selectedRow >= scrollOffset + gamesPerColumn) {
15653
- scrollOffset = selectedRow - gamesPerColumn + 1;
15662
+ } else if (selectedRow >= scrollOffset + entriesPerColumn) {
15663
+ scrollOffset = selectedRow - entriesPerColumn + 1;
15654
15664
  }
15655
- const maxScroll = Math.max(0, Math.ceil(games.length / 2) - gamesPerColumn);
15665
+ const maxScroll = Math.max(0, Math.ceil(menuEntries.length / 2) - entriesPerColumn);
15656
15666
  scrollOffset = Math.max(0, Math.min(scrollOffset, maxScroll));
15657
15667
  const hasScrollUp = scrollOffset > 0;
15658
- const hasScrollDown = (scrollOffset + gamesPerColumn) * 2 < games.length;
15668
+ const hasScrollDown = (scrollOffset + entriesPerColumn) * 2 < menuEntries.length;
15659
15669
  const totalWidth = boxWidth * 2 + columnGap;
15660
15670
  const startX = Math.floor((cols - totalWidth) / 2);
15661
15671
  const leftBoxX = startX;
@@ -15666,35 +15676,41 @@ function showGamesMenu(terminal, optionsOrCallback) {
15666
15676
  const topRight = topBorderContent - topLeft;
15667
15677
  output += `\x1B[${listStartY};${leftBoxX}H${themeColor}\u2554${"\u2550".repeat(topLeft)}${hasScrollUp ? "\x1B[33m" : ""}${topScrollIndicator}${hasScrollUp ? themeColor : ""}${"\u2550".repeat(topRight)}\u2557\x1B[0m`;
15668
15678
  output += `\x1B[${listStartY};${rightBoxX}H${themeColor}\u2554${"\u2550".repeat(topLeft)}${hasScrollUp ? "\x1B[33m" : ""}${topScrollIndicator}${hasScrollUp ? themeColor : ""}${"\u2550".repeat(topRight)}\u2557\x1B[0m`;
15669
- for (let row = 0; row < gamesPerColumn; row++) {
15679
+ for (let row = 0; row < entriesPerColumn; row++) {
15670
15680
  const leftIdx = (scrollOffset + row) * 2;
15671
15681
  const rightIdx = leftIdx + 1;
15672
15682
  const y = listStartY + 1 + row * 2;
15673
- if (leftIdx < games.length) {
15674
- output += renderGameEntry(games[leftIdx], leftIdx, leftIdx === selectedIndex, y, leftBoxX, boxWidth);
15683
+ if (leftIdx < menuEntries.length) {
15684
+ output += renderEntry(menuEntries[leftIdx], leftIdx, leftIdx === selectedIndex, y, leftBoxX, boxWidth);
15675
15685
  } else {
15676
15686
  output += `\x1B[${y};${leftBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15677
15687
  output += `\x1B[${y + 1};${leftBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15678
15688
  }
15679
- if (rightIdx < games.length) {
15680
- output += renderGameEntry(games[rightIdx], rightIdx, rightIdx === selectedIndex, y, rightBoxX, boxWidth);
15689
+ if (rightIdx < menuEntries.length) {
15690
+ output += renderEntry(menuEntries[rightIdx], rightIdx, rightIdx === selectedIndex, y, rightBoxX, boxWidth);
15681
15691
  } else {
15682
15692
  output += `\x1B[${y};${rightBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15683
15693
  output += `\x1B[${y + 1};${rightBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15684
15694
  }
15685
15695
  }
15686
- const bottomY = listStartY + 1 + gamesPerColumn * 2;
15696
+ const bottomY = listStartY + 1 + entriesPerColumn * 2;
15687
15697
  const bottomScrollIndicator = hasScrollDown ? " \u25BC " : "\u2550\u2550\u2550";
15688
15698
  const bottomBorderContent = boxWidth - 2 - bottomScrollIndicator.length;
15689
15699
  const bottomLeft = Math.floor(bottomBorderContent / 2);
15690
15700
  const bottomRight = bottomBorderContent - bottomLeft;
15691
15701
  output += `\x1B[${bottomY};${leftBoxX}H${themeColor}\u255A${"\u2550".repeat(bottomLeft)}${hasScrollDown ? "\x1B[33m" : ""}${bottomScrollIndicator}${hasScrollDown ? themeColor : ""}${"\u2550".repeat(bottomRight)}\u255D\x1B[0m`;
15692
15702
  output += `\x1B[${bottomY};${rightBoxX}H${themeColor}\u255A${"\u2550".repeat(bottomLeft)}${hasScrollDown ? "\x1B[33m" : ""}${bottomScrollIndicator}${hasScrollDown ? themeColor : ""}${"\u2550".repeat(bottomRight)}\u255D\x1B[0m`;
15703
+ if (onActionSelect) {
15704
+ const actionHint = "Press V to Vibe Code Your Own Game";
15705
+ const hintX = Math.floor((cols - actionHint.length) / 2);
15706
+ const ctaStyle = lightTheme ? "\x1B[1;30;106m" : "\x1B[1;30;103m";
15707
+ output += `\x1B[${rows};${hintX}H${ctaStyle}${actionHint}\x1B[0m`;
15708
+ }
15693
15709
  const controls = `\u2191\u2193\u2190\u2192 Navigate | ENTER Select | 1-9 Quick | Q Quit`;
15694
15710
  output += `\x1B[${rows - 1};${Math.floor((cols - controls.length) / 2)}H\x1B[2m${controls}\x1B[0m`;
15695
15711
  terminal.write(output);
15696
15712
  }
15697
- function renderGameEntry(game, index, isSelected, y, boxX, boxWidth) {
15713
+ function renderEntry(entry, index, isSelected, y, boxX, boxWidth) {
15698
15714
  let output = "";
15699
15715
  const contentWidth = boxWidth - 2;
15700
15716
  const prefix = isSelected ? "\u25B6" : " ";
@@ -15702,10 +15718,10 @@ function showGamesMenu(terminal, optionsOrCallback) {
15702
15718
  const keyColor = isSelected ? "" : "\x1B[33m";
15703
15719
  const keyNum = index + 1;
15704
15720
  const keyDisplay = keyNum <= 9 ? `${keyNum}` : " ";
15705
- const lineContent = `${prefix} [${keyDisplay}] ${game.name}`;
15721
+ const lineContent = `${prefix} [${keyDisplay}] ${entry.name}`;
15706
15722
  const padding = Math.max(0, contentWidth - lineContent.length);
15707
15723
  output += `\x1B[${y};${boxX}H${themeColor}\u2551\x1B[0m${highlight}${keyColor}${lineContent}${" ".repeat(padding)}\x1B[0m${themeColor}\u2551\x1B[0m`;
15708
- const descContent = ` ${game.description}`;
15724
+ const descContent = ` ${entry.description}`;
15709
15725
  const descPadding = Math.max(0, contentWidth - descContent.length);
15710
15726
  const descColor = isSelected ? lightTheme ? "\x1B[2;7;97m" : "\x1B[2;7m" : "\x1B[2m";
15711
15727
  output += `\x1B[${y + 1};${boxX}H${themeColor}\u2551\x1B[0m${descColor}${descContent}${" ".repeat(descPadding)}\x1B[0m${themeColor}\u2551\x1B[0m`;
@@ -15753,16 +15769,16 @@ function showGamesMenu(terminal, optionsOrCallback) {
15753
15769
  if (useTwoColumns) {
15754
15770
  selectedIndex = Math.max(0, selectedIndex - 2);
15755
15771
  } else {
15756
- selectedIndex = (selectedIndex - 1 + games.length) % games.length;
15772
+ selectedIndex = (selectedIndex - 1 + menuEntries.length) % menuEntries.length;
15757
15773
  }
15758
15774
  render();
15759
15775
  return;
15760
15776
  }
15761
15777
  if (key === "ArrowDown") {
15762
15778
  if (useTwoColumns) {
15763
- selectedIndex = Math.min(games.length - 1, selectedIndex + 2);
15779
+ selectedIndex = Math.min(menuEntries.length - 1, selectedIndex + 2);
15764
15780
  } else {
15765
- selectedIndex = (selectedIndex + 1) % games.length;
15781
+ selectedIndex = (selectedIndex + 1) % menuEntries.length;
15766
15782
  }
15767
15783
  render();
15768
15784
  return;
@@ -15775,38 +15791,49 @@ function showGamesMenu(terminal, optionsOrCallback) {
15775
15791
  return;
15776
15792
  }
15777
15793
  if (key === "ArrowRight" && useTwoColumns) {
15778
- if (selectedIndex % 2 === 0 && selectedIndex + 1 < games.length) {
15794
+ if (selectedIndex % 2 === 0 && selectedIndex + 1 < menuEntries.length) {
15779
15795
  selectedIndex++;
15780
15796
  }
15781
15797
  render();
15782
15798
  return;
15783
15799
  }
15784
- const launchGame = async (game) => {
15800
+ if (keyLower === "v" && onActionSelect) {
15801
+ keyListener?.dispose();
15802
+ running = false;
15803
+ exitAlternateBuffer(terminal, "games-menu-vibe");
15804
+ onActionSelect("vibe");
15805
+ return;
15806
+ }
15807
+ const launchEntry = async (entry) => {
15785
15808
  keyListener?.dispose();
15786
15809
  running = false;
15787
15810
  exitAlternateBuffer(terminal, "games-menu-launch");
15788
15811
  try {
15789
- await playSelectTransition(terminal, game.name);
15790
- if (onGameSelect) {
15791
- onGameSelect(game.id);
15812
+ if (entry.kind === "game") {
15813
+ await playSelectTransition(terminal, entry.name);
15814
+ if (onGameSelect) {
15815
+ onGameSelect(entry.id);
15816
+ }
15817
+ } else if (onActionSelect) {
15818
+ onActionSelect(entry.id);
15792
15819
  }
15793
15820
  } catch (err) {
15794
- console.error("[GamesMenu] Failed to launch game:", err);
15821
+ console.error("[GamesMenu] Failed to launch menu entry:", err);
15795
15822
  forceExitAlternateBuffer(terminal, "games-menu-launch-error");
15796
- terminal.write(`\x1B[91mError launching game: ${err}\x1B[0m\r
15823
+ terminal.write(`\x1B[91mError launching menu entry: ${err}\x1B[0m\r
15797
15824
  `);
15798
15825
  }
15799
15826
  };
15800
15827
  if (key === "Enter") {
15801
- launchGame(games[selectedIndex]).catch((err) => {
15802
- console.error("[GamesMenu] Unhandled launch error:", err);
15828
+ launchEntry(menuEntries[selectedIndex]).catch((err) => {
15829
+ console.error("[GamesMenu] Unhandled menu launch error:", err);
15803
15830
  });
15804
15831
  return;
15805
15832
  }
15806
15833
  const numKey = parseInt(key);
15807
- if (numKey >= 1 && numKey <= games.length && numKey <= 9) {
15808
- launchGame(games[numKey - 1]).catch((err) => {
15809
- console.error("[GamesMenu] Unhandled launch error:", err);
15834
+ if (numKey >= 1 && numKey <= menuEntries.length && numKey <= 9) {
15835
+ launchEntry(menuEntries[numKey - 1]).catch((err) => {
15836
+ console.error("[GamesMenu] Unhandled menu launch error:", err);
15810
15837
  });
15811
15838
  return;
15812
15839
  }