clibuddy 1.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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/adventure/adventureUI.d.ts +24 -0
  4. package/dist/adventure/adventureUI.js +290 -0
  5. package/dist/adventure/adventures.d.ts +4 -0
  6. package/dist/adventure/adventures.js +206 -0
  7. package/dist/adventure/biomes.d.ts +30 -0
  8. package/dist/adventure/biomes.js +80 -0
  9. package/dist/adventure/combat/combat.d.ts +14 -0
  10. package/dist/adventure/combat/combat.js +638 -0
  11. package/dist/adventure/combat/combatUI.d.ts +5 -0
  12. package/dist/adventure/combat/combatUI.js +116 -0
  13. package/dist/adventure/combat/conditions.d.ts +20 -0
  14. package/dist/adventure/combat/conditions.js +111 -0
  15. package/dist/adventure/combat/enemies.d.ts +4 -0
  16. package/dist/adventure/combat/enemies.js +430 -0
  17. package/dist/adventure/combat/gear.d.ts +3 -0
  18. package/dist/adventure/combat/gear.js +199 -0
  19. package/dist/adventure/combat/skills.d.ts +6 -0
  20. package/dist/adventure/combat/skills.js +197 -0
  21. package/dist/adventure/combat.d.ts +31 -0
  22. package/dist/adventure/combat.js +732 -0
  23. package/dist/adventure/combatUI.d.ts +5 -0
  24. package/dist/adventure/combatUI.js +116 -0
  25. package/dist/adventure/endless.d.ts +18 -0
  26. package/dist/adventure/endless.js +154 -0
  27. package/dist/adventure/enemies.d.ts +4 -0
  28. package/dist/adventure/enemies.js +320 -0
  29. package/dist/adventure/engine.d.ts +20 -0
  30. package/dist/adventure/engine.js +137 -0
  31. package/dist/adventure/gear.d.ts +3 -0
  32. package/dist/adventure/gear.js +149 -0
  33. package/dist/adventure/generation/biomes.d.ts +30 -0
  34. package/dist/adventure/generation/biomes.js +102 -0
  35. package/dist/adventure/generation/endless.d.ts +18 -0
  36. package/dist/adventure/generation/endless.js +154 -0
  37. package/dist/adventure/generation/generator.d.ts +9 -0
  38. package/dist/adventure/generation/generator.js +245 -0
  39. package/dist/adventure/generation/templates.d.ts +25 -0
  40. package/dist/adventure/generation/templates.js +228 -0
  41. package/dist/adventure/generator.d.ts +9 -0
  42. package/dist/adventure/generator.js +245 -0
  43. package/dist/adventure/skills.d.ts +6 -0
  44. package/dist/adventure/skills.js +197 -0
  45. package/dist/adventure/templates.d.ts +25 -0
  46. package/dist/adventure/templates.js +228 -0
  47. package/dist/adventure/types.d.ts +236 -0
  48. package/dist/adventure/types.js +97 -0
  49. package/dist/app/state.d.ts +49 -0
  50. package/dist/app/state.js +51 -0
  51. package/dist/buddy/activities.d.ts +16 -0
  52. package/dist/buddy/activities.js +90 -0
  53. package/dist/buddy/decay.d.ts +3 -0
  54. package/dist/buddy/decay.js +45 -0
  55. package/dist/buddy/leveling.d.ts +11 -0
  56. package/dist/buddy/leveling.js +25 -0
  57. package/dist/buddy/roll.d.ts +4 -0
  58. package/dist/buddy/roll.js +61 -0
  59. package/dist/buddy/species.d.ts +4 -0
  60. package/dist/buddy/species.js +592 -0
  61. package/dist/buddy/titles.d.ts +17 -0
  62. package/dist/buddy/titles.js +89 -0
  63. package/dist/buddy/types.d.ts +92 -0
  64. package/dist/buddy/types.js +21 -0
  65. package/dist/commands/actions.d.ts +2 -0
  66. package/dist/commands/actions.js +141 -0
  67. package/dist/commands/admin.d.ts +2 -0
  68. package/dist/commands/admin.js +202 -0
  69. package/dist/commands/registry.d.ts +25 -0
  70. package/dist/commands/registry.js +31 -0
  71. package/dist/commands/social.d.ts +2 -0
  72. package/dist/commands/social.js +92 -0
  73. package/dist/dialogue/engine.d.ts +7 -0
  74. package/dist/dialogue/engine.js +68 -0
  75. package/dist/dialogue/lines.d.ts +26 -0
  76. package/dist/dialogue/lines.js +294 -0
  77. package/dist/events/engine.d.ts +20 -0
  78. package/dist/events/engine.js +51 -0
  79. package/dist/events/events.d.ts +13 -0
  80. package/dist/events/events.js +149 -0
  81. package/dist/index.d.ts +10 -0
  82. package/dist/index.js +1665 -0
  83. package/dist/inventory/finding.d.ts +11 -0
  84. package/dist/inventory/finding.js +99 -0
  85. package/dist/inventory/items.d.ts +31 -0
  86. package/dist/inventory/items.js +63 -0
  87. package/dist/minigames/copycat.d.ts +2 -0
  88. package/dist/minigames/copycat.js +153 -0
  89. package/dist/minigames/fetch.d.ts +2 -0
  90. package/dist/minigames/fetch.js +179 -0
  91. package/dist/minigames/moodmatch.d.ts +2 -0
  92. package/dist/minigames/moodmatch.js +144 -0
  93. package/dist/minigames/quickpaws.d.ts +2 -0
  94. package/dist/minigames/quickpaws.js +142 -0
  95. package/dist/minigames/registry.d.ts +5 -0
  96. package/dist/minigames/registry.js +16 -0
  97. package/dist/minigames/rpsplus.d.ts +2 -0
  98. package/dist/minigames/rpsplus.js +168 -0
  99. package/dist/minigames/treasurehunt.d.ts +2 -0
  100. package/dist/minigames/treasurehunt.js +146 -0
  101. package/dist/minigames/types.d.ts +30 -0
  102. package/dist/minigames/types.js +69 -0
  103. package/dist/rendering/commandPalette.d.ts +16 -0
  104. package/dist/rendering/commandPalette.js +77 -0
  105. package/dist/rendering/display.d.ts +9 -0
  106. package/dist/rendering/display.js +231 -0
  107. package/dist/rendering/inventoryUI.d.ts +14 -0
  108. package/dist/rendering/inventoryUI.js +99 -0
  109. package/dist/rendering/items.d.ts +7 -0
  110. package/dist/rendering/items.js +34 -0
  111. package/dist/rendering/listUtils.d.ts +3 -0
  112. package/dist/rendering/listUtils.js +24 -0
  113. package/dist/rendering/minigameUI.d.ts +11 -0
  114. package/dist/rendering/minigameUI.js +37 -0
  115. package/dist/rendering/overlayUI.d.ts +24 -0
  116. package/dist/rendering/overlayUI.js +184 -0
  117. package/dist/rendering/scene.d.ts +8 -0
  118. package/dist/rendering/scene.js +87 -0
  119. package/dist/rendering/screen.d.ts +43 -0
  120. package/dist/rendering/screen.js +97 -0
  121. package/dist/sound/sound.d.ts +11 -0
  122. package/dist/sound/sound.js +55 -0
  123. package/dist/state/save.d.ts +5 -0
  124. package/dist/state/save.js +100 -0
  125. package/dist/state/settings.d.ts +7 -0
  126. package/dist/state/settings.js +81 -0
  127. package/dist/story/mainStory.d.ts +4 -0
  128. package/dist/story/mainStory.js +3111 -0
  129. package/dist/story/npcs.d.ts +17 -0
  130. package/dist/story/npcs.js +137 -0
  131. package/dist/story/progress.d.ts +26 -0
  132. package/dist/story/progress.js +168 -0
  133. package/dist/story/seasonal.d.ts +6 -0
  134. package/dist/story/seasonal.js +96 -0
  135. package/dist/story/shop.d.ts +7 -0
  136. package/dist/story/shop.js +26 -0
  137. package/dist/story/sideQuests.d.ts +4 -0
  138. package/dist/story/sideQuests.js +151 -0
  139. package/dist/story/types.d.ts +61 -0
  140. package/dist/story/types.js +36 -0
  141. package/dist/updates.d.ts +23 -0
  142. package/dist/updates.js +142 -0
  143. package/package.json +53 -0
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOverlay = createOverlay;
4
+ exports.openOverlay = openOverlay;
5
+ exports.scrollOverlay = scrollOverlay;
6
+ exports.renderTitlesOverlay = renderTitlesOverlay;
7
+ exports.renderNotificationsOverlay = renderNotificationsOverlay;
8
+ exports.getSettingEntries = getSettingEntries;
9
+ exports.renderSettingsOverlay = renderSettingsOverlay;
10
+ exports.renderGearOverlay = renderGearOverlay;
11
+ exports.renderCombatInfoOverlay = renderCombatInfoOverlay;
12
+ exports.renderShopOverlay = renderShopOverlay;
13
+ const titles_1 = require("../buddy/titles");
14
+ const combat_1 = require("../adventure/combat/combat");
15
+ const items_1 = require("../inventory/items");
16
+ const shop_1 = require("../story/shop");
17
+ const listUtils_1 = require("./listUtils");
18
+ const screen_1 = require("./screen");
19
+ function createOverlay() {
20
+ return { type: null, scrollIndex: 0 };
21
+ }
22
+ function openOverlay(type) {
23
+ return { type, scrollIndex: 0 };
24
+ }
25
+ function scrollOverlay(overlay, delta, maxItems) {
26
+ return { ...overlay, scrollIndex: (0, listUtils_1.moveListIndex)(overlay.scrollIndex, delta, maxItems, false) };
27
+ }
28
+ function formatTimeAgo(timestamp) {
29
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
30
+ if (seconds < 60)
31
+ return "just now";
32
+ const minutes = Math.floor(seconds / 60);
33
+ if (minutes < 60)
34
+ return `${minutes}m ago`;
35
+ const hours = Math.floor(minutes / 60);
36
+ if (hours < 24)
37
+ return `${hours}h ago`;
38
+ const days = Math.floor(hours / 24);
39
+ return `${days}d ago`;
40
+ }
41
+ function renderTitlesOverlay(state, overlay) {
42
+ const lines = [];
43
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
44
+ lines.push(` ${screen_1.ansi.bold}Titles${screen_1.ansi.reset} ${screen_1.ansi.dim}(${state.titles.length}/${titles_1.TITLE_RULES.length} earned) Esc to close${screen_1.ansi.reset}`);
45
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
46
+ for (let i = 0; i < titles_1.TITLE_RULES.length; i++) {
47
+ const rule = titles_1.TITLE_RULES[i];
48
+ const earned = state.titles.includes(rule.id);
49
+ const active = state.activeTitle === rule.id;
50
+ const selected = i === overlay.scrollIndex;
51
+ const pointer = selected ? `${screen_1.ansi.colors.cyan}▸${screen_1.ansi.reset}` : " ";
52
+ if (earned) {
53
+ const tag = active ? ` ${screen_1.ansi.colors.cyan}[equipped]${screen_1.ansi.reset}` : "";
54
+ lines.push(` ${pointer} ${screen_1.ansi.colors.green}✓${screen_1.ansi.reset} ${screen_1.ansi.bold}${rule.label}${screen_1.ansi.reset}${tag}`);
55
+ }
56
+ else {
57
+ lines.push(` ${pointer} ${screen_1.ansi.dim}✗ ??? — ${rule.hint}${screen_1.ansi.reset}`);
58
+ }
59
+ }
60
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
61
+ lines.push(` ${screen_1.ansi.dim}Enter to equip selected title${screen_1.ansi.reset}`);
62
+ return lines;
63
+ }
64
+ function renderNotificationsOverlay(notifications, overlay) {
65
+ const lines = [];
66
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
67
+ lines.push(` ${screen_1.ansi.bold}Notifications${screen_1.ansi.reset} ${screen_1.ansi.dim}(${notifications.length}) Esc to close${screen_1.ansi.reset}`);
68
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
69
+ if (notifications.length === 0) {
70
+ lines.push(` ${screen_1.ansi.dim} No notifications.${screen_1.ansi.reset}`);
71
+ }
72
+ else {
73
+ // Show newest first
74
+ const reversed = [...notifications].reverse();
75
+ for (let i = 0; i < reversed.length; i++) {
76
+ const notif = reversed[i];
77
+ const selected = i === overlay.scrollIndex;
78
+ const pointer = selected ? `${screen_1.ansi.colors.cyan}▸${screen_1.ansi.reset}` : " ";
79
+ const timeAgo = formatTimeAgo(notif.timestamp);
80
+ lines.push(` ${pointer} ${notif.message}`);
81
+ lines.push(` ${screen_1.ansi.dim}${timeAgo}${screen_1.ansi.reset}`);
82
+ }
83
+ }
84
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
85
+ lines.push(` ${screen_1.ansi.dim}Enter to clear all${screen_1.ansi.reset}`);
86
+ return lines;
87
+ }
88
+ const SETTING_ENTRIES = [
89
+ { key: "sound", name: "Sound", description: "Terminal bell on events" },
90
+ ];
91
+ function getSettingEntries() {
92
+ return SETTING_ENTRIES;
93
+ }
94
+ function renderSettingsOverlay(settings, overlay) {
95
+ const lines = [];
96
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
97
+ lines.push(` ${screen_1.ansi.bold}Settings${screen_1.ansi.reset} ${screen_1.ansi.dim}Enter to toggle, Esc to close${screen_1.ansi.reset}`);
98
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
99
+ for (let i = 0; i < SETTING_ENTRIES.length; i++) {
100
+ const entry = SETTING_ENTRIES[i];
101
+ const value = settings[entry.key];
102
+ const selected = i === overlay.scrollIndex;
103
+ const pointer = selected ? `${screen_1.ansi.colors.cyan}▸${screen_1.ansi.reset}` : " ";
104
+ const toggle = value
105
+ ? `${screen_1.ansi.colors.green}[ON] ${screen_1.ansi.reset}`
106
+ : `${screen_1.ansi.colors.red}[OFF]${screen_1.ansi.reset}`;
107
+ lines.push(` ${pointer} ${toggle} ${screen_1.ansi.bold}${entry.name}${screen_1.ansi.reset} ${screen_1.ansi.dim}${entry.description}${screen_1.ansi.reset}`);
108
+ }
109
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
110
+ return lines;
111
+ }
112
+ function renderGearOverlay(state) {
113
+ const s = state.adventureStats;
114
+ const weapon = s.equippedWeapon ? (0, items_1.getItem)(s.equippedWeapon) : null;
115
+ const armor = s.equippedArmor ? (0, items_1.getItem)(s.equippedArmor) : null;
116
+ const helmet = s.equippedHelmet ? (0, items_1.getItem)(s.equippedHelmet) : null;
117
+ const boots = s.equippedBoots ? (0, items_1.getItem)(s.equippedBoots) : null;
118
+ const accessory = s.equippedAccessory ? (0, items_1.getItem)(s.equippedAccessory) : null;
119
+ const lines = [];
120
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
121
+ lines.push(` ${screen_1.ansi.bold}Equipped Gear${screen_1.ansi.reset} ${screen_1.ansi.dim}Esc to close${screen_1.ansi.reset}`);
122
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
123
+ lines.push(` ⚔ Weapon: ${weapon ? `${weapon.name} (+${weapon.combatATK} ATK)${weapon.gearElement ? ` [${weapon.gearElement}]` : ""}` : `${screen_1.ansi.dim}None${screen_1.ansi.reset}`}`);
124
+ if (weapon?.onHitEffect)
125
+ lines.push(` ${screen_1.ansi.dim}${Math.round((weapon.onHitChance ?? 0) * 100)}% ${weapon.onHitEffect} on hit${screen_1.ansi.reset}`);
126
+ lines.push(` 🛡 Armor: ${armor ? `${armor.name} (+${armor.combatDEF} DEF)` : `${screen_1.ansi.dim}None${screen_1.ansi.reset}`}`);
127
+ if (armor?.combatHP)
128
+ lines.push(` ${screen_1.ansi.dim}+${armor.combatHP} HP${screen_1.ansi.reset}`);
129
+ lines.push(` 🪖 Helmet: ${helmet ? `${helmet.name} (${helmet.description})` : `${screen_1.ansi.dim}None${screen_1.ansi.reset}`}`);
130
+ lines.push(` 👢 Boots: ${boots ? `${boots.name} (${boots.description})` : `${screen_1.ansi.dim}None${screen_1.ansi.reset}`}`);
131
+ lines.push(` 💍 Acc: ${accessory ? `${accessory.name} (${accessory.description})` : `${screen_1.ansi.dim}None${screen_1.ansi.reset}`}`);
132
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
133
+ lines.push(` ${screen_1.ansi.dim}Equip gear from /inventory${screen_1.ansi.reset}`);
134
+ return lines;
135
+ }
136
+ function renderCombatInfoOverlay(state) {
137
+ const stats = (0, combat_1.deriveCombatStats)(state);
138
+ const { equippedWeapon, equippedArmor } = state.adventureStats;
139
+ const weapon = equippedWeapon ? (0, items_1.getItem)(equippedWeapon) : null;
140
+ const armor = equippedArmor ? (0, items_1.getItem)(equippedArmor) : null;
141
+ const lines = [];
142
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
143
+ lines.push(` ${screen_1.ansi.bold}Combat Stats${screen_1.ansi.reset} ${screen_1.ansi.dim}Esc to close${screen_1.ansi.reset}`);
144
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
145
+ lines.push(` HP: ${screen_1.ansi.colors.green}${stats.maxHp}${screen_1.ansi.reset}`);
146
+ lines.push(` ATK: ${screen_1.ansi.colors.red}${stats.atk}${screen_1.ansi.reset}${weapon ? ` (base + ${weapon.combatATK} from ${weapon.name})` : ""}`);
147
+ lines.push(` DEF: ${screen_1.ansi.colors.blue}${stats.def}${screen_1.ansi.reset}${armor ? ` (base + ${armor.combatDEF} from ${armor.name})` : ""}`);
148
+ lines.push(` SPD: ${stats.spd}`);
149
+ lines.push(` Dodge: ${Math.round(stats.dodgeChance * 100)}%`);
150
+ lines.push("");
151
+ lines.push(` ${screen_1.ansi.dim}Adventures: ${state.adventureStats.adventuresCompleted} | Battles won: ${state.adventureStats.battlesWon}${screen_1.ansi.reset}`);
152
+ lines.push(` ${screen_1.ansi.dim}Bosses defeated: ${state.adventureStats.bossesDefeated} | Perfect runs: ${state.adventureStats.perfectAdventures}${screen_1.ansi.reset}`);
153
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
154
+ return lines;
155
+ }
156
+ function renderShopOverlay(state, overlay) {
157
+ const lines = [];
158
+ const gold = state.questProgress.gold;
159
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
160
+ lines.push(` ${screen_1.ansi.bold}Pip's Shop${screen_1.ansi.reset} ${screen_1.ansi.colors.yellow}Gold: ${gold}${screen_1.ansi.reset} ${screen_1.ansi.dim}Esc to close${screen_1.ansi.reset}`);
161
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
162
+ for (let i = 0; i < shop_1.SHOP_ITEMS.length; i++) {
163
+ const shopItem = shop_1.SHOP_ITEMS[i];
164
+ const item = (0, items_1.getItem)(shopItem.itemId);
165
+ if (!item)
166
+ continue;
167
+ const selected = i === overlay.scrollIndex;
168
+ const pointer = selected ? `${screen_1.ansi.colors.cyan}▸${screen_1.ansi.reset}` : " ";
169
+ const canAfford = gold >= shopItem.price;
170
+ const priceColor = canAfford ? screen_1.ansi.colors.yellow : screen_1.ansi.colors.red;
171
+ const rarityColor = items_1.ITEM_RARITY_COLORS[item.rarity] ?? "";
172
+ if (selected) {
173
+ lines.push(` ${pointer} ${rarityColor}${item.name}${screen_1.ansi.reset} ${priceColor}${shopItem.price}g${screen_1.ansi.reset}`);
174
+ lines.push(` ${screen_1.ansi.dim}${item.description}${screen_1.ansi.reset}`);
175
+ }
176
+ else {
177
+ lines.push(` ${pointer} ${screen_1.ansi.dim}${item.name} ${shopItem.price}g${screen_1.ansi.reset}`);
178
+ }
179
+ }
180
+ lines.push(` ${screen_1.ansi.dim}${"─".repeat(48)}${screen_1.ansi.reset}`);
181
+ lines.push(` ${screen_1.ansi.dim}Enter to buy${screen_1.ansi.reset}`);
182
+ return lines;
183
+ }
184
+ //# sourceMappingURL=overlayUI.js.map
@@ -0,0 +1,8 @@
1
+ import { SceneItem } from "./items";
2
+ /**
3
+ * Compose a scene by placing buddy art and an optional item
4
+ * into a fixed-width character buffer.
5
+ * Returns an array of lines ready to render.
6
+ */
7
+ export declare function composeScene(buddyArt: string[], item: SceneItem | null, groundLine?: boolean): string[];
8
+ //# sourceMappingURL=scene.d.ts.map
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.composeScene = composeScene;
4
+ const SCENE_WIDTH = 50;
5
+ /**
6
+ * Compose a scene by placing buddy art and an optional item
7
+ * into a fixed-width character buffer.
8
+ * Returns an array of lines ready to render.
9
+ */
10
+ function composeScene(buddyArt, item, groundLine = true) {
11
+ if (!item) {
12
+ // No item — just center the buddy with optional ground line
13
+ const lines = buddyArt.map((line) => centerPad(line, SCENE_WIDTH));
14
+ if (groundLine) {
15
+ lines.push(centerPad("─".repeat(Math.max(...buddyArt.map(visibleLength)) + 4), SCENE_WIDTH));
16
+ }
17
+ return lines;
18
+ }
19
+ if (item.position === "below") {
20
+ return composeBuddyAboveItem(buddyArt, item);
21
+ }
22
+ // "right" or "left" — side by side
23
+ return composeSideBySide(buddyArt, item);
24
+ }
25
+ function composeBuddyAboveItem(buddyArt, item) {
26
+ const lines = [];
27
+ // Center buddy above the item
28
+ for (const line of buddyArt) {
29
+ lines.push(centerPad(line, SCENE_WIDTH));
30
+ }
31
+ // Item below
32
+ for (const line of item.art) {
33
+ lines.push(centerPad(line, SCENE_WIDTH));
34
+ }
35
+ return lines;
36
+ }
37
+ function composeSideBySide(buddyArt, item) {
38
+ const itemArt = item.art;
39
+ const gap = " ";
40
+ // Determine heights and vertical alignment (bottom-align)
41
+ const maxHeight = Math.max(buddyArt.length, itemArt.length);
42
+ // Pad shorter array from the top
43
+ const paddedBuddy = topPad(buddyArt, maxHeight);
44
+ const paddedItem = topPad(itemArt, maxHeight);
45
+ const buddyWidth = Math.max(...buddyArt.map(visibleLength));
46
+ const lines = [];
47
+ for (let i = 0; i < maxHeight; i++) {
48
+ const bLine = rightPad(paddedBuddy[i], buddyWidth);
49
+ const iLine = paddedItem[i];
50
+ if (item.position === "right") {
51
+ lines.push(centerPad(bLine + gap + iLine, SCENE_WIDTH));
52
+ }
53
+ else {
54
+ lines.push(centerPad(iLine + gap + bLine, SCENE_WIDTH));
55
+ }
56
+ }
57
+ // Ground line
58
+ const totalWidth = buddyWidth + gap.length + Math.max(...itemArt.map(visibleLength));
59
+ lines.push(centerPad("─".repeat(totalWidth + 2), SCENE_WIDTH));
60
+ return lines;
61
+ }
62
+ /** Pad array from top with empty strings to reach target length */
63
+ function topPad(arr, targetLen) {
64
+ const padding = targetLen - arr.length;
65
+ if (padding <= 0)
66
+ return arr;
67
+ return [...Array(padding).fill(""), ...arr];
68
+ }
69
+ /** Visible length of a string (excluding ANSI escape codes) */
70
+ function visibleLength(str) {
71
+ // Strip ANSI codes
72
+ return str.replace(/\x1b\[[0-9;]*m/g, "").length;
73
+ }
74
+ /** Right-pad a string with spaces to target visible width */
75
+ function rightPad(str, targetWidth) {
76
+ const pad = targetWidth - visibleLength(str);
77
+ return pad > 0 ? str + " ".repeat(pad) : str;
78
+ }
79
+ /** Center a string within target width */
80
+ function centerPad(str, targetWidth) {
81
+ const len = visibleLength(str);
82
+ if (len >= targetWidth)
83
+ return str;
84
+ const leftPad = Math.floor((targetWidth - len) / 2);
85
+ return " ".repeat(leftPad) + str;
86
+ }
87
+ //# sourceMappingURL=scene.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Full-screen terminal renderer.
3
+ * Owns the entire terminal — clears and redraws each frame.
4
+ * No scrolling, no message spam.
5
+ */
6
+ export declare const ansi: {
7
+ clearScreen: string;
8
+ hideCursor: string;
9
+ showCursor: string;
10
+ moveTo: (row: number, col: number) => string;
11
+ eraseDown: string;
12
+ bold: string;
13
+ dim: string;
14
+ reset: string;
15
+ colors: {
16
+ red: string;
17
+ green: string;
18
+ yellow: string;
19
+ blue: string;
20
+ magenta: string;
21
+ cyan: string;
22
+ white: string;
23
+ gray: string;
24
+ };
25
+ };
26
+ export declare class Screen {
27
+ private regions;
28
+ private footer;
29
+ private inputLine;
30
+ private width;
31
+ private height;
32
+ constructor();
33
+ /** Replace all content and redraw */
34
+ draw(content: string, footer?: string): void;
35
+ /** Update just the input line at the bottom without full redraw */
36
+ drawInputLine(input: string): void;
37
+ /** Draw an overlay (command palette) over the bottom portion of the screen */
38
+ drawOverlay(lines: string[], inputLine: string): void;
39
+ getWidth(): number;
40
+ getHeight(): number;
41
+ cleanup(): void;
42
+ }
43
+ //# sourceMappingURL=screen.d.ts.map
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Full-screen terminal renderer.
4
+ * Owns the entire terminal — clears and redraws each frame.
5
+ * No scrolling, no message spam.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.Screen = exports.ansi = void 0;
9
+ const ESC = "\x1b";
10
+ exports.ansi = {
11
+ clearScreen: `${ESC}[2J${ESC}[H`,
12
+ hideCursor: `${ESC}[?25l`,
13
+ showCursor: `${ESC}[?25h`,
14
+ moveTo: (row, col) => `${ESC}[${row};${col}H`,
15
+ eraseDown: `${ESC}[J`,
16
+ bold: `${ESC}[1m`,
17
+ dim: `${ESC}[2m`,
18
+ reset: `${ESC}[0m`,
19
+ colors: {
20
+ red: `${ESC}[31m`,
21
+ green: `${ESC}[32m`,
22
+ yellow: `${ESC}[33m`,
23
+ blue: `${ESC}[34m`,
24
+ magenta: `${ESC}[35m`,
25
+ cyan: `${ESC}[36m`,
26
+ white: `${ESC}[37m`,
27
+ gray: `${ESC}[90m`,
28
+ },
29
+ };
30
+ class Screen {
31
+ regions = [];
32
+ footer = "";
33
+ inputLine = "";
34
+ width;
35
+ height;
36
+ constructor() {
37
+ this.width = process.stdout.columns || 80;
38
+ this.height = process.stdout.rows || 24;
39
+ process.stdout.on("resize", () => {
40
+ this.width = process.stdout.columns || 80;
41
+ this.height = process.stdout.rows || 24;
42
+ });
43
+ }
44
+ /** Replace all content and redraw */
45
+ draw(content, footer) {
46
+ const out = process.stdout;
47
+ out.write(exports.ansi.hideCursor);
48
+ out.write(exports.ansi.clearScreen);
49
+ out.write(content);
50
+ if (footer) {
51
+ // Draw footer at bottom
52
+ out.write(exports.ansi.moveTo(this.height - 1, 1));
53
+ out.write(footer);
54
+ }
55
+ // Position cursor at input line
56
+ out.write(exports.ansi.moveTo(this.height, 1));
57
+ out.write(exports.ansi.eraseDown);
58
+ out.write(this.inputLine);
59
+ out.write(exports.ansi.showCursor);
60
+ }
61
+ /** Update just the input line at the bottom without full redraw */
62
+ drawInputLine(input) {
63
+ this.inputLine = input;
64
+ const out = process.stdout;
65
+ out.write(exports.ansi.moveTo(this.height, 1));
66
+ out.write("\x1b[2K"); // erase line
67
+ out.write(input);
68
+ }
69
+ /** Draw an overlay (command palette) over the bottom portion of the screen */
70
+ drawOverlay(lines, inputLine) {
71
+ const out = process.stdout;
72
+ const startRow = this.height - lines.length - 1;
73
+ // Draw overlay lines
74
+ for (let i = 0; i < lines.length; i++) {
75
+ out.write(exports.ansi.moveTo(startRow + i, 1));
76
+ out.write("\x1b[2K"); // erase line
77
+ out.write(lines[i]);
78
+ }
79
+ // Draw input line at bottom
80
+ out.write(exports.ansi.moveTo(this.height, 1));
81
+ out.write("\x1b[2K");
82
+ out.write(inputLine);
83
+ out.write(exports.ansi.showCursor);
84
+ }
85
+ getWidth() {
86
+ return this.width;
87
+ }
88
+ getHeight() {
89
+ return this.height;
90
+ }
91
+ cleanup() {
92
+ process.stdout.write(exports.ansi.showCursor);
93
+ process.stdout.write(exports.ansi.clearScreen);
94
+ }
95
+ }
96
+ exports.Screen = Screen;
97
+ //# sourceMappingURL=screen.js.map
@@ -0,0 +1,11 @@
1
+ export declare function soundLevelUp(): void;
2
+ export declare function soundTitleEarned(): void;
3
+ export declare function soundItemFound(): void;
4
+ export declare function soundDeath(): void;
5
+ export declare function soundRareRoll(): void;
6
+ export declare function soundGameEnd(): void;
7
+ export declare function soundBattleWin(): void;
8
+ export declare function soundBossEncounter(): void;
9
+ export declare function soundAdventureComplete(): void;
10
+ export declare function soundCombo(): void;
11
+ //# sourceMappingURL=sound.d.ts.map
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.soundLevelUp = soundLevelUp;
4
+ exports.soundTitleEarned = soundTitleEarned;
5
+ exports.soundItemFound = soundItemFound;
6
+ exports.soundDeath = soundDeath;
7
+ exports.soundRareRoll = soundRareRoll;
8
+ exports.soundGameEnd = soundGameEnd;
9
+ exports.soundBattleWin = soundBattleWin;
10
+ exports.soundBossEncounter = soundBossEncounter;
11
+ exports.soundAdventureComplete = soundAdventureComplete;
12
+ exports.soundCombo = soundCombo;
13
+ const settings_1 = require("../state/settings");
14
+ function bell() {
15
+ if ((0, settings_1.getSettings)().sound) {
16
+ process.stdout.write("\x07");
17
+ }
18
+ }
19
+ function doubleBell() {
20
+ if ((0, settings_1.getSettings)().sound) {
21
+ process.stdout.write("\x07");
22
+ setTimeout(() => process.stdout.write("\x07"), 200);
23
+ }
24
+ }
25
+ function soundLevelUp() {
26
+ bell();
27
+ }
28
+ function soundTitleEarned() {
29
+ bell();
30
+ }
31
+ function soundItemFound() {
32
+ bell();
33
+ }
34
+ function soundDeath() {
35
+ doubleBell();
36
+ }
37
+ function soundRareRoll() {
38
+ doubleBell();
39
+ }
40
+ function soundGameEnd() {
41
+ bell();
42
+ }
43
+ function soundBattleWin() {
44
+ bell();
45
+ }
46
+ function soundBossEncounter() {
47
+ doubleBell();
48
+ }
49
+ function soundAdventureComplete() {
50
+ doubleBell();
51
+ }
52
+ function soundCombo() {
53
+ bell();
54
+ }
55
+ //# sourceMappingURL=sound.js.map
@@ -0,0 +1,5 @@
1
+ import { BuddyState } from "../buddy/types";
2
+ export declare function saveState(state: BuddyState): void;
3
+ export declare function loadState(): BuddyState | null;
4
+ export declare function deleteState(): void;
5
+ //# sourceMappingURL=save.d.ts.map
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.saveState = saveState;
37
+ exports.loadState = loadState;
38
+ exports.deleteState = deleteState;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const os = __importStar(require("os"));
42
+ const types_1 = require("../story/types");
43
+ const STATE_DIR = path.join(os.homedir(), ".clibuddy");
44
+ const STATE_FILE = path.join(STATE_DIR, "state.json");
45
+ function ensureDir() {
46
+ if (!fs.existsSync(STATE_DIR)) {
47
+ fs.mkdirSync(STATE_DIR, { recursive: true });
48
+ }
49
+ }
50
+ function saveState(state) {
51
+ ensureDir();
52
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
53
+ }
54
+ function loadState() {
55
+ if (!fs.existsSync(STATE_FILE))
56
+ return null;
57
+ try {
58
+ const raw = fs.readFileSync(STATE_FILE, "utf-8");
59
+ const state = JSON.parse(raw);
60
+ // Backfill new fields for old save files
61
+ if (!state.titles)
62
+ state.titles = [];
63
+ if (!state.actionCounts)
64
+ state.actionCounts = { feeds: 0, plays: 0, sleeps: 0, heals: 0, revives: 0, pets: 0, minigamesWon: 0, itemsFound: 0, combosTriggered: 0 };
65
+ // Backfill new action count fields
66
+ if (state.actionCounts.pets === undefined)
67
+ state.actionCounts.pets = 0;
68
+ if (state.actionCounts.minigamesWon === undefined)
69
+ state.actionCounts.minigamesWon = 0;
70
+ if (state.actionCounts.itemsFound === undefined)
71
+ state.actionCounts.itemsFound = 0;
72
+ if (state.actionCounts.combosTriggered === undefined)
73
+ state.actionCounts.combosTriggered = 0;
74
+ if (!state.inventory)
75
+ state.inventory = [];
76
+ if (!state.notifications)
77
+ state.notifications = [];
78
+ // Migrate old string[] notifications to Notification[]
79
+ if (state.notifications.length > 0 && typeof state.notifications[0] === "string") {
80
+ state.notifications = state.notifications.map((msg) => ({ message: msg, timestamp: Date.now() }));
81
+ }
82
+ if (!state.adventureStats)
83
+ state.adventureStats = {
84
+ adventuresCompleted: 0, battlesWon: 0, battlesLost: 0,
85
+ battlesFled: 0, bossesDefeated: 0, totalDamageDealt: 0, perfectAdventures: 0,
86
+ };
87
+ if (!state.questProgress)
88
+ state.questProgress = (0, types_1.createDefaultQuestProgress)();
89
+ return state;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ function deleteState() {
96
+ if (fs.existsSync(STATE_FILE)) {
97
+ fs.unlinkSync(STATE_FILE);
98
+ }
99
+ }
100
+ //# sourceMappingURL=save.js.map
@@ -0,0 +1,7 @@
1
+ export interface Settings {
2
+ sound: boolean;
3
+ }
4
+ export declare function loadSettings(): Settings;
5
+ export declare function saveSettings(settings: Settings): void;
6
+ export declare function getSettings(): Settings;
7
+ //# sourceMappingURL=settings.d.ts.map