roguelike-cli 1.3.2 → 1.3.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.
@@ -33,42 +33,39 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ACHIEVEMENTS = void 0;
36
+ exports.ACHIEVEMENT_THRESHOLDS = void 0;
37
37
  exports.xpForLevel = xpForLevel;
38
38
  exports.levelFromXP = levelFromXP;
39
39
  exports.xpToNextLevel = xpToNextLevel;
40
40
  exports.readProfile = readProfile;
41
41
  exports.saveProfile = saveProfile;
42
- exports.addXP = addXP;
43
42
  exports.completeTask = completeTask;
43
+ exports.getAchievementInfo = getAchievementInfo;
44
44
  exports.formatStats = formatStats;
45
45
  exports.formatAchievements = formatAchievements;
46
+ exports.formatInventoryDisplay = formatInventoryDisplay;
46
47
  exports.addToUndoHistory = addToUndoHistory;
47
48
  exports.getLastUndo = getLastUndo;
48
49
  exports.performUndo = performUndo;
49
50
  const fs = __importStar(require("fs"));
50
51
  const path = __importStar(require("path"));
51
52
  const os = __importStar(require("os"));
53
+ const dictionaries_1 = require("../data/dictionaries");
54
+ const loot_1 = require("../data/loot");
52
55
  const PROFILE_FILE = path.join(os.homedir(), '.rlc', 'profile.json');
53
- // Achievement definitions
54
- exports.ACHIEVEMENTS = [
55
- { id: 'first_blood', name: 'First Blood', description: 'Complete your first task' },
56
- { id: 'ten_tasks', name: 'Getting Started', description: 'Complete 10 tasks' },
57
- { id: 'fifty_tasks', name: 'Productive', description: 'Complete 50 tasks' },
58
- { id: 'hundred_tasks', name: 'Centurion', description: 'Complete 100 tasks' },
59
- { id: 'deep_nesting', name: 'Deep Diver', description: 'Complete a task at depth 5+' },
60
- { id: 'boss_slayer', name: 'Boss Slayer', description: 'Complete a boss task' },
61
- { id: 'five_bosses', name: 'Boss Hunter', description: 'Defeat 5 bosses' },
62
- { id: 'speedrunner', name: 'Speedrunner', description: 'Complete a task on the same day it was created' },
63
- { id: 'streak_3', name: 'On a Roll', description: '3 day completion streak' },
64
- { id: 'streak_7', name: 'Streak Master', description: '7 day completion streak' },
65
- { id: 'streak_30', name: 'Unstoppable', description: '30 day completion streak' },
66
- { id: 'level_5', name: 'Adventurer', description: 'Reach level 5' },
67
- { id: 'level_10', name: 'Veteran', description: 'Reach level 10' },
68
- { id: 'level_25', name: 'Legend', description: 'Reach level 25' },
69
- { id: 'xp_1000', name: 'XP Collector', description: 'Earn 1000 XP' },
70
- { id: 'xp_10000', name: 'XP Hoarder', description: 'Earn 10000 XP' },
71
- ];
56
+ // Achievement thresholds (infinite, level-based)
57
+ exports.ACHIEVEMENT_THRESHOLDS = {
58
+ tasks: [1, 10, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
59
+ bosses: [1, 5, 10, 25, 50, 100, 250, 500],
60
+ streaks: [3, 7, 14, 30, 60, 90, 180, 365],
61
+ depths: [3, 5, 7, 10, 15, 20],
62
+ levels: [5, 10, 25, 50, 100, 150, 200],
63
+ xp: [1000, 5000, 10000, 50000, 100000, 500000, 1000000],
64
+ };
65
+ // Map threshold to achievement ID
66
+ function getAchievementId(type, threshold) {
67
+ return `${type}_${threshold}`;
68
+ }
72
69
  // XP required for each level (cumulative)
73
70
  function xpForLevel(level) {
74
71
  return Math.floor(100 * Math.pow(1.5, level - 1));
@@ -106,6 +103,10 @@ function readProfile() {
106
103
  return {
107
104
  ...createDefaultProfile(),
108
105
  ...profile,
106
+ stats: {
107
+ ...createDefaultProfile().stats,
108
+ ...profile.stats,
109
+ },
109
110
  };
110
111
  }
111
112
  catch {
@@ -130,36 +131,41 @@ function createDefaultProfile() {
130
131
  currentStreak: 0,
131
132
  longestStreak: 0,
132
133
  achievements: [],
134
+ inventory: [],
133
135
  undoHistory: [],
134
136
  stats: {
135
137
  completedByDay: {},
136
138
  createdAt: new Date().toISOString(),
139
+ deepestDepth: 0,
140
+ speedruns: 0,
141
+ nightOwlTasks: 0,
142
+ earlyBirdTasks: 0,
137
143
  },
138
144
  };
139
145
  }
140
- function addXP(amount) {
141
- const profile = readProfile();
142
- const oldLevel = profile.level;
143
- profile.totalXP += amount;
144
- profile.level = levelFromXP(profile.totalXP);
145
- saveProfile(profile);
146
- return {
147
- newXP: profile.totalXP,
148
- levelUp: profile.level > oldLevel,
149
- newLevel: profile.level,
150
- };
151
- }
152
- function completeTask(xp, isBoss, depth, createdAt) {
146
+ function completeTask(xp, isBoss, depth, createdAt, rulesPreset) {
153
147
  const profile = readProfile();
154
148
  const oldLevel = profile.level;
155
149
  const today = new Date().toISOString().split('T')[0];
156
150
  const createdDate = createdAt.split('T')[0];
151
+ const hour = new Date().getHours();
157
152
  // Add XP
158
153
  profile.totalXP += xp;
159
154
  profile.tasksCompleted += 1;
160
155
  if (isBoss) {
161
156
  profile.bossesDefeated += 1;
162
157
  }
158
+ // Track special completions
159
+ if (createdDate === today) {
160
+ profile.stats.speedruns = (profile.stats.speedruns || 0) + 1;
161
+ }
162
+ if (hour >= 0 && hour < 6) {
163
+ profile.stats.earlyBirdTasks = (profile.stats.earlyBirdTasks || 0) + 1;
164
+ }
165
+ if (hour >= 0 && hour < 5) {
166
+ profile.stats.nightOwlTasks = (profile.stats.nightOwlTasks || 0) + 1;
167
+ }
168
+ profile.stats.deepestDepth = Math.max(profile.stats.deepestDepth || 0, depth);
163
169
  // Update streak
164
170
  if (profile.lastCompletionDate) {
165
171
  const lastDate = new Date(profile.lastCompletionDate);
@@ -171,7 +177,6 @@ function completeTask(xp, isBoss, depth, createdAt) {
171
177
  else if (diffDays > 1) {
172
178
  profile.currentStreak = 1;
173
179
  }
174
- // Same day - streak continues but doesn't increment
175
180
  }
176
181
  else {
177
182
  profile.currentStreak = 1;
@@ -182,105 +187,228 @@ function completeTask(xp, isBoss, depth, createdAt) {
182
187
  profile.stats.completedByDay[today] = (profile.stats.completedByDay[today] || 0) + 1;
183
188
  // Update level
184
189
  profile.level = levelFromXP(profile.totalXP);
190
+ const levelUp = profile.level > oldLevel;
185
191
  // Check for new achievements
186
192
  const newAchievements = [];
187
193
  const checkAchievement = (id) => {
188
194
  if (!profile.achievements.includes(id)) {
189
195
  profile.achievements.push(id);
190
- const achievement = exports.ACHIEVEMENTS.find(a => a.id === id);
191
- if (achievement)
192
- newAchievements.push(achievement);
196
+ newAchievements.push(id);
193
197
  }
194
198
  };
195
- // Task count achievements
196
- if (profile.tasksCompleted >= 1)
197
- checkAchievement('first_blood');
198
- if (profile.tasksCompleted >= 10)
199
- checkAchievement('ten_tasks');
200
- if (profile.tasksCompleted >= 50)
201
- checkAchievement('fifty_tasks');
202
- if (profile.tasksCompleted >= 100)
203
- checkAchievement('hundred_tasks');
204
- // Boss achievements
205
- if (isBoss)
206
- checkAchievement('boss_slayer');
207
- if (profile.bossesDefeated >= 5)
208
- checkAchievement('five_bosses');
209
- // Depth achievement
210
- if (depth >= 5)
211
- checkAchievement('deep_nesting');
212
- // Speedrunner achievement
213
- if (createdDate === today)
214
- checkAchievement('speedrunner');
215
- // Streak achievements
216
- if (profile.currentStreak >= 3)
217
- checkAchievement('streak_3');
218
- if (profile.currentStreak >= 7)
219
- checkAchievement('streak_7');
220
- if (profile.currentStreak >= 30)
221
- checkAchievement('streak_30');
222
- // Level achievements
223
- if (profile.level >= 5)
224
- checkAchievement('level_5');
225
- if (profile.level >= 10)
226
- checkAchievement('level_10');
227
- if (profile.level >= 25)
228
- checkAchievement('level_25');
229
- // XP achievements
230
- if (profile.totalXP >= 1000)
231
- checkAchievement('xp_1000');
232
- if (profile.totalXP >= 10000)
233
- checkAchievement('xp_10000');
199
+ // Task count achievements (infinite)
200
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.tasks) {
201
+ if (profile.tasksCompleted >= threshold) {
202
+ checkAchievement(getAchievementId('tasks', threshold));
203
+ }
204
+ }
205
+ // Boss achievements (infinite)
206
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.bosses) {
207
+ if (profile.bossesDefeated >= threshold) {
208
+ checkAchievement(getAchievementId('bosses', threshold));
209
+ }
210
+ }
211
+ // Depth achievements (infinite)
212
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.depths) {
213
+ if (profile.stats.deepestDepth >= threshold) {
214
+ checkAchievement(getAchievementId('depth', threshold));
215
+ }
216
+ }
217
+ // Streak achievements (infinite)
218
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.streaks) {
219
+ if (profile.currentStreak >= threshold) {
220
+ checkAchievement(getAchievementId('streak', threshold));
221
+ }
222
+ }
223
+ // Level achievements (infinite)
224
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.levels) {
225
+ if (profile.level >= threshold) {
226
+ checkAchievement(getAchievementId('level', threshold));
227
+ }
228
+ }
229
+ // XP achievements (infinite)
230
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.xp) {
231
+ if (profile.totalXP >= threshold) {
232
+ checkAchievement(getAchievementId('xp', threshold));
233
+ }
234
+ }
235
+ // Special achievements
236
+ if (createdDate === today) {
237
+ checkAchievement('speedrun');
238
+ }
239
+ if (hour >= 0 && hour < 5) {
240
+ checkAchievement('nightowl');
241
+ }
242
+ if (hour >= 5 && hour < 7) {
243
+ checkAchievement('earlybird');
244
+ }
245
+ // Roll for loot
246
+ let lootDropped;
247
+ // Roll from task completion
248
+ const taskLoot = (0, loot_1.rollForLoot)(profile.level, isBoss ? 'boss' : 'task', rulesPreset);
249
+ if (taskLoot.dropped && taskLoot.item) {
250
+ lootDropped = taskLoot.item;
251
+ profile.inventory = profile.inventory || [];
252
+ profile.inventory.push(taskLoot.item);
253
+ }
254
+ // Additional roll on level up
255
+ if (levelUp) {
256
+ const levelLoot = (0, loot_1.rollForLoot)(profile.level, 'levelup', rulesPreset);
257
+ if (levelLoot.dropped && levelLoot.item) {
258
+ if (!lootDropped) {
259
+ lootDropped = levelLoot.item;
260
+ }
261
+ profile.inventory.push(levelLoot.item);
262
+ }
263
+ }
264
+ // Additional roll on new achievements
265
+ if (newAchievements.length > 0) {
266
+ const achievementLoot = (0, loot_1.rollForLoot)(profile.level, 'achievement', rulesPreset);
267
+ if (achievementLoot.dropped && achievementLoot.item) {
268
+ if (!lootDropped) {
269
+ lootDropped = achievementLoot.item;
270
+ }
271
+ profile.inventory.push(achievementLoot.item);
272
+ }
273
+ }
234
274
  saveProfile(profile);
235
275
  return {
236
276
  xpGained: xp,
237
- levelUp: profile.level > oldLevel,
277
+ levelUp,
278
+ oldLevel,
238
279
  newLevel: profile.level,
239
280
  newAchievements,
281
+ lootDropped,
282
+ };
283
+ }
284
+ // Get achievement display info from dictionary
285
+ function getAchievementInfo(achievementId, dict) {
286
+ // Parse achievement ID
287
+ const parts = achievementId.split('_');
288
+ const type = parts[0];
289
+ const threshold = parseInt(parts[1]);
290
+ // Map to dictionary keys
291
+ const keyMap = {
292
+ tasks: { 1: 'firstTask', 10: 'tasks10', 50: 'tasks50', 100: 'tasks100', 500: 'tasks500', 1000: 'tasks1000' },
293
+ bosses: { 1: 'boss1', 5: 'boss5', 10: 'boss10', 25: 'boss25' },
294
+ streak: { 3: 'streak3', 7: 'streak7', 14: 'streak14', 30: 'streak30' },
295
+ depth: { 3: 'depth3', 5: 'depth5', 10: 'depth10' },
296
+ };
297
+ // Special achievements
298
+ if (achievementId === 'speedrun') {
299
+ return dict.achievements.speedrun;
300
+ }
301
+ if (achievementId === 'nightowl') {
302
+ return dict.achievements.nightOwl;
303
+ }
304
+ if (achievementId === 'earlybird') {
305
+ return dict.achievements.earlyBird;
306
+ }
307
+ // Get from map
308
+ if (keyMap[type] && keyMap[type][threshold]) {
309
+ const key = keyMap[type][threshold];
310
+ return dict.achievements[key];
311
+ }
312
+ // Generate for achievements beyond dictionary
313
+ return {
314
+ name: `${type.charAt(0).toUpperCase() + type.slice(1)} ${threshold}`,
315
+ desc: `Reach ${threshold} ${type}`,
240
316
  };
241
317
  }
242
- function formatStats() {
318
+ function formatStats(rulesPreset) {
243
319
  const profile = readProfile();
320
+ const dict = (0, dictionaries_1.getDictionary)(rulesPreset);
244
321
  const nextLevel = xpToNextLevel(profile.totalXP);
245
322
  const lines = [
246
323
  '',
247
324
  '=== PLAYER STATS ===',
248
325
  '',
249
- `Level: ${profile.level}`,
250
- `XP: ${profile.totalXP} (${nextLevel.current}/${nextLevel.required} to next level)`,
326
+ `${dict.stats.level}: ${profile.level}`,
327
+ `${dict.stats.xp}: ${profile.totalXP} (${nextLevel.current}/${nextLevel.required} to next)`,
251
328
  `Progress: [${'#'.repeat(Math.floor(nextLevel.progress / 5))}${'.'.repeat(20 - Math.floor(nextLevel.progress / 5))}] ${nextLevel.progress}%`,
252
329
  '',
253
- `Tasks Completed: ${profile.tasksCompleted}`,
254
- `Bosses Defeated: ${profile.bossesDefeated}`,
255
- `Current Streak: ${profile.currentStreak} days`,
256
- `Longest Streak: ${profile.longestStreak} days`,
330
+ `${dict.stats.tasksCompleted}: ${profile.tasksCompleted}`,
331
+ `${dict.stats.bossesDefeated}: ${profile.bossesDefeated}`,
332
+ `${dict.stats.currentStreak}: ${profile.currentStreak} days`,
333
+ `${dict.stats.longestStreak}: ${profile.longestStreak} days`,
257
334
  '',
258
- `Achievements: ${profile.achievements.length}/${exports.ACHIEVEMENTS.length}`,
335
+ `Achievements: ${profile.achievements.length}`,
336
+ `${dict.stats.inventory}: ${(profile.inventory || []).length} items`,
337
+ `Inventory Value: ${(0, loot_1.calculateInventoryValue)(profile.inventory || [])}`,
259
338
  '',
260
339
  ];
261
340
  return lines.join('\n');
262
341
  }
263
- function formatAchievements() {
342
+ function formatAchievements(rulesPreset) {
264
343
  const profile = readProfile();
344
+ const dict = (0, dictionaries_1.getDictionary)(rulesPreset);
265
345
  const lines = [
266
346
  '',
267
347
  '=== ACHIEVEMENTS ===',
268
348
  '',
269
349
  ];
270
- for (const achievement of exports.ACHIEVEMENTS) {
271
- const unlocked = profile.achievements.includes(achievement.id);
272
- const status = unlocked ? '[x]' : '[ ]';
273
- lines.push(`${status} ${achievement.name}`);
274
- lines.push(` ${achievement.description}`);
350
+ // Show unlocked achievements
351
+ if (profile.achievements.length === 0) {
352
+ lines.push('No achievements yet. Complete tasks to unlock!');
353
+ }
354
+ else {
355
+ lines.push('Unlocked:');
356
+ for (const id of profile.achievements) {
357
+ const info = getAchievementInfo(id, dict);
358
+ if (info) {
359
+ lines.push(`[x] ${info.name}`);
360
+ lines.push(` ${info.desc}`);
361
+ }
362
+ }
275
363
  }
364
+ // Show next achievements to unlock
276
365
  lines.push('');
277
- lines.push(`Unlocked: ${profile.achievements.length}/${exports.ACHIEVEMENTS.length}`);
366
+ lines.push('Next to unlock:');
367
+ // Find next task achievement
368
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.tasks) {
369
+ const id = getAchievementId('tasks', threshold);
370
+ if (!profile.achievements.includes(id)) {
371
+ const info = getAchievementInfo(id, dict);
372
+ if (info) {
373
+ lines.push(`[ ] ${info.name} (${profile.tasksCompleted}/${threshold})`);
374
+ }
375
+ break;
376
+ }
377
+ }
378
+ // Find next boss achievement
379
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.bosses) {
380
+ const id = getAchievementId('bosses', threshold);
381
+ if (!profile.achievements.includes(id)) {
382
+ const info = getAchievementInfo(id, dict);
383
+ if (info) {
384
+ lines.push(`[ ] ${info.name} (${profile.bossesDefeated}/${threshold})`);
385
+ }
386
+ break;
387
+ }
388
+ }
389
+ // Find next streak achievement
390
+ for (const threshold of exports.ACHIEVEMENT_THRESHOLDS.streaks) {
391
+ const id = getAchievementId('streak', threshold);
392
+ if (!profile.achievements.includes(id)) {
393
+ const info = getAchievementInfo(id, dict);
394
+ if (info) {
395
+ lines.push(`[ ] ${info.name} (${profile.currentStreak}/${threshold})`);
396
+ }
397
+ break;
398
+ }
399
+ }
400
+ lines.push('');
401
+ lines.push(`Total unlocked: ${profile.achievements.length}`);
278
402
  lines.push('');
279
403
  return lines.join('\n');
280
404
  }
405
+ function formatInventoryDisplay(rulesPreset) {
406
+ const profile = readProfile();
407
+ const dict = (0, dictionaries_1.getDictionary)(rulesPreset);
408
+ return (0, loot_1.formatInventory)(profile.inventory || [], dict);
409
+ }
281
410
  function addToUndoHistory(entry) {
282
411
  const profile = readProfile();
283
- // Keep only last 10 undo entries
284
412
  profile.undoHistory = profile.undoHistory || [];
285
413
  profile.undoHistory.unshift(entry);
286
414
  if (profile.undoHistory.length > 10) {
@@ -301,15 +429,12 @@ function performUndo() {
301
429
  return { success: false, entry: null, message: 'Nothing to undo.' };
302
430
  }
303
431
  const entry = profile.undoHistory.shift();
304
- // Subtract XP
305
432
  profile.totalXP = Math.max(0, profile.totalXP - entry.xpLost);
306
433
  profile.tasksCompleted = Math.max(0, profile.tasksCompleted - 1);
307
434
  if (entry.wasBoss) {
308
435
  profile.bossesDefeated = Math.max(0, profile.bossesDefeated - 1);
309
436
  }
310
- // Update level
311
437
  profile.level = levelFromXP(profile.totalXP);
312
- // Update daily stats
313
438
  const today = new Date().toISOString().split('T')[0];
314
439
  if (profile.stats.completedByDay[today]) {
315
440
  profile.stats.completedByDay[today] = Math.max(0, profile.stats.completedByDay[today] - 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roguelike-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "AI-powered interactive terminal for creating schemas and todo lists",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -110,15 +110,15 @@ export async function initCommand(existingRl?: readline.Interface): Promise<void
110
110
  const apiKey = apiKeyInput.trim() || existingApiKey;
111
111
 
112
112
  if (!apiKey) {
113
- console.log('Warning: API key not set. You can set it later with: config -k <key>');
113
+ console.log('Warning: API key not set. You can set it later with: config -K=<key>');
114
114
  } else if (apiKeyInput.trim()) {
115
115
  console.log('API key saved');
116
116
  } else {
117
117
  console.log('Using existing API key');
118
118
  }
119
119
 
120
- // 4. Theme/Rules selection
121
- console.log('\nSelect AI Theme (affects language style):');
120
+ // 4. Rules preset selection
121
+ console.log('\nSelect AI Rules (affects language style):');
122
122
  const presetKeys = Object.keys(RULES_PRESETS);
123
123
  presetKeys.forEach((key, index) => {
124
124
  console.log(` ${index + 1}. ${RULES_PRESETS[key].name}`);
@@ -177,7 +177,7 @@ export async function initCommand(existingRl?: readline.Interface): Promise<void
177
177
  console.log(`Root directory: ${rootDir}`);
178
178
  console.log(`AI Provider: ${selectedProvider.name}`);
179
179
  console.log(`Model: ${selectedProvider.model}`);
180
- console.log(`Theme: ${RULES_PRESETS[selectedPreset]?.name || 'Custom'}\n`);
180
+ console.log(`Rules: ${RULES_PRESETS[selectedPreset]?.name || 'Custom'}\n`);
181
181
  } finally {
182
182
  if (shouldCloseRl) {
183
183
  rl.close();
@@ -13,7 +13,18 @@ export interface Config {
13
13
  rulesPreset?: string;
14
14
  }
15
15
 
16
- // Preset rules for different themes
16
+ // Supported models for validation
17
+ export const SUPPORTED_MODELS = [
18
+ 'claude-sonnet-4-20250514',
19
+ 'claude-opus-4-20250514',
20
+ 'gpt-4o',
21
+ 'gpt-4-turbo',
22
+ 'gemini-3-pro',
23
+ 'gemini-2.0-flash',
24
+ 'grok-beta',
25
+ ];
26
+
27
+ // Preset rules
17
28
  export const RULES_PRESETS: Record<string, { name: string; rules: string }> = {
18
29
  default: {
19
30
  name: 'Default (No theme)',