roguelike-cli 1.3.1 → 1.3.3
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 +132 -133
- package/dist/ai/claude.js +14 -4
- package/dist/commands/init.js +39 -6
- package/dist/config/config.js +42 -0
- package/dist/interactive/commands.js +77 -65
- package/dist/interactive/startup.js +7 -6
- package/package.json +1 -1
- package/src/ai/claude.ts +18 -4
- package/src/commands/init.ts +43 -7
- package/src/config/config.ts +45 -0
- package/src/interactive/commands.ts +83 -70
- package/src/interactive/startup.ts +7 -6
package/README.md
CHANGED
|
@@ -17,21 +17,14 @@
|
|
|
17
17
|
╚═════════════════════════╝
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
**A gamified task manager where every task is a folder and every project is a dungeon.**
|
|
23
|
-
|
|
24
|
-
Instead of flat text files, your tasks become a **file system tree**. Nested tasks = nested folders. Complete tasks to earn XP, level up, and unlock achievements.
|
|
20
|
+
**Turn your backlog into a dungeon crawl.** Roguelike CLI is a terminal-based task manager that transforms productivity into an RPG experience. Complete quests, level up, and conquer your goals.
|
|
25
21
|
|
|
26
22
|
## Features
|
|
27
23
|
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
- **Set deadlines** with human-readable dates
|
|
33
|
-
- **Generate visualizations** - trees, block diagrams, dungeon maps
|
|
34
|
-
- Let **AI help** you structure complex projects
|
|
24
|
+
- **RPG Engine** — Tasks are quests. Completing them earns XP, unlocks achievements, and levels you up.
|
|
25
|
+
- **AI Game Master** — Integrated AI helps decompose complex tasks and generates structured plans.
|
|
26
|
+
- **Local-First** — Your data stays on your machine in simple folders and JSON files.
|
|
27
|
+
- **Customizable Rules** — Choose adventure style: Fantasy, Space Opera, Star Wars, Cyberpunk, and more.
|
|
35
28
|
|
|
36
29
|
## Install
|
|
37
30
|
|
|
@@ -43,36 +36,101 @@ rlc
|
|
|
43
36
|
## Quick Start
|
|
44
37
|
|
|
45
38
|
```
|
|
46
|
-
> todo launch startup
|
|
39
|
+
> todo launch my startup
|
|
47
40
|
|
|
48
41
|
├── Research [BOSS]
|
|
49
42
|
│ ├── Market analysis
|
|
50
|
-
│ └── User interviews
|
|
43
|
+
│ └── User interviews [DUE: +7d]
|
|
51
44
|
├── Development
|
|
52
|
-
│ ├── Backend API [DUE:
|
|
45
|
+
│ ├── Backend API [DUE: Jan 20]
|
|
53
46
|
│ └── Frontend UI
|
|
54
47
|
└── Launch [MILESTONE]
|
|
55
|
-
└── Marketing campaign
|
|
56
48
|
|
|
57
|
-
[Type "save" to create
|
|
49
|
+
[Type "save" to create folders]
|
|
58
50
|
> save
|
|
59
|
-
Created
|
|
51
|
+
Created: launch-my-startup/
|
|
60
52
|
|
|
61
|
-
> cd launch-startup/research
|
|
53
|
+
> cd launch-my-startup/research
|
|
62
54
|
> done
|
|
63
55
|
|
|
64
|
-
===
|
|
56
|
+
=== QUEST COMPLETED ===
|
|
65
57
|
|
|
66
|
-
Tasks completed: 3
|
|
67
|
-
Bosses defeated: 1
|
|
68
58
|
+45 XP
|
|
69
|
-
|
|
70
59
|
*** LEVEL UP! ***
|
|
71
|
-
You are now level 2!
|
|
72
60
|
|
|
73
61
|
=== NEW ACHIEVEMENTS ===
|
|
74
|
-
[x] First Blood: Complete your first
|
|
75
|
-
[x] Boss Slayer:
|
|
62
|
+
[x] First Blood: Complete your first quest
|
|
63
|
+
[x] Boss Slayer: Defeat a boss
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Folder Structure
|
|
67
|
+
|
|
68
|
+
Every task is a folder. You can drop files directly into task folders — designs, docs, code, anything. Your file manager becomes your task manager.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
my-startup/
|
|
72
|
+
├── research/
|
|
73
|
+
│ ├── market-analysis/
|
|
74
|
+
│ │ └── competitors.xlsx <- attached file
|
|
75
|
+
│ └── user-interviews/
|
|
76
|
+
│ └── notes.md <- attached file
|
|
77
|
+
├── development/
|
|
78
|
+
│ ├── backend-api/
|
|
79
|
+
│ │ └── spec.yaml <- attached file
|
|
80
|
+
│ └── frontend-ui/
|
|
81
|
+
└── launch/
|
|
82
|
+
└── marketing/
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Navigate with `cd`, view with `tree`, open in Finder with `open`. It's just folders.
|
|
86
|
+
|
|
87
|
+
## Rules
|
|
88
|
+
|
|
89
|
+
Rules change how the AI speaks. Set during `init` or with `config -R="<rules>"`.
|
|
90
|
+
|
|
91
|
+
| Preset | Style |
|
|
92
|
+
|--------|-------|
|
|
93
|
+
| `default` | Standard task manager language |
|
|
94
|
+
| `fantasy` | Quests, dungeons, dragons, loot |
|
|
95
|
+
| `space` | Missions, starships, commanders |
|
|
96
|
+
| `starwars` | Jedi, Force, Rebel Alliance |
|
|
97
|
+
| `western` | Bounties, sheriffs, frontier |
|
|
98
|
+
| `cyberpunk` | Gigs, netrunners, corps |
|
|
99
|
+
| `pirate` | Plunder, treasure, seven seas |
|
|
100
|
+
|
|
101
|
+
### Custom Rules
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
> config -R="Speak like a medieval knight. Tasks are 'duties'. Use 'huzzah' for success."
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Or select "Custom" during `init` to enter your own rules.
|
|
108
|
+
|
|
109
|
+
## Dungeon Map
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
> map
|
|
113
|
+
|
|
114
|
+
#########################################
|
|
115
|
+
# # #
|
|
116
|
+
# [Research] # [Development] #
|
|
117
|
+
# x Analysis +---* Backend #
|
|
118
|
+
# x Interviews # * Frontend #
|
|
119
|
+
# # #
|
|
120
|
+
##########+###########+#################
|
|
121
|
+
| |
|
|
122
|
+
##########+###########+#################
|
|
123
|
+
# #
|
|
124
|
+
# [Launch] #
|
|
125
|
+
# * Marketing #
|
|
126
|
+
# @ SHIP IT! [BOSS] #
|
|
127
|
+
# #
|
|
128
|
+
#########################################
|
|
129
|
+
|
|
130
|
+
Legend: * Task x Done @ Boss + Door
|
|
131
|
+
|
|
132
|
+
> map --ai
|
|
133
|
+
(AI generates creative dungeon layout based on your tasks)
|
|
76
134
|
```
|
|
77
135
|
|
|
78
136
|
## Commands
|
|
@@ -84,24 +142,21 @@ You are now level 2!
|
|
|
84
142
|
| `ls` | List tasks (shows status) |
|
|
85
143
|
| `tree` | Task tree with deadlines |
|
|
86
144
|
| `tree -A` | Include files |
|
|
87
|
-
| `tree --depth=N` | Limit depth |
|
|
88
145
|
| `cd <task>` | Enter task |
|
|
89
|
-
| `..`, `...` | Go up
|
|
90
|
-
| `pwd` | Current path |
|
|
146
|
+
| `..`, `...` | Go up levels |
|
|
91
147
|
| `open` | Open in Finder |
|
|
92
148
|
|
|
93
149
|
### Task Management
|
|
94
150
|
|
|
95
151
|
| Command | Description |
|
|
96
152
|
|---------|-------------|
|
|
97
|
-
| `done` | Complete task (
|
|
98
|
-
| `undo` | Undo last done
|
|
99
|
-
| `
|
|
100
|
-
| `boss` | Toggle boss
|
|
101
|
-
| `block [node]` | Block by task
|
|
102
|
-
| `unblock` | Remove
|
|
103
|
-
| `
|
|
104
|
-
| `check` | Show overdue/upcoming deadlines |
|
|
153
|
+
| `done` | Complete task (earns XP) |
|
|
154
|
+
| `undo` | Undo last done |
|
|
155
|
+
| `dl <date>` | Set deadline (dl +3d, dl Jan 15) |
|
|
156
|
+
| `boss` | Toggle boss (3x XP) |
|
|
157
|
+
| `block [node]` | Block by task |
|
|
158
|
+
| `unblock` | Remove block |
|
|
159
|
+
| `check` | Show deadlines |
|
|
105
160
|
|
|
106
161
|
### Gamification
|
|
107
162
|
|
|
@@ -109,136 +164,80 @@ You are now level 2!
|
|
|
109
164
|
|---------|-------------|
|
|
110
165
|
| `stats` | XP, level, streaks |
|
|
111
166
|
| `achievements` | Achievement list |
|
|
112
|
-
| `map` | Dungeon map
|
|
113
|
-
| `map --ai` | AI-generated
|
|
167
|
+
| `map` | Dungeon map |
|
|
168
|
+
| `map --ai` | AI-generated map |
|
|
114
169
|
|
|
115
170
|
### File Operations
|
|
116
171
|
|
|
117
172
|
| Command | Description |
|
|
118
173
|
|---------|-------------|
|
|
119
174
|
| `mkdir <name>` | Create task |
|
|
120
|
-
| `cp
|
|
121
|
-
| `mv <src> <dst>` | Move/rename |
|
|
122
|
-
| `rm <name>` | Delete file |
|
|
123
|
-
| `rm -rf <name>` | Delete folder |
|
|
175
|
+
| `cp`, `mv`, `rm` | Standard ops |
|
|
124
176
|
|
|
125
|
-
###
|
|
177
|
+
### Configuration
|
|
126
178
|
|
|
127
179
|
| Command | Description |
|
|
128
180
|
|---------|-------------|
|
|
129
|
-
|
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
181
|
+
| `init` | Setup wizard |
|
|
182
|
+
| `config` | View settings |
|
|
183
|
+
| `config -K=<key>` | or `--key=<key>` |
|
|
184
|
+
| `config -M=<model>` | or `--model=<model>` |
|
|
185
|
+
| `config -R="<rules>"` | or `--rules="<rules>"` |
|
|
186
|
+
|
|
187
|
+
### Clipboard
|
|
188
|
+
|
|
189
|
+
| Command | Description |
|
|
190
|
+
|---------|-------------|
|
|
191
|
+
| `<cmd> \| pbcopy` | Copy to clipboard (macOS) |
|
|
192
|
+
| `<cmd> \| clip` | Copy to clipboard (Windows) |
|
|
132
193
|
|
|
133
194
|
## Deadlines
|
|
134
195
|
|
|
135
196
|
```
|
|
136
|
-
>
|
|
137
|
-
>
|
|
138
|
-
>
|
|
139
|
-
> deadline Jan 15
|
|
197
|
+
> dl today # Due today
|
|
198
|
+
> dl tomorrow # Due tomorrow
|
|
199
|
+
> dl +3d # In 3 days
|
|
200
|
+
> deadline Jan 15 # Specific date
|
|
140
201
|
```
|
|
141
202
|
|
|
142
203
|
Tree shows deadlines:
|
|
143
|
-
|
|
144
204
|
```
|
|
145
|
-
├── Backend
|
|
205
|
+
├── Backend/ [BOSS] [3d left]
|
|
146
206
|
│ ├── Database/ [DONE]
|
|
147
|
-
│ └──
|
|
207
|
+
│ └── API/ [OVERDUE 2d]
|
|
148
208
|
└── Frontend/ [tomorrow]
|
|
149
209
|
```
|
|
150
210
|
|
|
151
211
|
## XP System
|
|
152
212
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
|
158
|
-
|-------|-------------|
|
|
159
|
-
| 1 | 0 |
|
|
160
|
-
| 2 | 100 |
|
|
161
|
-
| 3 | 150 |
|
|
162
|
-
| 5 | 337 |
|
|
163
|
-
| 10 | 3,844 |
|
|
213
|
+
| Factor | XP |
|
|
214
|
+
|--------|-----|
|
|
215
|
+
| Base task | 10 |
|
|
216
|
+
| Per depth level | +5 |
|
|
217
|
+
| Boss multiplier | 3x |
|
|
164
218
|
|
|
165
219
|
## Achievements
|
|
166
220
|
|
|
167
|
-
| Achievement |
|
|
168
|
-
|
|
221
|
+
| Achievement | How to unlock |
|
|
222
|
+
|-------------|---------------|
|
|
169
223
|
| First Blood | Complete first task |
|
|
170
224
|
| Getting Started | Complete 10 tasks |
|
|
171
|
-
| Productive | Complete 50 tasks |
|
|
172
225
|
| Centurion | Complete 100 tasks |
|
|
173
|
-
| Deep Diver |
|
|
174
|
-
| Boss Slayer | Complete a boss
|
|
175
|
-
|
|
|
176
|
-
| Speedrunner | Complete task same day |
|
|
177
|
-
| On a Roll | 3 day streak |
|
|
226
|
+
| Deep Diver | Task at depth 5+ |
|
|
227
|
+
| Boss Slayer | Complete a boss |
|
|
228
|
+
| Speedrunner | Same-day completion |
|
|
178
229
|
| Streak Master | 7 day streak |
|
|
179
|
-
| Unstoppable | 30 day streak |
|
|
180
|
-
| Adventurer | Reach level 5 |
|
|
181
|
-
| Veteran | Reach level 10 |
|
|
182
|
-
| Legend | Reach level 25 |
|
|
183
|
-
|
|
184
|
-
## Dungeon Map
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
> map
|
|
188
|
-
|
|
189
|
-
###########################################
|
|
190
|
-
# # #
|
|
191
|
-
# [Research] # [Development] #
|
|
192
|
-
# * Market +---* Backend #
|
|
193
|
-
# x Users # @ Deploy BOSS #
|
|
194
|
-
# # #
|
|
195
|
-
##########+############+##################
|
|
196
|
-
| |
|
|
197
|
-
##########+############+##################
|
|
198
|
-
# #
|
|
199
|
-
# [Launch] #
|
|
200
|
-
# * Marketing #
|
|
201
|
-
# @ SHIP IT! [BOSS] #
|
|
202
|
-
# #
|
|
203
|
-
###########################################
|
|
204
|
-
|
|
205
|
-
Legend: * Task x Done @ Boss ! Blocked + Door
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Use `map --ai` for creative AI-generated layouts.
|
|
209
|
-
|
|
210
|
-
## Block Diagrams
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
> schema kubernetes cluster
|
|
214
|
-
|
|
215
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
216
|
-
│ Kubernetes Cluster │
|
|
217
|
-
│ │
|
|
218
|
-
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
219
|
-
│ │ Control Plane │ │ Worker Nodes │ │
|
|
220
|
-
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
221
|
-
│ └──────────┬───────────────┘ │
|
|
222
|
-
│ ┌──────────────────┐│┌──────────────────┐ │
|
|
223
|
-
│ │ PostgreSQL │││ Redis │ │
|
|
224
|
-
│ └──────────────────┘│└──────────────────┘ │
|
|
225
|
-
└─────────────────────────────────────────────────────────────┘
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Clipboard
|
|
229
|
-
|
|
230
|
-
```
|
|
231
|
-
> tree | pbcopy # macOS
|
|
232
|
-
> tree | clip # Windows
|
|
233
|
-
> ls | copy # Alternative
|
|
234
|
-
```
|
|
235
230
|
|
|
236
|
-
##
|
|
231
|
+
## Supported Models
|
|
237
232
|
|
|
238
233
|
```
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
claude-sonnet-4-20250514 (default)
|
|
235
|
+
claude-opus-4-20250514
|
|
236
|
+
gpt-4o
|
|
237
|
+
gpt-4-turbo
|
|
238
|
+
gemini-3-pro
|
|
239
|
+
gemini-2.0-flash
|
|
240
|
+
grok-beta
|
|
242
241
|
```
|
|
243
242
|
|
|
244
243
|
## Website
|
package/dist/ai/claude.js
CHANGED
|
@@ -46,7 +46,7 @@ Create a creative, interesting dungeon layout for the given tasks.
|
|
|
46
46
|
Output ONLY the ASCII map, no JSON wrapper.`;
|
|
47
47
|
async function generateSchemaWithAI(input, config, signal, history) {
|
|
48
48
|
if (!config.apiKey) {
|
|
49
|
-
throw new Error('API key not set. Use config
|
|
49
|
+
throw new Error('API key not set. Use config -k=<key> to set it.');
|
|
50
50
|
}
|
|
51
51
|
const client = new sdk_1.default({
|
|
52
52
|
apiKey: config.apiKey,
|
|
@@ -68,10 +68,15 @@ async function generateSchemaWithAI(input, config, signal, history) {
|
|
|
68
68
|
});
|
|
69
69
|
try {
|
|
70
70
|
const model = config.model || 'claude-sonnet-4-20250514';
|
|
71
|
+
// Build system prompt with custom rules
|
|
72
|
+
let systemPrompt = SYSTEM_PROMPT;
|
|
73
|
+
if (config.rules) {
|
|
74
|
+
systemPrompt += '\n\nADDITIONAL STYLE RULES (apply to all responses):\n' + config.rules;
|
|
75
|
+
}
|
|
71
76
|
const message = await client.messages.create({
|
|
72
77
|
model: model,
|
|
73
78
|
max_tokens: 2000,
|
|
74
|
-
system:
|
|
79
|
+
system: systemPrompt,
|
|
75
80
|
messages: messages,
|
|
76
81
|
});
|
|
77
82
|
const content = message.content[0];
|
|
@@ -98,17 +103,22 @@ async function generateSchemaWithAI(input, config, signal, history) {
|
|
|
98
103
|
}
|
|
99
104
|
async function generateDungeonMapWithAI(treeContent, config, signal) {
|
|
100
105
|
if (!config.apiKey) {
|
|
101
|
-
throw new Error('API key not set. Use config
|
|
106
|
+
throw new Error('API key not set. Use config -k=<key> to set it.');
|
|
102
107
|
}
|
|
103
108
|
const client = new sdk_1.default({
|
|
104
109
|
apiKey: config.apiKey,
|
|
105
110
|
});
|
|
106
111
|
try {
|
|
107
112
|
const model = config.model || 'claude-sonnet-4-20250514';
|
|
113
|
+
// Build system prompt with custom rules
|
|
114
|
+
let systemPrompt = DUNGEON_MAP_PROMPT;
|
|
115
|
+
if (config.rules) {
|
|
116
|
+
systemPrompt += '\n\nADDITIONAL STYLE RULES:\n' + config.rules;
|
|
117
|
+
}
|
|
108
118
|
const message = await client.messages.create({
|
|
109
119
|
model: model,
|
|
110
120
|
max_tokens: 2000,
|
|
111
|
-
system:
|
|
121
|
+
system: systemPrompt,
|
|
112
122
|
messages: [{
|
|
113
123
|
role: 'user',
|
|
114
124
|
content: 'Generate a dungeon map for this task tree:\n\n' + treeContent
|
package/dist/commands/init.js
CHANGED
|
@@ -63,7 +63,6 @@ function copyRecursive(src, dest) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
async function initCommand(existingRl) {
|
|
66
|
-
// Create our own readline if not provided, or use existing one
|
|
67
66
|
const rl = existingRl || readline.createInterface({
|
|
68
67
|
input: process.stdin,
|
|
69
68
|
output: process.stdout,
|
|
@@ -73,7 +72,6 @@ async function initCommand(existingRl) {
|
|
|
73
72
|
console.log('\n╔═══════════════════════════════════════╗');
|
|
74
73
|
console.log('║ ROGUELIKE CLI INITIALIZATION WIZARD ║');
|
|
75
74
|
console.log('╚═══════════════════════════════════════╝\n');
|
|
76
|
-
// Get existing config if any
|
|
77
75
|
const existingConfig = await (0, config_1.initConfig)();
|
|
78
76
|
const oldStoragePath = existingConfig?.storagePath;
|
|
79
77
|
// 1. Root directory
|
|
@@ -121,7 +119,7 @@ async function initCommand(existingRl) {
|
|
|
121
119
|
const selectedIndex = parseInt(aiChoice.trim()) - 1 || 0;
|
|
122
120
|
const selectedProvider = aiProviders[selectedIndex] || aiProviders[0];
|
|
123
121
|
console.log(`Selected: ${selectedProvider.name} (${selectedProvider.model})`);
|
|
124
|
-
// 3. API Key
|
|
122
|
+
// 3. API Key
|
|
125
123
|
const existingApiKey = existingConfig?.apiKey || '';
|
|
126
124
|
const hasExistingKey = existingApiKey.length > 0;
|
|
127
125
|
const keyPrompt = hasExistingKey
|
|
@@ -130,7 +128,7 @@ async function initCommand(existingRl) {
|
|
|
130
128
|
const apiKeyInput = await question(rl, keyPrompt);
|
|
131
129
|
const apiKey = apiKeyInput.trim() || existingApiKey;
|
|
132
130
|
if (!apiKey) {
|
|
133
|
-
console.log('Warning: API key not set. You can set it later with config
|
|
131
|
+
console.log('Warning: API key not set. You can set it later with: config -K=<key>');
|
|
134
132
|
}
|
|
135
133
|
else if (apiKeyInput.trim()) {
|
|
136
134
|
console.log('API key saved');
|
|
@@ -138,6 +136,39 @@ async function initCommand(existingRl) {
|
|
|
138
136
|
else {
|
|
139
137
|
console.log('Using existing API key');
|
|
140
138
|
}
|
|
139
|
+
// 4. Rules preset selection
|
|
140
|
+
console.log('\nSelect AI Rules (affects language style):');
|
|
141
|
+
const presetKeys = Object.keys(config_1.RULES_PRESETS);
|
|
142
|
+
presetKeys.forEach((key, index) => {
|
|
143
|
+
console.log(` ${index + 1}. ${config_1.RULES_PRESETS[key].name}`);
|
|
144
|
+
});
|
|
145
|
+
console.log(` ${presetKeys.length + 1}. Custom (enter your own rules)`);
|
|
146
|
+
const existingPreset = existingConfig?.rulesPreset || 'default';
|
|
147
|
+
const defaultPresetIndex = presetKeys.indexOf(existingPreset) + 1 || 1;
|
|
148
|
+
const themeChoice = await question(rl, `\nEnter choice [1-${presetKeys.length + 1}] (default: ${defaultPresetIndex}): `);
|
|
149
|
+
const themeIndex = parseInt(themeChoice.trim()) - 1;
|
|
150
|
+
let selectedRules = '';
|
|
151
|
+
let selectedPreset = 'default';
|
|
152
|
+
if (themeIndex >= 0 && themeIndex < presetKeys.length) {
|
|
153
|
+
selectedPreset = presetKeys[themeIndex];
|
|
154
|
+
selectedRules = config_1.RULES_PRESETS[selectedPreset].rules;
|
|
155
|
+
console.log(`Selected: ${config_1.RULES_PRESETS[selectedPreset].name}`);
|
|
156
|
+
}
|
|
157
|
+
else if (themeIndex === presetKeys.length) {
|
|
158
|
+
// Custom rules
|
|
159
|
+
console.log('\nEnter your custom rules for AI (how it should speak, what terms to use):');
|
|
160
|
+
console.log('Example: "Use pirate language. Tasks are treasure hunts. Be playful."');
|
|
161
|
+
const customRules = await question(rl, '\nYour rules: ');
|
|
162
|
+
selectedRules = customRules.trim();
|
|
163
|
+
selectedPreset = 'custom';
|
|
164
|
+
console.log('Custom rules saved');
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Default
|
|
168
|
+
selectedPreset = existingConfig?.rulesPreset || 'default';
|
|
169
|
+
selectedRules = existingConfig?.rules || '';
|
|
170
|
+
console.log(`Keeping: ${config_1.RULES_PRESETS[selectedPreset]?.name || 'Default'}`);
|
|
171
|
+
}
|
|
141
172
|
// Save config
|
|
142
173
|
const config = {
|
|
143
174
|
aiProvider: selectedProvider.name,
|
|
@@ -146,9 +177,10 @@ async function initCommand(existingRl) {
|
|
|
146
177
|
storagePath: rootDir,
|
|
147
178
|
currentPath: rootDir,
|
|
148
179
|
model: selectedProvider.model,
|
|
180
|
+
rules: selectedRules,
|
|
181
|
+
rulesPreset: selectedPreset,
|
|
149
182
|
};
|
|
150
183
|
(0, config_1.saveConfig)(config);
|
|
151
|
-
// Ensure storage directory exists
|
|
152
184
|
if (!fs.existsSync(rootDir)) {
|
|
153
185
|
fs.mkdirSync(rootDir, { recursive: true });
|
|
154
186
|
}
|
|
@@ -157,7 +189,8 @@ async function initCommand(existingRl) {
|
|
|
157
189
|
console.log('╚═══════════════════════════════════════╝\n');
|
|
158
190
|
console.log(`Root directory: ${rootDir}`);
|
|
159
191
|
console.log(`AI Provider: ${selectedProvider.name}`);
|
|
160
|
-
console.log(`Model: ${selectedProvider.model}
|
|
192
|
+
console.log(`Model: ${selectedProvider.model}`);
|
|
193
|
+
console.log(`Rules: ${config_1.RULES_PRESETS[selectedPreset]?.name || 'Custom'}\n`);
|
|
161
194
|
}
|
|
162
195
|
finally {
|
|
163
196
|
if (shouldCloseRl) {
|
package/dist/config/config.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RULES_PRESETS = exports.SUPPORTED_MODELS = void 0;
|
|
36
37
|
exports.initConfig = initConfig;
|
|
37
38
|
exports.saveConfig = saveConfig;
|
|
38
39
|
exports.getConfig = getConfig;
|
|
@@ -40,6 +41,47 @@ exports.updateConfig = updateConfig;
|
|
|
40
41
|
const fs = __importStar(require("fs"));
|
|
41
42
|
const path = __importStar(require("path"));
|
|
42
43
|
const os = __importStar(require("os"));
|
|
44
|
+
// Supported models for validation
|
|
45
|
+
exports.SUPPORTED_MODELS = [
|
|
46
|
+
'claude-sonnet-4-20250514',
|
|
47
|
+
'claude-opus-4-20250514',
|
|
48
|
+
'gpt-4o',
|
|
49
|
+
'gpt-4-turbo',
|
|
50
|
+
'gemini-3-pro',
|
|
51
|
+
'gemini-2.0-flash',
|
|
52
|
+
'grok-beta',
|
|
53
|
+
];
|
|
54
|
+
// Preset rules
|
|
55
|
+
exports.RULES_PRESETS = {
|
|
56
|
+
default: {
|
|
57
|
+
name: 'Default (No theme)',
|
|
58
|
+
rules: '',
|
|
59
|
+
},
|
|
60
|
+
fantasy: {
|
|
61
|
+
name: 'Fantasy RPG',
|
|
62
|
+
rules: 'Use fantasy RPG language. Tasks are "quests", completing them is "slaying". Major milestones are "boss battles". Use terms like "adventurer", "dungeon", "loot", "guild". Add flavor text with swords, dragons, magic.',
|
|
63
|
+
},
|
|
64
|
+
space: {
|
|
65
|
+
name: 'Space Opera',
|
|
66
|
+
rules: 'Use sci-fi space language. Tasks are "missions", completing them is "mission accomplished". Major milestones are "final frontier". Use terms like "commander", "starship", "coordinates", "hyperdrive". Add flavor with stars, planets, aliens.',
|
|
67
|
+
},
|
|
68
|
+
starwars: {
|
|
69
|
+
name: 'Star Wars',
|
|
70
|
+
rules: 'Use Star Wars language. Tasks are "missions from the Rebel Alliance". Completing is "defeating the Empire". Milestones are "destroying the Death Star". Use "Jedi", "Force", "Padawan", "Master". May the Force be with you.',
|
|
71
|
+
},
|
|
72
|
+
western: {
|
|
73
|
+
name: 'Wild West',
|
|
74
|
+
rules: 'Use Wild West language. Tasks are "bounties", completing them is "collecting the reward". Milestones are "showdowns". Use terms like "sheriff", "outlaw", "saloon", "frontier", "partner". Add dusty trails and tumbleweeds.',
|
|
75
|
+
},
|
|
76
|
+
cyberpunk: {
|
|
77
|
+
name: 'Cyberpunk',
|
|
78
|
+
rules: 'Use cyberpunk language. Tasks are "gigs", completing them is "flatlined". Milestones are "megacorp takedowns". Use terms like "netrunner", "chrome", "corpo", "edgerunner", "eddies". Add neon and rain.',
|
|
79
|
+
},
|
|
80
|
+
pirate: {
|
|
81
|
+
name: 'Pirate',
|
|
82
|
+
rules: 'Use pirate language. Tasks are "plunder", completing them is "claiming the treasure". Milestones are "capturing the flagship". Use "captain", "crew", "booty", "seven seas", "landlubber". Arr matey!',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
43
85
|
const CONFIG_FILE = path.join(os.homedir(), '.rlc', 'config.json');
|
|
44
86
|
const DEFAULT_STORAGE = path.join(os.homedir(), '.rlc', 'workspace');
|
|
45
87
|
async function initConfig() {
|
|
@@ -478,10 +478,10 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
478
478
|
output += '\n';
|
|
479
479
|
return wrapResult({ output });
|
|
480
480
|
}
|
|
481
|
-
// Deadline command
|
|
482
|
-
if (command === 'deadline') {
|
|
481
|
+
// Deadline command (dl as alias)
|
|
482
|
+
if (command === 'deadline' || command === 'dl') {
|
|
483
483
|
if (parts.length < 2) {
|
|
484
|
-
return wrapResult({ output: 'Usage: deadline <date
|
|
484
|
+
return wrapResult({ output: 'Usage: deadline <date> (or dl <date>)\nExamples: dl today, dl +3d, deadline Jan 15' });
|
|
485
485
|
}
|
|
486
486
|
const dateStr = parts.slice(1).join(' ');
|
|
487
487
|
const deadline = parseDeadline(dateStr);
|
|
@@ -931,36 +931,55 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
931
931
|
return wrapResult({ output: currentPath });
|
|
932
932
|
}
|
|
933
933
|
if (command === 'config') {
|
|
934
|
+
const { updateConfig, SUPPORTED_MODELS } = await Promise.resolve().then(() => __importStar(require('../config/config')));
|
|
935
|
+
// Check for flags (uppercase short, lowercase long)
|
|
936
|
+
const keyFlag = parts.find(p => p.startsWith('-K=') || p.startsWith('--key='));
|
|
937
|
+
const modelFlag = parts.find(p => p.startsWith('-M=') || p.startsWith('--model='));
|
|
938
|
+
const rulesFlag = parts.find(p => p.startsWith('-R=') || p.startsWith('--rules='));
|
|
939
|
+
if (keyFlag) {
|
|
940
|
+
const value = keyFlag.split('=').slice(1).join('=');
|
|
941
|
+
if (!value) {
|
|
942
|
+
return wrapResult({ output: 'Error: API key cannot be empty' });
|
|
943
|
+
}
|
|
944
|
+
updateConfig({ apiKey: value });
|
|
945
|
+
return wrapResult({ output: 'API key updated.' });
|
|
946
|
+
}
|
|
947
|
+
if (modelFlag) {
|
|
948
|
+
const value = modelFlag.split('=').slice(1).join('=');
|
|
949
|
+
if (!SUPPORTED_MODELS.includes(value)) {
|
|
950
|
+
return wrapResult({
|
|
951
|
+
output: `Error: Unknown model "${value}"\n\nSupported models:\n ${SUPPORTED_MODELS.join('\n ')}`
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
updateConfig({ model: value });
|
|
955
|
+
return wrapResult({ output: `Model updated: ${value}` });
|
|
956
|
+
}
|
|
957
|
+
if (rulesFlag) {
|
|
958
|
+
const value = rulesFlag.split('=').slice(1).join('=');
|
|
959
|
+
updateConfig({ rules: value, rulesPreset: 'custom' });
|
|
960
|
+
return wrapResult({ output: 'Rules updated.' });
|
|
961
|
+
}
|
|
962
|
+
// Show config
|
|
934
963
|
const maskedKey = config.apiKey
|
|
935
964
|
? config.apiKey.slice(0, 8) + '...' + config.apiKey.slice(-4)
|
|
936
965
|
: '(not set)';
|
|
966
|
+
const rulesPreview = config.rules
|
|
967
|
+
? (config.rules.length > 60 ? config.rules.substring(0, 60) + '...' : config.rules)
|
|
968
|
+
: '(default)';
|
|
937
969
|
const output = `
|
|
938
970
|
Provider: ${config.aiProvider}
|
|
939
971
|
Model: ${config.model || '(default)'}
|
|
940
972
|
API Key: ${maskedKey}
|
|
941
973
|
Storage: ${config.storagePath}
|
|
974
|
+
Rules: ${rulesPreview}
|
|
975
|
+
|
|
976
|
+
Set with flags:
|
|
977
|
+
config -K=<key> or --key=<key>
|
|
978
|
+
config -M=<model> or --model=<model>
|
|
979
|
+
config -R="<rules>" or --rules="<rules>"
|
|
942
980
|
`.trim();
|
|
943
981
|
return wrapResult({ output });
|
|
944
982
|
}
|
|
945
|
-
if (command.startsWith('config:')) {
|
|
946
|
-
const configParts = input.split(':').slice(1).join(':').trim().split('=');
|
|
947
|
-
if (configParts.length !== 2) {
|
|
948
|
-
return { output: 'Usage: config:key=value' };
|
|
949
|
-
}
|
|
950
|
-
const key = configParts[0].trim();
|
|
951
|
-
const value = configParts[1].trim();
|
|
952
|
-
if (key === 'apiKey') {
|
|
953
|
-
const { updateConfig } = await Promise.resolve().then(() => __importStar(require('../config/config')));
|
|
954
|
-
updateConfig({ apiKey: value });
|
|
955
|
-
return { output: 'API key updated.' };
|
|
956
|
-
}
|
|
957
|
-
if (key === 'storagePath') {
|
|
958
|
-
const { updateConfig } = await Promise.resolve().then(() => __importStar(require('../config/config')));
|
|
959
|
-
updateConfig({ storagePath: value, currentPath: value });
|
|
960
|
-
return { output: `Storage path updated to: ${value}` };
|
|
961
|
-
}
|
|
962
|
-
return { output: `Unknown config key: ${key}` };
|
|
963
|
-
}
|
|
964
983
|
if (command === 'help') {
|
|
965
984
|
return wrapResult({
|
|
966
985
|
output: `
|
|
@@ -968,59 +987,52 @@ Storage: ${config.storagePath}
|
|
|
968
987
|
|
|
969
988
|
Navigation:
|
|
970
989
|
ls List tasks and files
|
|
971
|
-
tree
|
|
972
|
-
tree -A Include files
|
|
973
|
-
tree --depth=N Limit tree depth
|
|
990
|
+
tree [-A] [--depth=N] Show task tree
|
|
974
991
|
cd <task> Navigate into task
|
|
975
|
-
|
|
976
|
-
pwd
|
|
977
|
-
open Open
|
|
978
|
-
|
|
979
|
-
Task Management:
|
|
980
|
-
mkdir <name> Create new task
|
|
981
|
-
done Mark current task as completed (recursive)
|
|
982
|
-
undo Undo last done (restores XP)
|
|
983
|
-
deadline <date> Set deadline (today, tomorrow, +3d, Jan 15)
|
|
984
|
-
boss Toggle boss/milestone status (3x XP)
|
|
985
|
-
block [node] Block by task (or text reason)
|
|
986
|
-
unblock Remove blocked status
|
|
987
|
-
status Show current task details
|
|
988
|
-
check Show overdue/upcoming deadlines
|
|
992
|
+
.., ... Go up 1 or 2 levels
|
|
993
|
+
pwd Current path
|
|
994
|
+
open Open in Finder
|
|
989
995
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
996
|
+
Tasks:
|
|
997
|
+
mkdir <name> Create task
|
|
998
|
+
done Complete (earns XP)
|
|
999
|
+
undo Undo last done
|
|
1000
|
+
dl <date> Set deadline (dl +3d, dl Jan 15)
|
|
1001
|
+
boss Toggle boss (3x XP)
|
|
1002
|
+
block [node] Block by task
|
|
1003
|
+
unblock Remove block
|
|
1004
|
+
status Task details
|
|
1005
|
+
check Deadline alerts
|
|
995
1006
|
|
|
996
1007
|
Gamification:
|
|
997
|
-
stats
|
|
998
|
-
achievements
|
|
999
|
-
map Dungeon map
|
|
1000
|
-
map --ai AI-generated
|
|
1008
|
+
stats XP, level, streaks
|
|
1009
|
+
achievements Achievement list
|
|
1010
|
+
map Dungeon map
|
|
1011
|
+
map --ai AI-generated map
|
|
1001
1012
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
cancel Discard pending schema
|
|
1013
|
+
Rules (AI style presets):
|
|
1014
|
+
Set via init or config -R="<rules>"
|
|
1015
|
+
Presets: fantasy, space, starwars, western, cyberpunk, pirate
|
|
1006
1016
|
|
|
1007
|
-
|
|
1017
|
+
Config:
|
|
1008
1018
|
init Setup wizard
|
|
1009
1019
|
config Show settings
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
exit, quit Exit
|
|
1020
|
+
config -K=<key> or --key=<key>
|
|
1021
|
+
config -M=<model> or --model=<model>
|
|
1022
|
+
config -R="<rules>" or --rules="<rules>"
|
|
1014
1023
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1024
|
+
Files:
|
|
1025
|
+
cp, mv, rm [-rf] Standard operations
|
|
1026
|
+
clean --yes Clear folder
|
|
1027
|
+
|
|
1028
|
+
AI:
|
|
1029
|
+
<description> Generate preview
|
|
1030
|
+
save Save to folders
|
|
1031
|
+
cancel Discard
|
|
1018
1032
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
deadline +3d Due in 3 days
|
|
1023
|
-
check See all upcoming deadlines
|
|
1033
|
+
Clipboard:
|
|
1034
|
+
<cmd> | pbcopy macOS
|
|
1035
|
+
<cmd> | clip Windows
|
|
1024
1036
|
|
|
1025
1037
|
www.rlc.rocks
|
|
1026
1038
|
`.trim()
|
|
@@ -19,15 +19,16 @@ const ASCII_ART = [
|
|
|
19
19
|
'║ Roguelike CLI ║',
|
|
20
20
|
'╚═════════════════════════╝',
|
|
21
21
|
'',
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
' Gamification: stats, achievements, map, check',
|
|
22
|
+
' Tasks: done, undo, dl <date>, boss, block',
|
|
23
|
+
' Stats: stats, achievements, map --ai',
|
|
25
24
|
'',
|
|
26
|
-
'
|
|
27
|
-
'
|
|
25
|
+
' Rules: fantasy, space, starwars, cyberpunk',
|
|
26
|
+
' Config: init, config -R="<rules>"',
|
|
28
27
|
'',
|
|
29
|
-
'
|
|
28
|
+
' TAB autocomplete, | pbcopy to copy',
|
|
29
|
+
' <description> -> refine -> save',
|
|
30
30
|
'',
|
|
31
|
+
' help - commands',
|
|
31
32
|
' www.rlc.rocks',
|
|
32
33
|
'',
|
|
33
34
|
' Ready...',
|
package/package.json
CHANGED
package/src/ai/claude.ts
CHANGED
|
@@ -60,7 +60,7 @@ export async function generateSchemaWithAI(
|
|
|
60
60
|
history?: ConversationMessage[]
|
|
61
61
|
): Promise<GeneratedSchema | null> {
|
|
62
62
|
if (!config.apiKey) {
|
|
63
|
-
throw new Error('API key not set. Use config
|
|
63
|
+
throw new Error('API key not set. Use config -k=<key> to set it.');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const client = new Anthropic({
|
|
@@ -87,10 +87,17 @@ export async function generateSchemaWithAI(
|
|
|
87
87
|
|
|
88
88
|
try {
|
|
89
89
|
const model = config.model || 'claude-sonnet-4-20250514';
|
|
90
|
+
|
|
91
|
+
// Build system prompt with custom rules
|
|
92
|
+
let systemPrompt = SYSTEM_PROMPT;
|
|
93
|
+
if (config.rules) {
|
|
94
|
+
systemPrompt += '\n\nADDITIONAL STYLE RULES (apply to all responses):\n' + config.rules;
|
|
95
|
+
}
|
|
96
|
+
|
|
90
97
|
const message = await client.messages.create({
|
|
91
98
|
model: model,
|
|
92
99
|
max_tokens: 2000,
|
|
93
|
-
system:
|
|
100
|
+
system: systemPrompt,
|
|
94
101
|
messages: messages,
|
|
95
102
|
});
|
|
96
103
|
|
|
@@ -126,7 +133,7 @@ export async function generateDungeonMapWithAI(
|
|
|
126
133
|
signal?: AbortSignal
|
|
127
134
|
): Promise<string | null> {
|
|
128
135
|
if (!config.apiKey) {
|
|
129
|
-
throw new Error('API key not set. Use config
|
|
136
|
+
throw new Error('API key not set. Use config -k=<key> to set it.');
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
const client = new Anthropic({
|
|
@@ -135,10 +142,17 @@ export async function generateDungeonMapWithAI(
|
|
|
135
142
|
|
|
136
143
|
try {
|
|
137
144
|
const model = config.model || 'claude-sonnet-4-20250514';
|
|
145
|
+
|
|
146
|
+
// Build system prompt with custom rules
|
|
147
|
+
let systemPrompt = DUNGEON_MAP_PROMPT;
|
|
148
|
+
if (config.rules) {
|
|
149
|
+
systemPrompt += '\n\nADDITIONAL STYLE RULES:\n' + config.rules;
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
const message = await client.messages.create({
|
|
139
153
|
model: model,
|
|
140
154
|
max_tokens: 2000,
|
|
141
|
-
system:
|
|
155
|
+
system: systemPrompt,
|
|
142
156
|
messages: [{
|
|
143
157
|
role: 'user',
|
|
144
158
|
content: 'Generate a dungeon map for this task tree:\n\n' + treeContent
|
package/src/commands/init.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as readline from 'readline';
|
|
5
|
-
import { Config, saveConfig, initConfig } from '../config/config';
|
|
5
|
+
import { Config, saveConfig, initConfig, RULES_PRESETS } from '../config/config';
|
|
6
6
|
|
|
7
7
|
function question(rl: readline.Interface, query: string): Promise<string> {
|
|
8
8
|
return new Promise((resolve) => {
|
|
@@ -31,7 +31,6 @@ function copyRecursive(src: string, dest: string): void {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export async function initCommand(existingRl?: readline.Interface): Promise<void> {
|
|
34
|
-
// Create our own readline if not provided, or use existing one
|
|
35
34
|
const rl = existingRl || readline.createInterface({
|
|
36
35
|
input: process.stdin,
|
|
37
36
|
output: process.stdout,
|
|
@@ -44,7 +43,6 @@ export async function initCommand(existingRl?: readline.Interface): Promise<void
|
|
|
44
43
|
console.log('║ ROGUELIKE CLI INITIALIZATION WIZARD ║');
|
|
45
44
|
console.log('╚═══════════════════════════════════════╝\n');
|
|
46
45
|
|
|
47
|
-
// Get existing config if any
|
|
48
46
|
const existingConfig = await initConfig();
|
|
49
47
|
const oldStoragePath = existingConfig?.storagePath;
|
|
50
48
|
|
|
@@ -101,7 +99,7 @@ export async function initCommand(existingRl?: readline.Interface): Promise<void
|
|
|
101
99
|
|
|
102
100
|
console.log(`Selected: ${selectedProvider.name} (${selectedProvider.model})`);
|
|
103
101
|
|
|
104
|
-
// 3. API Key
|
|
102
|
+
// 3. API Key
|
|
105
103
|
const existingApiKey = existingConfig?.apiKey || '';
|
|
106
104
|
const hasExistingKey = existingApiKey.length > 0;
|
|
107
105
|
const keyPrompt = hasExistingKey
|
|
@@ -112,13 +110,49 @@ export async function initCommand(existingRl?: readline.Interface): Promise<void
|
|
|
112
110
|
const apiKey = apiKeyInput.trim() || existingApiKey;
|
|
113
111
|
|
|
114
112
|
if (!apiKey) {
|
|
115
|
-
console.log('Warning: API key not set. You can set it later with config
|
|
113
|
+
console.log('Warning: API key not set. You can set it later with: config -K=<key>');
|
|
116
114
|
} else if (apiKeyInput.trim()) {
|
|
117
115
|
console.log('API key saved');
|
|
118
116
|
} else {
|
|
119
117
|
console.log('Using existing API key');
|
|
120
118
|
}
|
|
121
119
|
|
|
120
|
+
// 4. Rules preset selection
|
|
121
|
+
console.log('\nSelect AI Rules (affects language style):');
|
|
122
|
+
const presetKeys = Object.keys(RULES_PRESETS);
|
|
123
|
+
presetKeys.forEach((key, index) => {
|
|
124
|
+
console.log(` ${index + 1}. ${RULES_PRESETS[key].name}`);
|
|
125
|
+
});
|
|
126
|
+
console.log(` ${presetKeys.length + 1}. Custom (enter your own rules)`);
|
|
127
|
+
|
|
128
|
+
const existingPreset = existingConfig?.rulesPreset || 'default';
|
|
129
|
+
const defaultPresetIndex = presetKeys.indexOf(existingPreset) + 1 || 1;
|
|
130
|
+
|
|
131
|
+
const themeChoice = await question(rl, `\nEnter choice [1-${presetKeys.length + 1}] (default: ${defaultPresetIndex}): `);
|
|
132
|
+
const themeIndex = parseInt(themeChoice.trim()) - 1;
|
|
133
|
+
|
|
134
|
+
let selectedRules = '';
|
|
135
|
+
let selectedPreset = 'default';
|
|
136
|
+
|
|
137
|
+
if (themeIndex >= 0 && themeIndex < presetKeys.length) {
|
|
138
|
+
selectedPreset = presetKeys[themeIndex];
|
|
139
|
+
selectedRules = RULES_PRESETS[selectedPreset].rules;
|
|
140
|
+
console.log(`Selected: ${RULES_PRESETS[selectedPreset].name}`);
|
|
141
|
+
} else if (themeIndex === presetKeys.length) {
|
|
142
|
+
// Custom rules
|
|
143
|
+
console.log('\nEnter your custom rules for AI (how it should speak, what terms to use):');
|
|
144
|
+
console.log('Example: "Use pirate language. Tasks are treasure hunts. Be playful."');
|
|
145
|
+
const customRules = await question(rl, '\nYour rules: ');
|
|
146
|
+
selectedRules = customRules.trim();
|
|
147
|
+
selectedPreset = 'custom';
|
|
148
|
+
console.log('Custom rules saved');
|
|
149
|
+
} else {
|
|
150
|
+
// Default
|
|
151
|
+
selectedPreset = existingConfig?.rulesPreset || 'default';
|
|
152
|
+
selectedRules = existingConfig?.rules || '';
|
|
153
|
+
console.log(`Keeping: ${RULES_PRESETS[selectedPreset]?.name || 'Default'}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
122
156
|
// Save config
|
|
123
157
|
const config: Config = {
|
|
124
158
|
aiProvider: selectedProvider.name as any,
|
|
@@ -127,11 +161,12 @@ export async function initCommand(existingRl?: readline.Interface): Promise<void
|
|
|
127
161
|
storagePath: rootDir,
|
|
128
162
|
currentPath: rootDir,
|
|
129
163
|
model: selectedProvider.model,
|
|
164
|
+
rules: selectedRules,
|
|
165
|
+
rulesPreset: selectedPreset,
|
|
130
166
|
};
|
|
131
167
|
|
|
132
168
|
saveConfig(config);
|
|
133
169
|
|
|
134
|
-
// Ensure storage directory exists
|
|
135
170
|
if (!fs.existsSync(rootDir)) {
|
|
136
171
|
fs.mkdirSync(rootDir, { recursive: true });
|
|
137
172
|
}
|
|
@@ -141,7 +176,8 @@ export async function initCommand(existingRl?: readline.Interface): Promise<void
|
|
|
141
176
|
console.log('╚═══════════════════════════════════════╝\n');
|
|
142
177
|
console.log(`Root directory: ${rootDir}`);
|
|
143
178
|
console.log(`AI Provider: ${selectedProvider.name}`);
|
|
144
|
-
console.log(`Model: ${selectedProvider.model}
|
|
179
|
+
console.log(`Model: ${selectedProvider.model}`);
|
|
180
|
+
console.log(`Rules: ${RULES_PRESETS[selectedPreset]?.name || 'Custom'}\n`);
|
|
145
181
|
} finally {
|
|
146
182
|
if (shouldCloseRl) {
|
|
147
183
|
rl.close();
|
package/src/config/config.ts
CHANGED
|
@@ -9,8 +9,53 @@ export interface Config {
|
|
|
9
9
|
storagePath: string;
|
|
10
10
|
currentPath: string;
|
|
11
11
|
model?: string;
|
|
12
|
+
rules?: string;
|
|
13
|
+
rulesPreset?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
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
|
|
28
|
+
export const RULES_PRESETS: Record<string, { name: string; rules: string }> = {
|
|
29
|
+
default: {
|
|
30
|
+
name: 'Default (No theme)',
|
|
31
|
+
rules: '',
|
|
32
|
+
},
|
|
33
|
+
fantasy: {
|
|
34
|
+
name: 'Fantasy RPG',
|
|
35
|
+
rules: 'Use fantasy RPG language. Tasks are "quests", completing them is "slaying". Major milestones are "boss battles". Use terms like "adventurer", "dungeon", "loot", "guild". Add flavor text with swords, dragons, magic.',
|
|
36
|
+
},
|
|
37
|
+
space: {
|
|
38
|
+
name: 'Space Opera',
|
|
39
|
+
rules: 'Use sci-fi space language. Tasks are "missions", completing them is "mission accomplished". Major milestones are "final frontier". Use terms like "commander", "starship", "coordinates", "hyperdrive". Add flavor with stars, planets, aliens.',
|
|
40
|
+
},
|
|
41
|
+
starwars: {
|
|
42
|
+
name: 'Star Wars',
|
|
43
|
+
rules: 'Use Star Wars language. Tasks are "missions from the Rebel Alliance". Completing is "defeating the Empire". Milestones are "destroying the Death Star". Use "Jedi", "Force", "Padawan", "Master". May the Force be with you.',
|
|
44
|
+
},
|
|
45
|
+
western: {
|
|
46
|
+
name: 'Wild West',
|
|
47
|
+
rules: 'Use Wild West language. Tasks are "bounties", completing them is "collecting the reward". Milestones are "showdowns". Use terms like "sheriff", "outlaw", "saloon", "frontier", "partner". Add dusty trails and tumbleweeds.',
|
|
48
|
+
},
|
|
49
|
+
cyberpunk: {
|
|
50
|
+
name: 'Cyberpunk',
|
|
51
|
+
rules: 'Use cyberpunk language. Tasks are "gigs", completing them is "flatlined". Milestones are "megacorp takedowns". Use terms like "netrunner", "chrome", "corpo", "edgerunner", "eddies". Add neon and rain.',
|
|
52
|
+
},
|
|
53
|
+
pirate: {
|
|
54
|
+
name: 'Pirate',
|
|
55
|
+
rules: 'Use pirate language. Tasks are "plunder", completing them is "claiming the treasure". Milestones are "capturing the flagship". Use "captain", "crew", "booty", "seven seas", "landlubber". Arr matey!',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
14
59
|
const CONFIG_FILE = path.join(os.homedir(), '.rlc', 'config.json');
|
|
15
60
|
const DEFAULT_STORAGE = path.join(os.homedir(), '.rlc', 'workspace');
|
|
16
61
|
|
|
@@ -566,10 +566,10 @@ export async function processCommand(
|
|
|
566
566
|
return wrapResult({ output });
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
-
// Deadline command
|
|
570
|
-
if (command === 'deadline') {
|
|
569
|
+
// Deadline command (dl as alias)
|
|
570
|
+
if (command === 'deadline' || command === 'dl') {
|
|
571
571
|
if (parts.length < 2) {
|
|
572
|
-
return wrapResult({ output: 'Usage: deadline <date
|
|
572
|
+
return wrapResult({ output: 'Usage: deadline <date> (or dl <date>)\nExamples: dl today, dl +3d, deadline Jan 15' });
|
|
573
573
|
}
|
|
574
574
|
|
|
575
575
|
const dateStr = parts.slice(1).join(' ');
|
|
@@ -1107,44 +1107,64 @@ export async function processCommand(
|
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
1109
|
if (command === 'config') {
|
|
1110
|
+
const { updateConfig, SUPPORTED_MODELS } = await import('../config/config');
|
|
1111
|
+
|
|
1112
|
+
// Check for flags (uppercase short, lowercase long)
|
|
1113
|
+
const keyFlag = parts.find(p => p.startsWith('-K=') || p.startsWith('--key='));
|
|
1114
|
+
const modelFlag = parts.find(p => p.startsWith('-M=') || p.startsWith('--model='));
|
|
1115
|
+
const rulesFlag = parts.find(p => p.startsWith('-R=') || p.startsWith('--rules='));
|
|
1116
|
+
|
|
1117
|
+
if (keyFlag) {
|
|
1118
|
+
const value = keyFlag.split('=').slice(1).join('=');
|
|
1119
|
+
if (!value) {
|
|
1120
|
+
return wrapResult({ output: 'Error: API key cannot be empty' });
|
|
1121
|
+
}
|
|
1122
|
+
updateConfig({ apiKey: value });
|
|
1123
|
+
return wrapResult({ output: 'API key updated.' });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (modelFlag) {
|
|
1127
|
+
const value = modelFlag.split('=').slice(1).join('=');
|
|
1128
|
+
if (!SUPPORTED_MODELS.includes(value)) {
|
|
1129
|
+
return wrapResult({
|
|
1130
|
+
output: `Error: Unknown model "${value}"\n\nSupported models:\n ${SUPPORTED_MODELS.join('\n ')}`
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
updateConfig({ model: value });
|
|
1134
|
+
return wrapResult({ output: `Model updated: ${value}` });
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
if (rulesFlag) {
|
|
1138
|
+
const value = rulesFlag.split('=').slice(1).join('=');
|
|
1139
|
+
updateConfig({ rules: value, rulesPreset: 'custom' });
|
|
1140
|
+
return wrapResult({ output: 'Rules updated.' });
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Show config
|
|
1110
1144
|
const maskedKey = config.apiKey
|
|
1111
1145
|
? config.apiKey.slice(0, 8) + '...' + config.apiKey.slice(-4)
|
|
1112
1146
|
: '(not set)';
|
|
1113
1147
|
|
|
1148
|
+
const rulesPreview = config.rules
|
|
1149
|
+
? (config.rules.length > 60 ? config.rules.substring(0, 60) + '...' : config.rules)
|
|
1150
|
+
: '(default)';
|
|
1151
|
+
|
|
1114
1152
|
const output = `
|
|
1115
1153
|
Provider: ${config.aiProvider}
|
|
1116
1154
|
Model: ${config.model || '(default)'}
|
|
1117
1155
|
API Key: ${maskedKey}
|
|
1118
1156
|
Storage: ${config.storagePath}
|
|
1157
|
+
Rules: ${rulesPreview}
|
|
1158
|
+
|
|
1159
|
+
Set with flags:
|
|
1160
|
+
config -K=<key> or --key=<key>
|
|
1161
|
+
config -M=<model> or --model=<model>
|
|
1162
|
+
config -R="<rules>" or --rules="<rules>"
|
|
1119
1163
|
`.trim();
|
|
1120
1164
|
|
|
1121
1165
|
return wrapResult({ output });
|
|
1122
1166
|
}
|
|
1123
1167
|
|
|
1124
|
-
if (command.startsWith('config:')) {
|
|
1125
|
-
const configParts = input.split(':').slice(1).join(':').trim().split('=');
|
|
1126
|
-
if (configParts.length !== 2) {
|
|
1127
|
-
return { output: 'Usage: config:key=value' };
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
const key = configParts[0].trim();
|
|
1131
|
-
const value = configParts[1].trim();
|
|
1132
|
-
|
|
1133
|
-
if (key === 'apiKey') {
|
|
1134
|
-
const { updateConfig } = await import('../config/config');
|
|
1135
|
-
updateConfig({ apiKey: value });
|
|
1136
|
-
return { output: 'API key updated.' };
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
if (key === 'storagePath') {
|
|
1140
|
-
const { updateConfig } = await import('../config/config');
|
|
1141
|
-
updateConfig({ storagePath: value, currentPath: value });
|
|
1142
|
-
return { output: `Storage path updated to: ${value}` };
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
return { output: `Unknown config key: ${key}` };
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
1168
|
if (command === 'help') {
|
|
1149
1169
|
return wrapResult({
|
|
1150
1170
|
output: `
|
|
@@ -1152,59 +1172,52 @@ Storage: ${config.storagePath}
|
|
|
1152
1172
|
|
|
1153
1173
|
Navigation:
|
|
1154
1174
|
ls List tasks and files
|
|
1155
|
-
tree
|
|
1156
|
-
tree -A Include files
|
|
1157
|
-
tree --depth=N Limit tree depth
|
|
1175
|
+
tree [-A] [--depth=N] Show task tree
|
|
1158
1176
|
cd <task> Navigate into task
|
|
1159
|
-
|
|
1160
|
-
pwd
|
|
1161
|
-
open Open
|
|
1162
|
-
|
|
1163
|
-
Task Management:
|
|
1164
|
-
mkdir <name> Create new task
|
|
1165
|
-
done Mark current task as completed (recursive)
|
|
1166
|
-
undo Undo last done (restores XP)
|
|
1167
|
-
deadline <date> Set deadline (today, tomorrow, +3d, Jan 15)
|
|
1168
|
-
boss Toggle boss/milestone status (3x XP)
|
|
1169
|
-
block [node] Block by task (or text reason)
|
|
1170
|
-
unblock Remove blocked status
|
|
1171
|
-
status Show current task details
|
|
1172
|
-
check Show overdue/upcoming deadlines
|
|
1177
|
+
.., ... Go up 1 or 2 levels
|
|
1178
|
+
pwd Current path
|
|
1179
|
+
open Open in Finder
|
|
1173
1180
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1181
|
+
Tasks:
|
|
1182
|
+
mkdir <name> Create task
|
|
1183
|
+
done Complete (earns XP)
|
|
1184
|
+
undo Undo last done
|
|
1185
|
+
dl <date> Set deadline (dl +3d, dl Jan 15)
|
|
1186
|
+
boss Toggle boss (3x XP)
|
|
1187
|
+
block [node] Block by task
|
|
1188
|
+
unblock Remove block
|
|
1189
|
+
status Task details
|
|
1190
|
+
check Deadline alerts
|
|
1179
1191
|
|
|
1180
1192
|
Gamification:
|
|
1181
|
-
stats
|
|
1182
|
-
achievements
|
|
1183
|
-
map Dungeon map
|
|
1184
|
-
map --ai AI-generated
|
|
1193
|
+
stats XP, level, streaks
|
|
1194
|
+
achievements Achievement list
|
|
1195
|
+
map Dungeon map
|
|
1196
|
+
map --ai AI-generated map
|
|
1185
1197
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
cancel Discard pending schema
|
|
1198
|
+
Rules (AI style presets):
|
|
1199
|
+
Set via init or config -R="<rules>"
|
|
1200
|
+
Presets: fantasy, space, starwars, western, cyberpunk, pirate
|
|
1190
1201
|
|
|
1191
|
-
|
|
1202
|
+
Config:
|
|
1192
1203
|
init Setup wizard
|
|
1193
1204
|
config Show settings
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
exit, quit Exit
|
|
1205
|
+
config -K=<key> or --key=<key>
|
|
1206
|
+
config -M=<model> or --model=<model>
|
|
1207
|
+
config -R="<rules>" or --rules="<rules>"
|
|
1198
1208
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1209
|
+
Files:
|
|
1210
|
+
cp, mv, rm [-rf] Standard operations
|
|
1211
|
+
clean --yes Clear folder
|
|
1212
|
+
|
|
1213
|
+
AI:
|
|
1214
|
+
<description> Generate preview
|
|
1215
|
+
save Save to folders
|
|
1216
|
+
cancel Discard
|
|
1202
1217
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
deadline +3d Due in 3 days
|
|
1207
|
-
check See all upcoming deadlines
|
|
1218
|
+
Clipboard:
|
|
1219
|
+
<cmd> | pbcopy macOS
|
|
1220
|
+
<cmd> | clip Windows
|
|
1208
1221
|
|
|
1209
1222
|
www.rlc.rocks
|
|
1210
1223
|
`.trim()
|
|
@@ -17,15 +17,16 @@ const ASCII_ART = [
|
|
|
17
17
|
'║ Roguelike CLI ║',
|
|
18
18
|
'╚═════════════════════════╝',
|
|
19
19
|
'',
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
' Gamification: stats, achievements, map, check',
|
|
20
|
+
' Tasks: done, undo, dl <date>, boss, block',
|
|
21
|
+
' Stats: stats, achievements, map --ai',
|
|
23
22
|
'',
|
|
24
|
-
'
|
|
25
|
-
'
|
|
23
|
+
' Rules: fantasy, space, starwars, cyberpunk',
|
|
24
|
+
' Config: init, config -R="<rules>"',
|
|
26
25
|
'',
|
|
27
|
-
'
|
|
26
|
+
' TAB autocomplete, | pbcopy to copy',
|
|
27
|
+
' <description> -> refine -> save',
|
|
28
28
|
'',
|
|
29
|
+
' help - commands',
|
|
29
30
|
' www.rlc.rocks',
|
|
30
31
|
'',
|
|
31
32
|
' Ready...',
|