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.
- package/README.md +115 -85
- package/dist/commands/init.js +4 -4
- package/dist/config/config.js +12 -2
- package/dist/data/dictionaries.js +450 -0
- package/dist/data/loot.js +141 -0
- package/dist/interactive/commands.js +79 -75
- package/dist/interactive/startup.js +6 -5
- package/dist/storage/profile.js +222 -97
- package/package.json +1 -1
- package/src/commands/init.ts +4 -4
- package/src/config/config.ts +12 -1
- package/src/data/dictionaries.ts +521 -0
- package/src/data/loot.ts +177 -0
- package/src/interactive/commands.ts +92 -85
- package/src/interactive/startup.ts +6 -5
- package/src/storage/profile.ts +253 -94
package/src/data/loot.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { getDictionary, Dictionary } from './dictionaries';
|
|
2
|
+
|
|
3
|
+
export type Rarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
|
|
4
|
+
|
|
5
|
+
export interface LootItem {
|
|
6
|
+
name: string;
|
|
7
|
+
rarity: Rarity;
|
|
8
|
+
droppedAt: string;
|
|
9
|
+
source: 'task' | 'levelup' | 'achievement' | 'boss';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LootDropResult {
|
|
13
|
+
dropped: boolean;
|
|
14
|
+
item?: LootItem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Rarity drop chances by player level
|
|
18
|
+
// Higher level = higher chance for rare items, but overall drop rate decreases
|
|
19
|
+
function getDropChances(level: number): { dropRate: number; rarityWeights: Record<Rarity, number> } {
|
|
20
|
+
// Base drop rate decreases with level (starts at 30%, min 5%)
|
|
21
|
+
const baseDropRate = Math.max(0.05, 0.30 - (level * 0.01));
|
|
22
|
+
|
|
23
|
+
// Rarity weights shift toward rarer items at higher levels
|
|
24
|
+
const legendaryWeight = Math.min(0.05, level * 0.002);
|
|
25
|
+
const epicWeight = Math.min(0.10, level * 0.004);
|
|
26
|
+
const rareWeight = Math.min(0.20, 0.05 + level * 0.005);
|
|
27
|
+
const uncommonWeight = Math.min(0.30, 0.15 + level * 0.005);
|
|
28
|
+
const commonWeight = 1 - legendaryWeight - epicWeight - rareWeight - uncommonWeight;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
dropRate: baseDropRate,
|
|
32
|
+
rarityWeights: {
|
|
33
|
+
common: commonWeight,
|
|
34
|
+
uncommon: uncommonWeight,
|
|
35
|
+
rare: rareWeight,
|
|
36
|
+
epic: epicWeight,
|
|
37
|
+
legendary: legendaryWeight,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Bonus multipliers for different sources
|
|
43
|
+
const SOURCE_MULTIPLIERS: Record<string, number> = {
|
|
44
|
+
task: 1.0,
|
|
45
|
+
levelup: 3.0, // 3x chance on level up
|
|
46
|
+
achievement: 2.5, // 2.5x chance on achievement
|
|
47
|
+
boss: 2.0, // 2x chance on boss
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Select random rarity based on weights
|
|
51
|
+
function selectRarity(weights: Record<Rarity, number>): Rarity {
|
|
52
|
+
const roll = Math.random();
|
|
53
|
+
let cumulative = 0;
|
|
54
|
+
|
|
55
|
+
const rarities: Rarity[] = ['common', 'uncommon', 'rare', 'epic', 'legendary'];
|
|
56
|
+
for (const rarity of rarities) {
|
|
57
|
+
cumulative += weights[rarity];
|
|
58
|
+
if (roll < cumulative) {
|
|
59
|
+
return rarity;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return 'common';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Select random item from rarity pool
|
|
67
|
+
function selectItem(rarity: Rarity, dict: Dictionary): string {
|
|
68
|
+
const pool = dict.loot[rarity];
|
|
69
|
+
return pool[Math.floor(Math.random() * pool.length)];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Main function to roll for loot
|
|
73
|
+
export function rollForLoot(
|
|
74
|
+
level: number,
|
|
75
|
+
source: 'task' | 'levelup' | 'achievement' | 'boss',
|
|
76
|
+
rulesPreset?: string
|
|
77
|
+
): LootDropResult {
|
|
78
|
+
const dict = getDictionary(rulesPreset);
|
|
79
|
+
const { dropRate, rarityWeights } = getDropChances(level);
|
|
80
|
+
|
|
81
|
+
// Apply source multiplier
|
|
82
|
+
const adjustedDropRate = Math.min(0.95, dropRate * (SOURCE_MULTIPLIERS[source] || 1.0));
|
|
83
|
+
|
|
84
|
+
// Roll for drop
|
|
85
|
+
if (Math.random() > adjustedDropRate) {
|
|
86
|
+
return { dropped: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Select rarity and item
|
|
90
|
+
const rarity = selectRarity(rarityWeights);
|
|
91
|
+
const name = selectItem(rarity, dict);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
dropped: true,
|
|
95
|
+
item: {
|
|
96
|
+
name,
|
|
97
|
+
rarity,
|
|
98
|
+
droppedAt: new Date().toISOString(),
|
|
99
|
+
source,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Format loot drop message
|
|
105
|
+
export function formatLootDrop(item: LootItem, dict: Dictionary): string {
|
|
106
|
+
const rarityName = dict.rarities[item.rarity];
|
|
107
|
+
const raritySymbol = getRaritySymbol(item.rarity);
|
|
108
|
+
|
|
109
|
+
return `
|
|
110
|
+
=== ${dict.messages.lootDropped} ===
|
|
111
|
+
|
|
112
|
+
${raritySymbol} [${rarityName}] ${item.name}
|
|
113
|
+
`.trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Get rarity symbol/color indicator
|
|
117
|
+
function getRaritySymbol(rarity: Rarity): string {
|
|
118
|
+
switch (rarity) {
|
|
119
|
+
case 'common': return '[.]';
|
|
120
|
+
case 'uncommon': return '[+]';
|
|
121
|
+
case 'rare': return '[*]';
|
|
122
|
+
case 'epic': return '[#]';
|
|
123
|
+
case 'legendary': return '[!]';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Format inventory display
|
|
128
|
+
export function formatInventory(inventory: LootItem[], dict: Dictionary): string {
|
|
129
|
+
if (!inventory || inventory.length === 0) {
|
|
130
|
+
return `${dict.stats.inventory}: (empty)`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Group by rarity
|
|
134
|
+
const byRarity: Record<Rarity, LootItem[]> = {
|
|
135
|
+
legendary: [],
|
|
136
|
+
epic: [],
|
|
137
|
+
rare: [],
|
|
138
|
+
uncommon: [],
|
|
139
|
+
common: [],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
for (const item of inventory) {
|
|
143
|
+
byRarity[item.rarity].push(item);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const lines: string[] = [`=== ${dict.stats.inventory} (${inventory.length} items) ===`, ''];
|
|
147
|
+
|
|
148
|
+
for (const rarity of ['legendary', 'epic', 'rare', 'uncommon', 'common'] as Rarity[]) {
|
|
149
|
+
const items = byRarity[rarity];
|
|
150
|
+
if (items.length > 0) {
|
|
151
|
+
const symbol = getRaritySymbol(rarity);
|
|
152
|
+
const rarityName = dict.rarities[rarity];
|
|
153
|
+
lines.push(`${rarityName}:`);
|
|
154
|
+
for (const item of items) {
|
|
155
|
+
lines.push(` ${symbol} ${item.name}`);
|
|
156
|
+
}
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines.join('\n').trim();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Calculate inventory value (for stats)
|
|
165
|
+
export function calculateInventoryValue(inventory: LootItem[]): number {
|
|
166
|
+
if (!inventory) return 0;
|
|
167
|
+
|
|
168
|
+
const values: Record<Rarity, number> = {
|
|
169
|
+
common: 1,
|
|
170
|
+
uncommon: 5,
|
|
171
|
+
rare: 25,
|
|
172
|
+
epic: 100,
|
|
173
|
+
legendary: 500,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return inventory.reduce((sum, item) => sum + values[item.rarity], 0);
|
|
177
|
+
}
|
|
@@ -473,16 +473,27 @@ export async function processCommand(
|
|
|
473
473
|
|
|
474
474
|
// Stats command
|
|
475
475
|
if (command === 'stats') {
|
|
476
|
-
return wrapResult({ output: formatStats() });
|
|
476
|
+
return wrapResult({ output: formatStats(config.rulesPreset) });
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Inventory command
|
|
480
|
+
if (command === 'inventory' || command === 'inv' || command === 'loot') {
|
|
481
|
+
const { formatInventoryDisplay } = await import('../storage/profile');
|
|
482
|
+
return wrapResult({ output: formatInventoryDisplay(config.rulesPreset) });
|
|
477
483
|
}
|
|
478
484
|
|
|
479
485
|
// Achievements command
|
|
480
486
|
if (command === 'achievements' || command === 'ach') {
|
|
481
|
-
return wrapResult({ output: formatAchievements() });
|
|
487
|
+
return wrapResult({ output: formatAchievements(config.rulesPreset) });
|
|
482
488
|
}
|
|
483
489
|
|
|
484
490
|
// Done command - mark current node as completed
|
|
485
491
|
if (command === 'done') {
|
|
492
|
+
const { getDictionary } = await import('../data/dictionaries');
|
|
493
|
+
const { formatLootDrop } = await import('../data/loot');
|
|
494
|
+
const { getAchievementInfo } = await import('../storage/profile');
|
|
495
|
+
const dict = getDictionary(config.rulesPreset);
|
|
496
|
+
|
|
486
497
|
const nodeConfig = readNodeConfig(currentPath);
|
|
487
498
|
|
|
488
499
|
if (!nodeConfig) {
|
|
@@ -502,7 +513,8 @@ export async function processCommand(
|
|
|
502
513
|
result.xpGained,
|
|
503
514
|
nodeConfig.isBoss || false,
|
|
504
515
|
depth,
|
|
505
|
-
nodeConfig.createdAt
|
|
516
|
+
nodeConfig.createdAt,
|
|
517
|
+
config.rulesPreset
|
|
506
518
|
);
|
|
507
519
|
|
|
508
520
|
// Save to undo history
|
|
@@ -513,27 +525,33 @@ export async function processCommand(
|
|
|
513
525
|
timestamp: new Date().toISOString(),
|
|
514
526
|
});
|
|
515
527
|
|
|
516
|
-
let output = `\n===
|
|
517
|
-
output += `\nTasks
|
|
528
|
+
let output = `\n=== ${dict.messages.questCompleted} ===\n`;
|
|
529
|
+
output += `\nTasks: ${result.tasksCompleted}`;
|
|
518
530
|
if (result.bossesDefeated > 0) {
|
|
519
|
-
output +=
|
|
531
|
+
output += ` | Bosses: ${result.bossesDefeated}`;
|
|
520
532
|
}
|
|
521
533
|
output += `\n+${result.xpGained} XP`;
|
|
522
534
|
|
|
523
535
|
if (taskResult.levelUp) {
|
|
524
|
-
output += `\n\n***
|
|
525
|
-
output += `\
|
|
536
|
+
output += `\n\n*** ${dict.messages.levelUp} ***`;
|
|
537
|
+
output += `\n${dict.stats.level} ${taskResult.newLevel}!`;
|
|
526
538
|
}
|
|
527
539
|
|
|
528
540
|
if (taskResult.newAchievements.length > 0) {
|
|
529
|
-
output += `\n\n===
|
|
530
|
-
for (const
|
|
531
|
-
|
|
541
|
+
output += `\n\n=== ${dict.messages.newAchievement} ===`;
|
|
542
|
+
for (const achId of taskResult.newAchievements) {
|
|
543
|
+
const achInfo = getAchievementInfo(achId, dict);
|
|
544
|
+
if (achInfo) {
|
|
545
|
+
output += `\n[x] ${achInfo.name}: ${achInfo.desc}`;
|
|
546
|
+
}
|
|
532
547
|
}
|
|
533
548
|
}
|
|
534
549
|
|
|
535
|
-
|
|
536
|
-
|
|
550
|
+
if (taskResult.lootDropped) {
|
|
551
|
+
output += `\n\n${formatLootDrop(taskResult.lootDropped, dict)}`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
output += '\n\n[Type "undo" to revert]';
|
|
537
555
|
|
|
538
556
|
return wrapResult({ output });
|
|
539
557
|
}
|
|
@@ -566,10 +584,10 @@ export async function processCommand(
|
|
|
566
584
|
return wrapResult({ output });
|
|
567
585
|
}
|
|
568
586
|
|
|
569
|
-
// Deadline command
|
|
570
|
-
if (command === 'deadline') {
|
|
587
|
+
// Deadline command (dl as alias)
|
|
588
|
+
if (command === 'deadline' || command === 'dl') {
|
|
571
589
|
if (parts.length < 2) {
|
|
572
|
-
return wrapResult({ output: 'Usage: deadline <date
|
|
590
|
+
return wrapResult({ output: 'Usage: deadline <date> (or dl <date>)\nExamples: dl today, dl +3d, deadline Jan 15' });
|
|
573
591
|
}
|
|
574
592
|
|
|
575
593
|
const dateStr = parts.slice(1).join(' ');
|
|
@@ -1107,22 +1125,29 @@ export async function processCommand(
|
|
|
1107
1125
|
}
|
|
1108
1126
|
|
|
1109
1127
|
if (command === 'config') {
|
|
1110
|
-
const { updateConfig,
|
|
1128
|
+
const { updateConfig, SUPPORTED_MODELS } = await import('../config/config');
|
|
1111
1129
|
|
|
1112
|
-
// Check for flags
|
|
1113
|
-
const keyFlag = parts.find(p => p.startsWith('-
|
|
1114
|
-
const modelFlag = parts.find(p => p.startsWith('-
|
|
1115
|
-
const rulesFlag = parts.find(p => p.startsWith('-
|
|
1116
|
-
const themeFlag = parts.find(p => p.startsWith('-t=') || p.startsWith('--theme='));
|
|
1130
|
+
// Check for flags (uppercase short, lowercase long)
|
|
1131
|
+
const keyFlag = parts.find(p => p.startsWith('-K=') || p.startsWith('--key='));
|
|
1132
|
+
const modelFlag = parts.find(p => p.startsWith('-M=') || p.startsWith('--model='));
|
|
1133
|
+
const rulesFlag = parts.find(p => p.startsWith('-R=') || p.startsWith('--rules='));
|
|
1117
1134
|
|
|
1118
1135
|
if (keyFlag) {
|
|
1119
1136
|
const value = keyFlag.split('=').slice(1).join('=');
|
|
1137
|
+
if (!value) {
|
|
1138
|
+
return wrapResult({ output: 'Error: API key cannot be empty' });
|
|
1139
|
+
}
|
|
1120
1140
|
updateConfig({ apiKey: value });
|
|
1121
1141
|
return wrapResult({ output: 'API key updated.' });
|
|
1122
1142
|
}
|
|
1123
1143
|
|
|
1124
1144
|
if (modelFlag) {
|
|
1125
1145
|
const value = modelFlag.split('=').slice(1).join('=');
|
|
1146
|
+
if (!SUPPORTED_MODELS.includes(value)) {
|
|
1147
|
+
return wrapResult({
|
|
1148
|
+
output: `Error: Unknown model "${value}"\n\nSupported models:\n ${SUPPORTED_MODELS.join('\n ')}`
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1126
1151
|
updateConfig({ model: value });
|
|
1127
1152
|
return wrapResult({ output: `Model updated: ${value}` });
|
|
1128
1153
|
}
|
|
@@ -1130,21 +1155,7 @@ export async function processCommand(
|
|
|
1130
1155
|
if (rulesFlag) {
|
|
1131
1156
|
const value = rulesFlag.split('=').slice(1).join('=');
|
|
1132
1157
|
updateConfig({ rules: value, rulesPreset: 'custom' });
|
|
1133
|
-
return wrapResult({ output: '
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
if (themeFlag) {
|
|
1137
|
-
const value = themeFlag.split('=').slice(1).join('=').toLowerCase();
|
|
1138
|
-
if (RULES_PRESETS[value]) {
|
|
1139
|
-
updateConfig({
|
|
1140
|
-
rules: RULES_PRESETS[value].rules,
|
|
1141
|
-
rulesPreset: value
|
|
1142
|
-
});
|
|
1143
|
-
return wrapResult({ output: `Theme updated: ${RULES_PRESETS[value].name}` });
|
|
1144
|
-
} else {
|
|
1145
|
-
const themes = Object.keys(RULES_PRESETS).join(', ');
|
|
1146
|
-
return wrapResult({ output: `Unknown theme. Available: ${themes}` });
|
|
1147
|
-
}
|
|
1158
|
+
return wrapResult({ output: 'Rules updated.' });
|
|
1148
1159
|
}
|
|
1149
1160
|
|
|
1150
1161
|
// Show config
|
|
@@ -1152,27 +1163,21 @@ export async function processCommand(
|
|
|
1152
1163
|
? config.apiKey.slice(0, 8) + '...' + config.apiKey.slice(-4)
|
|
1153
1164
|
: '(not set)';
|
|
1154
1165
|
|
|
1155
|
-
const themeName = config.rulesPreset
|
|
1156
|
-
? (RULES_PRESETS[config.rulesPreset]?.name || 'Custom')
|
|
1157
|
-
: 'Default';
|
|
1158
|
-
|
|
1159
1166
|
const rulesPreview = config.rules
|
|
1160
|
-
? (config.rules.length >
|
|
1161
|
-
: '(
|
|
1167
|
+
? (config.rules.length > 60 ? config.rules.substring(0, 60) + '...' : config.rules)
|
|
1168
|
+
: '(default)';
|
|
1162
1169
|
|
|
1163
1170
|
const output = `
|
|
1164
1171
|
Provider: ${config.aiProvider}
|
|
1165
1172
|
Model: ${config.model || '(default)'}
|
|
1166
1173
|
API Key: ${maskedKey}
|
|
1167
1174
|
Storage: ${config.storagePath}
|
|
1168
|
-
Theme: ${themeName}
|
|
1169
1175
|
Rules: ${rulesPreview}
|
|
1170
1176
|
|
|
1171
1177
|
Set with flags:
|
|
1172
|
-
config -
|
|
1173
|
-
config -
|
|
1174
|
-
config -
|
|
1175
|
-
config -r="<rules>" Set custom rules
|
|
1178
|
+
config -K=<key> or --key=<key>
|
|
1179
|
+
config -M=<model> or --model=<model>
|
|
1180
|
+
config -R="<rules>" or --rules="<rules>"
|
|
1176
1181
|
`.trim();
|
|
1177
1182
|
|
|
1178
1183
|
return wrapResult({ output });
|
|
@@ -1187,49 +1192,51 @@ Navigation:
|
|
|
1187
1192
|
ls List tasks and files
|
|
1188
1193
|
tree [-A] [--depth=N] Show task tree
|
|
1189
1194
|
cd <task> Navigate into task
|
|
1190
|
-
|
|
1191
|
-
pwd
|
|
1192
|
-
open Open
|
|
1195
|
+
.., ... Go up 1 or 2 levels
|
|
1196
|
+
pwd Current path
|
|
1197
|
+
open Open in Finder
|
|
1193
1198
|
|
|
1194
|
-
|
|
1195
|
-
mkdir <name> Create
|
|
1196
|
-
done Complete
|
|
1199
|
+
Tasks:
|
|
1200
|
+
mkdir <name> Create task
|
|
1201
|
+
done Complete (earns XP)
|
|
1197
1202
|
undo Undo last done
|
|
1198
|
-
|
|
1199
|
-
boss Toggle boss
|
|
1200
|
-
block [node] Block by task
|
|
1203
|
+
dl <date> Set deadline (dl +3d, dl Jan 15)
|
|
1204
|
+
boss Toggle boss (3x XP)
|
|
1205
|
+
block [node] Block by task
|
|
1201
1206
|
unblock Remove block
|
|
1202
1207
|
status Task details
|
|
1203
|
-
check
|
|
1208
|
+
check Deadline alerts
|
|
1204
1209
|
|
|
1205
1210
|
Gamification:
|
|
1206
1211
|
stats XP, level, streaks
|
|
1207
|
-
achievements Achievement list
|
|
1208
|
-
|
|
1212
|
+
achievements Achievement list (infinite)
|
|
1213
|
+
inventory Loot collection
|
|
1214
|
+
map Dungeon map
|
|
1215
|
+
map --ai AI-generated map
|
|
1209
1216
|
|
|
1210
|
-
|
|
1217
|
+
Rules (AI style presets):
|
|
1218
|
+
Set via init or config -R="<rules>"
|
|
1219
|
+
Presets: fantasy, space, starwars, western, cyberpunk, pirate
|
|
1220
|
+
|
|
1221
|
+
Config:
|
|
1211
1222
|
init Setup wizard
|
|
1212
1223
|
config Show settings
|
|
1213
|
-
config -
|
|
1214
|
-
config -
|
|
1215
|
-
config -
|
|
1216
|
-
config -r="<rules>" Custom AI rules
|
|
1217
|
-
|
|
1218
|
-
Themes:
|
|
1219
|
-
default, fantasy, space, starwars, western, cyberpunk, pirate
|
|
1224
|
+
config -K=<key> or --key=<key>
|
|
1225
|
+
config -M=<model> or --model=<model>
|
|
1226
|
+
config -R="<rules>" or --rules="<rules>"
|
|
1220
1227
|
|
|
1221
|
-
|
|
1222
|
-
cp, mv, rm [-rf] Standard
|
|
1223
|
-
clean --yes Clear
|
|
1228
|
+
Files:
|
|
1229
|
+
cp, mv, rm [-rf] Standard operations
|
|
1230
|
+
clean --yes Clear folder
|
|
1224
1231
|
|
|
1225
|
-
AI
|
|
1226
|
-
<description>
|
|
1227
|
-
save Save to folders
|
|
1232
|
+
AI:
|
|
1233
|
+
<description> Generate preview
|
|
1234
|
+
save Save to folders
|
|
1228
1235
|
cancel Discard
|
|
1229
1236
|
|
|
1230
1237
|
Clipboard:
|
|
1231
|
-
<cmd> | pbcopy
|
|
1232
|
-
<cmd> | clip
|
|
1238
|
+
<cmd> | pbcopy macOS
|
|
1239
|
+
<cmd> | clip Windows
|
|
1233
1240
|
|
|
1234
1241
|
www.rlc.rocks
|
|
1235
1242
|
`.trim()
|
|
@@ -1254,17 +1261,17 @@ www.rlc.rocks
|
|
|
1254
1261
|
|
|
1255
1262
|
return wrapResult({ output: `Created todo folder: ${safeName}/` });
|
|
1256
1263
|
} else {
|
|
1257
|
-
|
|
1258
|
-
|
|
1264
|
+
const schemaPath = saveSchemaFile(
|
|
1265
|
+
currentPath,
|
|
1259
1266
|
pending.title,
|
|
1260
1267
|
pending.content
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
+
);
|
|
1269
|
+
const filename = path.basename(schemaPath);
|
|
1270
|
+
|
|
1271
|
+
sessionState.pending = null;
|
|
1272
|
+
sessionState.history = [];
|
|
1273
|
+
|
|
1274
|
+
return wrapResult({ output: `Saved: ${filename}` });
|
|
1268
1275
|
}
|
|
1269
1276
|
}
|
|
1270
1277
|
|
|
@@ -17,16 +17,17 @@ const ASCII_ART = [
|
|
|
17
17
|
'║ Roguelike CLI ║',
|
|
18
18
|
'╚═════════════════════════╝',
|
|
19
19
|
'',
|
|
20
|
-
' Tasks: done, undo,
|
|
21
|
-
' Stats: stats, achievements,
|
|
22
|
-
' Config: init, config -t=<theme>',
|
|
20
|
+
' Tasks: done, undo, dl <date>, boss, block',
|
|
21
|
+
' Stats: stats, achievements, inventory, map',
|
|
23
22
|
'',
|
|
24
|
-
'
|
|
23
|
+
' Rules: fantasy, space, starwars, cyberpunk',
|
|
24
|
+
' Config: init, config -R="<rules>"',
|
|
25
25
|
'',
|
|
26
26
|
' TAB autocomplete, | pbcopy to copy',
|
|
27
27
|
' <description> -> refine -> save',
|
|
28
28
|
'',
|
|
29
|
-
' help - commands
|
|
29
|
+
' help - commands',
|
|
30
|
+
' www.rlc.rocks',
|
|
30
31
|
'',
|
|
31
32
|
' Ready...',
|
|
32
33
|
'',
|