@hypersocial/cli-games 0.2.3 → 0.2.5

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/cli.js CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli.ts
4
+ import { spawn } from "child_process";
5
+ import { fileURLToPath } from "url";
6
+
3
7
  // src/themes/index.ts
4
8
  var themes = {
5
9
  cyan: {
@@ -2891,7 +2895,7 @@ function runCourierGame(terminal) {
2891
2895
  flapAnimation = 4;
2892
2896
  spawnParticles(particles, rigX, rigY + 2, 2, "\x1B[93m", ["\xB7", "\u2218"]);
2893
2897
  }
2894
- const spawn = (x, y, count, color, chars) => spawnParticles(particles, x, y, count, color, chars);
2898
+ const spawn2 = (x, y, count, color, chars) => spawnParticles(particles, x, y, count, color, chars);
2895
2899
  const firework = (x, y, intensity) => spawnFirework(particles, x, y, intensity);
2896
2900
  const popup = (x, y, text, color) => addPopup(popups, x, y, text, color);
2897
2901
  const sparkleTrail = (x, y, count, color) => spawnSparkleTrail(particles, x, y, count, color);
@@ -2972,7 +2976,7 @@ function runCourierGame(terminal) {
2972
2976
  if (levelCompleteAnimation % 8 === 0) {
2973
2977
  const burstX = 5 + Math.random() * (screenWidth - 10);
2974
2978
  const burstY = 2 + Math.random() * (screenHeight * 0.4);
2975
- spawn(
2979
+ spawn2(
2976
2980
  burstX,
2977
2981
  burstY,
2978
2982
  5,
@@ -3184,7 +3188,7 @@ function runCourierGame(terminal) {
3184
3188
  rigVY = -rigVY * 0.3;
3185
3189
  if (impactVelocity > 0.3) {
3186
3190
  screenShake = Math.min(5, Math.floor(impactVelocity * 4));
3187
- spawn(rigX, rigY + 2, Math.floor(impactVelocity * 5), "\x1B[93m", ["\xB7", "\u2022"]);
3191
+ spawn2(rigX, rigY + 2, Math.floor(impactVelocity * 5), "\x1B[93m", ["\xB7", "\u2022"]);
3188
3192
  }
3189
3193
  if (rigPlatformIndex === pickupPlatformIndex && !hasPassenger && tutorialCountdown === 0) {
3190
3194
  hasPassenger = true;
@@ -3193,14 +3197,14 @@ function runCourierGame(terminal) {
3193
3197
  passengerVX = rigVX * 0.5;
3194
3198
  passengerVY = rigVY * 0.5;
3195
3199
  passengerGraceFrames = 20;
3196
- spawn(rigX, rigY + 2, 6, themeColor, ["\u25C7", "\u2726"]);
3200
+ spawn2(rigX, rigY + 2, 6, themeColor, ["\u25C7", "\u2726"]);
3197
3201
  popup(rigX - 3, rigY - 1, "GOT IT!", "\x1B[1;93m");
3198
3202
  doorAnimation = 30;
3199
3203
  const dropoffPlat = level.platforms[dropoffPlatformIndex];
3200
3204
  const dropX = dropoffPlat.x + Math.floor(dropoffPlat.width / 2);
3201
3205
  const dropY = dropoffPlat.y - 5;
3202
3206
  popup(dropX - 4, dropY, "DROP HERE!", "\x1B[1;93m");
3203
- spawn(dropX, dropY + 2, 4, "\x1B[93m", ["\u25BD", "\u25C7", "\u2726"]);
3207
+ spawn2(dropX, dropY + 2, 4, "\x1B[93m", ["\u25BD", "\u25C7", "\u2726"]);
3204
3208
  }
3205
3209
  }
3206
3210
  if (checkWaterCollision(rigY + 2)) {
@@ -3213,7 +3217,7 @@ function runCourierGame(terminal) {
3213
3217
  crashAnimation = 35;
3214
3218
  screenShake = 15;
3215
3219
  splash(rigX, level.waterLevel, 1.25);
3216
- spawn(rigX, rigY, 12, "\x1B[91m", ["\u2717", "\xD7", "\u2593", "\u2591", "!", "*"]);
3220
+ spawn2(rigX, rigY, 12, "\x1B[91m", ["\u2717", "\xD7", "\u2593", "\u2591", "!", "*"]);
3217
3221
  popup(rigX - 4, rigY - 4, "\u{1F480} SPLASH! \u{1F480}", "\x1B[1;91m");
3218
3222
  return;
3219
3223
  }
@@ -3248,7 +3252,7 @@ function runCourierGame(terminal) {
3248
3252
  passengerVX *= 0.8;
3249
3253
  if (passPlatformIndex === dropoffPlatformIndex && Math.abs(passengerVY) < 0.3 && deliveringAnimation === 0) {
3250
3254
  deliveringAnimation = 40;
3251
- spawn(passengerX, passengerY - 1, 8, "\x1B[1;93m", ["\u2605", "\u2726", "\u25C6"]);
3255
+ spawn2(passengerX, passengerY - 1, 8, "\x1B[1;93m", ["\u2605", "\u2726", "\u25C6"]);
3252
3256
  popup(passengerX - 4, passengerY - 3, "DROPPING!", "\x1B[1;93m");
3253
3257
  }
3254
3258
  }
@@ -15636,9 +15640,13 @@ function runHyperFighterGame(terminal) {
15636
15640
  // src/games/gamesMenu.ts
15637
15641
  function showGamesMenu(terminal, optionsOrCallback) {
15638
15642
  const options = typeof optionsOrCallback === "function" ? { onGameSelect: optionsOrCallback } : optionsOrCallback || {};
15639
- const { onGameSelect, onQuit } = options;
15643
+ const { onGameSelect, onActionSelect, onQuit, extraActions = [] } = options;
15640
15644
  const themeColor = getCurrentThemeColor();
15641
15645
  const lightTheme = isLightTheme2();
15646
+ const menuEntries = [
15647
+ ...games.map((game) => ({ ...game, kind: "game" })),
15648
+ ...extraActions.map((action) => ({ ...action, kind: "action" }))
15649
+ ];
15642
15650
  let running = true;
15643
15651
  let selectedIndex = 0;
15644
15652
  let scrollOffset = 0;
@@ -15681,50 +15689,56 @@ function showGamesMenu(terminal, optionsOrCallback) {
15681
15689
  function renderSingleColumn(baseOutput, cols, rows, boxWidth, listStartY, maxVisibleGames) {
15682
15690
  let output = baseOutput;
15683
15691
  const boxX = Math.floor((cols - boxWidth) / 2);
15684
- const visibleGames = Math.min(maxVisibleGames, games.length);
15692
+ const visibleEntries = Math.min(maxVisibleGames, menuEntries.length);
15685
15693
  if (selectedIndex < scrollOffset) {
15686
15694
  scrollOffset = selectedIndex;
15687
- } else if (selectedIndex >= scrollOffset + visibleGames) {
15688
- scrollOffset = selectedIndex - visibleGames + 1;
15695
+ } else if (selectedIndex >= scrollOffset + visibleEntries) {
15696
+ scrollOffset = selectedIndex - visibleEntries + 1;
15689
15697
  }
15690
- scrollOffset = Math.max(0, Math.min(scrollOffset, games.length - visibleGames));
15698
+ scrollOffset = Math.max(0, Math.min(scrollOffset, menuEntries.length - visibleEntries));
15691
15699
  const hasScrollUp = scrollOffset > 0;
15692
- const hasScrollDown = scrollOffset + visibleGames < games.length;
15700
+ const hasScrollDown = scrollOffset + visibleEntries < menuEntries.length;
15693
15701
  const topScrollIndicator = hasScrollUp ? " \u25B2 more " : "\u2550".repeat(8);
15694
15702
  const topBorderWidth = boxWidth - 2 - topScrollIndicator.length;
15695
15703
  const topLeftPad = Math.floor(topBorderWidth / 2);
15696
15704
  const topRightPad = topBorderWidth - topLeftPad;
15697
15705
  output += `\x1B[${listStartY};${boxX}H${themeColor}\u2554${"\u2550".repeat(topLeftPad)}${hasScrollUp ? "\x1B[33m" : ""}${topScrollIndicator}${hasScrollUp ? themeColor : ""}${"\u2550".repeat(topRightPad)}\u2557\x1B[0m`;
15698
- for (let vi = 0; vi < visibleGames; vi++) {
15706
+ for (let vi = 0; vi < visibleEntries; vi++) {
15699
15707
  const i = scrollOffset + vi;
15700
- const game = games[i];
15708
+ const entry = menuEntries[i];
15701
15709
  const y = listStartY + 1 + vi * 2;
15702
15710
  const isSelected = i === selectedIndex;
15703
- output += renderGameEntry(game, i, isSelected, y, boxX, boxWidth);
15711
+ output += renderEntry(entry, i, isSelected, y, boxX, boxWidth);
15704
15712
  }
15705
- const bottomY = listStartY + 1 + visibleGames * 2;
15713
+ const bottomY = listStartY + 1 + visibleEntries * 2;
15706
15714
  const bottomScrollIndicator = hasScrollDown ? " \u25BC more " : "\u2550".repeat(8);
15707
15715
  const bottomBorderWidth = boxWidth - 2 - bottomScrollIndicator.length;
15708
15716
  const bottomLeftPad = Math.floor(bottomBorderWidth / 2);
15709
15717
  const bottomRightPad = bottomBorderWidth - bottomLeftPad;
15710
15718
  output += `\x1B[${bottomY};${boxX}H${themeColor}\u255A${"\u2550".repeat(bottomLeftPad)}${hasScrollDown ? "\x1B[33m" : ""}${bottomScrollIndicator}${hasScrollDown ? themeColor : ""}${"\u2550".repeat(bottomRightPad)}\u255D\x1B[0m`;
15719
+ if (onActionSelect) {
15720
+ const actionHint = "Press V to Vibe Code Your Own Game";
15721
+ const hintX = Math.floor((cols - actionHint.length) / 2);
15722
+ const ctaStyle = lightTheme ? "\x1B[1;30;106m" : "\x1B[1;30;103m";
15723
+ output += `\x1B[${rows};${hintX}H${ctaStyle}${actionHint}\x1B[0m`;
15724
+ }
15711
15725
  const controls = `\u2191\u2193 Navigate | ENTER Select | 1-9 Quick | Q Quit`;
15712
15726
  output += `\x1B[${rows - 1};${Math.floor((cols - controls.length) / 2)}H\x1B[2m${controls}\x1B[0m`;
15713
15727
  terminal.write(output);
15714
15728
  }
15715
15729
  function renderTwoColumns(baseOutput, cols, rows, boxWidth, columnGap, listStartY, maxVisibleGames) {
15716
15730
  let output = baseOutput;
15717
- const gamesPerColumn = Math.min(maxVisibleGames, Math.ceil(games.length / 2));
15731
+ const entriesPerColumn = Math.min(maxVisibleGames, Math.ceil(menuEntries.length / 2));
15718
15732
  const selectedRow = Math.floor(selectedIndex / 2);
15719
15733
  if (selectedRow < scrollOffset) {
15720
15734
  scrollOffset = selectedRow;
15721
- } else if (selectedRow >= scrollOffset + gamesPerColumn) {
15722
- scrollOffset = selectedRow - gamesPerColumn + 1;
15735
+ } else if (selectedRow >= scrollOffset + entriesPerColumn) {
15736
+ scrollOffset = selectedRow - entriesPerColumn + 1;
15723
15737
  }
15724
- const maxScroll = Math.max(0, Math.ceil(games.length / 2) - gamesPerColumn);
15738
+ const maxScroll = Math.max(0, Math.ceil(menuEntries.length / 2) - entriesPerColumn);
15725
15739
  scrollOffset = Math.max(0, Math.min(scrollOffset, maxScroll));
15726
15740
  const hasScrollUp = scrollOffset > 0;
15727
- const hasScrollDown = (scrollOffset + gamesPerColumn) * 2 < games.length;
15741
+ const hasScrollDown = (scrollOffset + entriesPerColumn) * 2 < menuEntries.length;
15728
15742
  const totalWidth = boxWidth * 2 + columnGap;
15729
15743
  const startX = Math.floor((cols - totalWidth) / 2);
15730
15744
  const leftBoxX = startX;
@@ -15735,35 +15749,41 @@ function showGamesMenu(terminal, optionsOrCallback) {
15735
15749
  const topRight = topBorderContent - topLeft;
15736
15750
  output += `\x1B[${listStartY};${leftBoxX}H${themeColor}\u2554${"\u2550".repeat(topLeft)}${hasScrollUp ? "\x1B[33m" : ""}${topScrollIndicator}${hasScrollUp ? themeColor : ""}${"\u2550".repeat(topRight)}\u2557\x1B[0m`;
15737
15751
  output += `\x1B[${listStartY};${rightBoxX}H${themeColor}\u2554${"\u2550".repeat(topLeft)}${hasScrollUp ? "\x1B[33m" : ""}${topScrollIndicator}${hasScrollUp ? themeColor : ""}${"\u2550".repeat(topRight)}\u2557\x1B[0m`;
15738
- for (let row = 0; row < gamesPerColumn; row++) {
15752
+ for (let row = 0; row < entriesPerColumn; row++) {
15739
15753
  const leftIdx = (scrollOffset + row) * 2;
15740
15754
  const rightIdx = leftIdx + 1;
15741
15755
  const y = listStartY + 1 + row * 2;
15742
- if (leftIdx < games.length) {
15743
- output += renderGameEntry(games[leftIdx], leftIdx, leftIdx === selectedIndex, y, leftBoxX, boxWidth);
15756
+ if (leftIdx < menuEntries.length) {
15757
+ output += renderEntry(menuEntries[leftIdx], leftIdx, leftIdx === selectedIndex, y, leftBoxX, boxWidth);
15744
15758
  } else {
15745
15759
  output += `\x1B[${y};${leftBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15746
15760
  output += `\x1B[${y + 1};${leftBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15747
15761
  }
15748
- if (rightIdx < games.length) {
15749
- output += renderGameEntry(games[rightIdx], rightIdx, rightIdx === selectedIndex, y, rightBoxX, boxWidth);
15762
+ if (rightIdx < menuEntries.length) {
15763
+ output += renderEntry(menuEntries[rightIdx], rightIdx, rightIdx === selectedIndex, y, rightBoxX, boxWidth);
15750
15764
  } else {
15751
15765
  output += `\x1B[${y};${rightBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15752
15766
  output += `\x1B[${y + 1};${rightBoxX}H${themeColor}\u2551${" ".repeat(boxWidth - 2)}\u2551\x1B[0m`;
15753
15767
  }
15754
15768
  }
15755
- const bottomY = listStartY + 1 + gamesPerColumn * 2;
15769
+ const bottomY = listStartY + 1 + entriesPerColumn * 2;
15756
15770
  const bottomScrollIndicator = hasScrollDown ? " \u25BC " : "\u2550\u2550\u2550";
15757
15771
  const bottomBorderContent = boxWidth - 2 - bottomScrollIndicator.length;
15758
15772
  const bottomLeft = Math.floor(bottomBorderContent / 2);
15759
15773
  const bottomRight = bottomBorderContent - bottomLeft;
15760
15774
  output += `\x1B[${bottomY};${leftBoxX}H${themeColor}\u255A${"\u2550".repeat(bottomLeft)}${hasScrollDown ? "\x1B[33m" : ""}${bottomScrollIndicator}${hasScrollDown ? themeColor : ""}${"\u2550".repeat(bottomRight)}\u255D\x1B[0m`;
15761
15775
  output += `\x1B[${bottomY};${rightBoxX}H${themeColor}\u255A${"\u2550".repeat(bottomLeft)}${hasScrollDown ? "\x1B[33m" : ""}${bottomScrollIndicator}${hasScrollDown ? themeColor : ""}${"\u2550".repeat(bottomRight)}\u255D\x1B[0m`;
15776
+ if (onActionSelect) {
15777
+ const actionHint = "Press V to Vibe Code Your Own Game";
15778
+ const hintX = Math.floor((cols - actionHint.length) / 2);
15779
+ const ctaStyle = lightTheme ? "\x1B[1;30;106m" : "\x1B[1;30;103m";
15780
+ output += `\x1B[${rows};${hintX}H${ctaStyle}${actionHint}\x1B[0m`;
15781
+ }
15762
15782
  const controls = `\u2191\u2193\u2190\u2192 Navigate | ENTER Select | 1-9 Quick | Q Quit`;
15763
15783
  output += `\x1B[${rows - 1};${Math.floor((cols - controls.length) / 2)}H\x1B[2m${controls}\x1B[0m`;
15764
15784
  terminal.write(output);
15765
15785
  }
15766
- function renderGameEntry(game, index, isSelected, y, boxX, boxWidth) {
15786
+ function renderEntry(entry, index, isSelected, y, boxX, boxWidth) {
15767
15787
  let output = "";
15768
15788
  const contentWidth = boxWidth - 2;
15769
15789
  const prefix = isSelected ? "\u25B6" : " ";
@@ -15771,10 +15791,10 @@ function showGamesMenu(terminal, optionsOrCallback) {
15771
15791
  const keyColor = isSelected ? "" : "\x1B[33m";
15772
15792
  const keyNum = index + 1;
15773
15793
  const keyDisplay = keyNum <= 9 ? `${keyNum}` : " ";
15774
- const lineContent = `${prefix} [${keyDisplay}] ${game.name}`;
15794
+ const lineContent = `${prefix} [${keyDisplay}] ${entry.name}`;
15775
15795
  const padding = Math.max(0, contentWidth - lineContent.length);
15776
15796
  output += `\x1B[${y};${boxX}H${themeColor}\u2551\x1B[0m${highlight}${keyColor}${lineContent}${" ".repeat(padding)}\x1B[0m${themeColor}\u2551\x1B[0m`;
15777
- const descContent = ` ${game.description}`;
15797
+ const descContent = ` ${entry.description}`;
15778
15798
  const descPadding = Math.max(0, contentWidth - descContent.length);
15779
15799
  const descColor = isSelected ? lightTheme ? "\x1B[2;7;97m" : "\x1B[2;7m" : "\x1B[2m";
15780
15800
  output += `\x1B[${y + 1};${boxX}H${themeColor}\u2551\x1B[0m${descColor}${descContent}${" ".repeat(descPadding)}\x1B[0m${themeColor}\u2551\x1B[0m`;
@@ -15822,16 +15842,16 @@ function showGamesMenu(terminal, optionsOrCallback) {
15822
15842
  if (useTwoColumns) {
15823
15843
  selectedIndex = Math.max(0, selectedIndex - 2);
15824
15844
  } else {
15825
- selectedIndex = (selectedIndex - 1 + games.length) % games.length;
15845
+ selectedIndex = (selectedIndex - 1 + menuEntries.length) % menuEntries.length;
15826
15846
  }
15827
15847
  render();
15828
15848
  return;
15829
15849
  }
15830
15850
  if (key === "ArrowDown") {
15831
15851
  if (useTwoColumns) {
15832
- selectedIndex = Math.min(games.length - 1, selectedIndex + 2);
15852
+ selectedIndex = Math.min(menuEntries.length - 1, selectedIndex + 2);
15833
15853
  } else {
15834
- selectedIndex = (selectedIndex + 1) % games.length;
15854
+ selectedIndex = (selectedIndex + 1) % menuEntries.length;
15835
15855
  }
15836
15856
  render();
15837
15857
  return;
@@ -15844,38 +15864,49 @@ function showGamesMenu(terminal, optionsOrCallback) {
15844
15864
  return;
15845
15865
  }
15846
15866
  if (key === "ArrowRight" && useTwoColumns) {
15847
- if (selectedIndex % 2 === 0 && selectedIndex + 1 < games.length) {
15867
+ if (selectedIndex % 2 === 0 && selectedIndex + 1 < menuEntries.length) {
15848
15868
  selectedIndex++;
15849
15869
  }
15850
15870
  render();
15851
15871
  return;
15852
15872
  }
15853
- const launchGame2 = async (game) => {
15873
+ if (keyLower === "v" && onActionSelect) {
15874
+ keyListener?.dispose();
15875
+ running = false;
15876
+ exitAlternateBuffer(terminal, "games-menu-vibe");
15877
+ onActionSelect("vibe");
15878
+ return;
15879
+ }
15880
+ const launchEntry = async (entry) => {
15854
15881
  keyListener?.dispose();
15855
15882
  running = false;
15856
15883
  exitAlternateBuffer(terminal, "games-menu-launch");
15857
15884
  try {
15858
- await playSelectTransition(terminal, game.name);
15859
- if (onGameSelect) {
15860
- onGameSelect(game.id);
15885
+ if (entry.kind === "game") {
15886
+ await playSelectTransition(terminal, entry.name);
15887
+ if (onGameSelect) {
15888
+ onGameSelect(entry.id);
15889
+ }
15890
+ } else if (onActionSelect) {
15891
+ onActionSelect(entry.id);
15861
15892
  }
15862
15893
  } catch (err) {
15863
- console.error("[GamesMenu] Failed to launch game:", err);
15894
+ console.error("[GamesMenu] Failed to launch menu entry:", err);
15864
15895
  forceExitAlternateBuffer(terminal, "games-menu-launch-error");
15865
- terminal.write(`\x1B[91mError launching game: ${err}\x1B[0m\r
15896
+ terminal.write(`\x1B[91mError launching menu entry: ${err}\x1B[0m\r
15866
15897
  `);
15867
15898
  }
15868
15899
  };
15869
15900
  if (key === "Enter") {
15870
- launchGame2(games[selectedIndex]).catch((err) => {
15871
- console.error("[GamesMenu] Unhandled launch error:", err);
15901
+ launchEntry(menuEntries[selectedIndex]).catch((err) => {
15902
+ console.error("[GamesMenu] Unhandled menu launch error:", err);
15872
15903
  });
15873
15904
  return;
15874
15905
  }
15875
15906
  const numKey = parseInt(key);
15876
- if (numKey >= 1 && numKey <= games.length && numKey <= 9) {
15877
- launchGame2(games[numKey - 1]).catch((err) => {
15878
- console.error("[GamesMenu] Unhandled launch error:", err);
15907
+ if (numKey >= 1 && numKey <= menuEntries.length && numKey <= 9) {
15908
+ launchEntry(menuEntries[numKey - 1]).catch((err) => {
15909
+ console.error("[GamesMenu] Unhandled menu launch error:", err);
15879
15910
  });
15880
15911
  return;
15881
15912
  }
@@ -16157,11 +16188,35 @@ function openMenu(terminal) {
16157
16188
  const game = games.find((g) => g.id === gameId);
16158
16189
  if (game) launchGame(terminal, game);
16159
16190
  },
16191
+ onActionSelect: (actionId) => {
16192
+ if (actionId !== "vibe") return;
16193
+ launchVibeFromMenu();
16194
+ },
16160
16195
  onQuit: () => {
16161
16196
  process.exit(0);
16162
16197
  }
16163
16198
  });
16164
16199
  }
16200
+ function launchVibeFromMenu() {
16201
+ if (process.stdin.isTTY) {
16202
+ process.stdin.setRawMode(false);
16203
+ }
16204
+ process.stdin.pause();
16205
+ process.stdout.write("\x1B[?25h");
16206
+ process.stdout.write("\x1B[0m");
16207
+ const cliPath = fileURLToPath(import.meta.url);
16208
+ const child = spawn(process.execPath, [cliPath, "vibe"], {
16209
+ stdio: "inherit",
16210
+ env: process.env
16211
+ });
16212
+ child.on("close", (code) => {
16213
+ process.exit(code ?? 0);
16214
+ });
16215
+ child.on("error", () => {
16216
+ console.error("Failed to launch vibe command");
16217
+ process.exit(1);
16218
+ });
16219
+ }
16165
16220
  function launchGame(terminal, game) {
16166
16221
  game.run(terminal);
16167
16222
  }