roguelike-cli 1.2.5 → 1.3.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.
- package/README.md +154 -208
- package/dist/ai/claude.js +53 -42
- package/dist/interactive/commands.js +417 -189
- package/dist/interactive/startup.js +6 -3
- package/dist/storage/nodeConfig.js +35 -1
- package/dist/storage/profile.js +276 -0
- package/package.json +1 -1
- package/src/ai/claude.ts +60 -43
- package/src/interactive/commands.ts +512 -196
- package/src/interactive/startup.ts +6 -3
- package/src/storage/nodeConfig.ts +48 -2
- package/src/storage/profile.ts +281 -0
|
@@ -41,60 +41,60 @@ const child_process_1 = require("child_process");
|
|
|
41
41
|
const storage_1 = require("../storage/storage");
|
|
42
42
|
const nodeConfig_1 = require("../storage/nodeConfig");
|
|
43
43
|
const claude_1 = require("../ai/claude");
|
|
44
|
+
const profile_1 = require("../storage/profile");
|
|
44
45
|
// Parse tree ASCII art and create folder structure
|
|
45
46
|
function createFoldersFromTree(rootPath, treeContent) {
|
|
46
|
-
// Create root folder
|
|
47
47
|
if (!fs.existsSync(rootPath)) {
|
|
48
48
|
fs.mkdirSync(rootPath, { recursive: true });
|
|
49
49
|
}
|
|
50
|
-
// Parse tree lines
|
|
51
50
|
const lines = treeContent.split('\n');
|
|
52
51
|
const stack = [{ path: rootPath, indent: -1 }];
|
|
53
52
|
for (const line of lines) {
|
|
54
|
-
// Skip empty lines
|
|
55
53
|
if (!line.trim())
|
|
56
54
|
continue;
|
|
57
|
-
// Extract node name from tree line
|
|
58
|
-
// Patterns: "├── Name", "└── Name", "│ ├── Name", etc.
|
|
59
55
|
const match = line.match(/^([\s│]*)[├└]──\s*(.+)$/);
|
|
60
56
|
if (!match)
|
|
61
57
|
continue;
|
|
62
58
|
const prefix = match[1];
|
|
63
59
|
let nodeName = match[2].trim();
|
|
64
|
-
// Calculate indent level (each │ or space block = 1 level)
|
|
65
60
|
const indent = Math.floor(prefix.replace(/│/g, ' ').length / 4);
|
|
66
|
-
//
|
|
61
|
+
// Extract metadata from node name
|
|
62
|
+
const isBoss = /\[BOSS\]/i.test(nodeName) || /\[MILESTONE\]/i.test(nodeName);
|
|
63
|
+
const deadlineMatch = nodeName.match(/\[(?:DUE|DEADLINE):\s*([^\]]+)\]/i);
|
|
64
|
+
const deadline = deadlineMatch ? deadlineMatch[1].trim() : undefined;
|
|
65
|
+
// Clean node name
|
|
67
66
|
nodeName = nodeName.replace(/\s*\([^)]*\)\s*/g, '').trim();
|
|
68
67
|
nodeName = nodeName.replace(/\s*\[[^\]]*\]\s*/g, '').trim();
|
|
69
|
-
// Create safe folder name
|
|
70
68
|
const safeName = nodeName
|
|
71
69
|
.toLowerCase()
|
|
72
70
|
.replace(/[^a-z0-9]+/g, '-')
|
|
73
71
|
.replace(/^-+|-+$/g, '');
|
|
74
72
|
if (!safeName)
|
|
75
73
|
continue;
|
|
76
|
-
// Pop stack until we find parent
|
|
77
74
|
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
78
75
|
stack.pop();
|
|
79
76
|
}
|
|
80
77
|
const parentPath = stack[stack.length - 1].path;
|
|
81
78
|
const folderPath = path.join(parentPath, safeName);
|
|
82
|
-
// Create folder
|
|
83
79
|
if (!fs.existsSync(folderPath)) {
|
|
84
80
|
fs.mkdirSync(folderPath, { recursive: true });
|
|
85
81
|
}
|
|
86
|
-
//
|
|
82
|
+
// Calculate depth for XP
|
|
83
|
+
const depth = stack.length;
|
|
87
84
|
(0, nodeConfig_1.writeNodeConfig)(folderPath, {
|
|
88
85
|
name: nodeName,
|
|
86
|
+
status: 'open',
|
|
87
|
+
xp: (0, nodeConfig_1.calculateXP)(depth, isBoss),
|
|
88
|
+
isBoss,
|
|
89
|
+
deadline,
|
|
89
90
|
createdAt: new Date().toISOString(),
|
|
90
91
|
updatedAt: new Date().toISOString(),
|
|
91
92
|
});
|
|
92
|
-
// Push to stack
|
|
93
93
|
stack.push({ path: folderPath, indent });
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
// Generate dungeon map visualization from folder structure
|
|
97
|
-
function generateDungeonMap(dirPath) {
|
|
97
|
+
function generateDungeonMap(dirPath, config) {
|
|
98
98
|
if (!fs.existsSync(dirPath)) {
|
|
99
99
|
return 'Directory does not exist.';
|
|
100
100
|
}
|
|
@@ -106,54 +106,64 @@ function generateDungeonMap(dirPath) {
|
|
|
106
106
|
const lines = [];
|
|
107
107
|
const roomWidth = 20;
|
|
108
108
|
const roomsPerRow = 2;
|
|
109
|
-
const wall = '
|
|
109
|
+
const wall = '#';
|
|
110
110
|
const door = '+';
|
|
111
111
|
const task = '*';
|
|
112
112
|
const milestone = '@';
|
|
113
|
-
|
|
113
|
+
const done = 'x';
|
|
114
|
+
const blocked = '!';
|
|
114
115
|
const rows = [];
|
|
115
116
|
for (let i = 0; i < folders.length; i += roomsPerRow) {
|
|
116
117
|
rows.push(folders.slice(i, i + roomsPerRow));
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
+
lines.push('');
|
|
119
120
|
lines.push(' ' + wall.repeat(roomWidth * roomsPerRow + 3));
|
|
120
121
|
rows.forEach((row, rowIndex) => {
|
|
121
|
-
// Room content
|
|
122
122
|
for (let line = 0; line < 6; line++) {
|
|
123
123
|
let rowStr = ' ' + wall;
|
|
124
124
|
row.forEach((folder, colIndex) => {
|
|
125
|
-
const
|
|
125
|
+
const folderPath = path.join(dirPath, folder.name);
|
|
126
|
+
const nodeConfig = (0, nodeConfig_1.readNodeConfig)(folderPath);
|
|
127
|
+
const name = (nodeConfig?.name || folder.name).replace(/-/g, ' ');
|
|
126
128
|
const displayName = name.length > roomWidth - 4
|
|
127
129
|
? name.substring(0, roomWidth - 7) + '...'
|
|
128
130
|
: name;
|
|
129
|
-
// Get sub-items
|
|
130
131
|
const subPath = path.join(dirPath, folder.name);
|
|
131
132
|
const subEntries = fs.existsSync(subPath)
|
|
132
133
|
? fs.readdirSync(subPath, { withFileTypes: true })
|
|
133
134
|
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
134
135
|
: [];
|
|
135
136
|
if (line === 0) {
|
|
136
|
-
// Empty line
|
|
137
137
|
rowStr += ' '.repeat(roomWidth - 1) + wall;
|
|
138
138
|
}
|
|
139
139
|
else if (line === 1) {
|
|
140
|
-
|
|
140
|
+
const statusIcon = nodeConfig?.status === 'done' ? '[DONE]'
|
|
141
|
+
: nodeConfig?.isBoss ? '[BOSS]'
|
|
142
|
+
: '';
|
|
143
|
+
const title = statusIcon ? `${statusIcon}` : `[${displayName}]`;
|
|
144
|
+
const padding = roomWidth - title.length - 1;
|
|
145
|
+
rowStr += ' ' + title + ' '.repeat(Math.max(0, padding - 1)) + wall;
|
|
146
|
+
}
|
|
147
|
+
else if (line === 2 && !nodeConfig?.isBoss) {
|
|
141
148
|
const title = `[${displayName}]`;
|
|
142
149
|
const padding = roomWidth - title.length - 1;
|
|
143
150
|
rowStr += ' ' + title + ' '.repeat(Math.max(0, padding - 1)) + wall;
|
|
144
151
|
}
|
|
145
152
|
else if (line >= 2 && line <= 4) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const subName = subEntries[itemIndex].name.replace(/-/g, ' ');
|
|
153
|
+
const itemIndex = nodeConfig?.isBoss ? line - 2 : line - 3;
|
|
154
|
+
if (itemIndex >= 0 && itemIndex < subEntries.length) {
|
|
155
|
+
const subConfig = (0, nodeConfig_1.readNodeConfig)(path.join(subPath, subEntries[itemIndex].name));
|
|
156
|
+
const subName = (subConfig?.name || subEntries[itemIndex].name).replace(/-/g, ' ');
|
|
150
157
|
const shortName = subName.length > roomWidth - 6
|
|
151
158
|
? subName.substring(0, roomWidth - 9) + '...'
|
|
152
159
|
: subName;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
let marker = task;
|
|
161
|
+
if (subConfig?.status === 'done')
|
|
162
|
+
marker = done;
|
|
163
|
+
else if (subConfig?.status === 'blocked')
|
|
164
|
+
marker = blocked;
|
|
165
|
+
else if (subConfig?.isBoss)
|
|
166
|
+
marker = milestone;
|
|
157
167
|
const itemStr = `${marker} ${shortName}`;
|
|
158
168
|
const itemPadding = roomWidth - itemStr.length - 1;
|
|
159
169
|
rowStr += ' ' + itemStr + ' '.repeat(Math.max(0, itemPadding - 1)) + wall;
|
|
@@ -163,10 +173,8 @@ function generateDungeonMap(dirPath) {
|
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
175
|
else {
|
|
166
|
-
// Empty line
|
|
167
176
|
rowStr += ' '.repeat(roomWidth - 1) + wall;
|
|
168
177
|
}
|
|
169
|
-
// Add door between rooms
|
|
170
178
|
if (colIndex < row.length - 1 && line === 3) {
|
|
171
179
|
rowStr = rowStr.slice(0, -1) + door + door + door;
|
|
172
180
|
}
|
|
@@ -174,21 +182,18 @@ function generateDungeonMap(dirPath) {
|
|
|
174
182
|
rowStr = rowStr.slice(0, -1) + wall;
|
|
175
183
|
}
|
|
176
184
|
});
|
|
177
|
-
// Fill empty space if odd number of rooms
|
|
178
185
|
if (row.length < roomsPerRow) {
|
|
179
186
|
rowStr += ' '.repeat(roomWidth) + wall;
|
|
180
187
|
}
|
|
181
188
|
lines.push(rowStr);
|
|
182
189
|
}
|
|
183
|
-
// Bottom border with doors to next row
|
|
184
190
|
if (rowIndex < rows.length - 1) {
|
|
185
191
|
let borderStr = ' ' + wall.repeat(Math.floor(roomWidth / 2)) + door;
|
|
186
192
|
borderStr += wall.repeat(roomWidth - 1) + door;
|
|
187
193
|
borderStr += wall.repeat(Math.floor(roomWidth / 2) + 1);
|
|
188
194
|
lines.push(borderStr);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
corridorStr += ' '.repeat(roomWidth - 1) + '│';
|
|
195
|
+
let corridorStr = ' ' + ' '.repeat(Math.floor(roomWidth / 2)) + '|';
|
|
196
|
+
corridorStr += ' '.repeat(roomWidth - 1) + '|';
|
|
192
197
|
lines.push(corridorStr);
|
|
193
198
|
borderStr = ' ' + wall.repeat(Math.floor(roomWidth / 2)) + door;
|
|
194
199
|
borderStr += wall.repeat(roomWidth - 1) + door;
|
|
@@ -196,19 +201,94 @@ function generateDungeonMap(dirPath) {
|
|
|
196
201
|
lines.push(borderStr);
|
|
197
202
|
}
|
|
198
203
|
});
|
|
199
|
-
// Bottom border
|
|
200
204
|
lines.push(' ' + wall.repeat(roomWidth * roomsPerRow + 3));
|
|
201
|
-
// Legend
|
|
202
205
|
lines.push('');
|
|
203
|
-
lines.push(`Legend: ${task} Task ${milestone} Milestone ${
|
|
206
|
+
lines.push(`Legend: ${task} Task ${done} Done ${milestone} Boss/Milestone ${blocked} Blocked ${door} Door`);
|
|
204
207
|
return lines.join('\n');
|
|
205
208
|
}
|
|
206
|
-
//
|
|
209
|
+
// Parse human-readable date
|
|
210
|
+
function parseDeadline(input) {
|
|
211
|
+
const lower = input.toLowerCase().trim();
|
|
212
|
+
const today = new Date();
|
|
213
|
+
if (lower === 'today') {
|
|
214
|
+
return today.toISOString().split('T')[0];
|
|
215
|
+
}
|
|
216
|
+
if (lower === 'tomorrow') {
|
|
217
|
+
today.setDate(today.getDate() + 1);
|
|
218
|
+
return today.toISOString().split('T')[0];
|
|
219
|
+
}
|
|
220
|
+
// +Nd format (e.g., +3d, +7d)
|
|
221
|
+
const plusDaysMatch = lower.match(/^\+(\d+)d$/);
|
|
222
|
+
if (plusDaysMatch) {
|
|
223
|
+
today.setDate(today.getDate() + parseInt(plusDaysMatch[1]));
|
|
224
|
+
return today.toISOString().split('T')[0];
|
|
225
|
+
}
|
|
226
|
+
// Try parsing as date
|
|
227
|
+
const parsed = new Date(input);
|
|
228
|
+
if (!isNaN(parsed.getTime())) {
|
|
229
|
+
return parsed.toISOString().split('T')[0];
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
// Format deadline for display
|
|
234
|
+
function formatDeadline(deadline) {
|
|
235
|
+
const deadlineDate = new Date(deadline);
|
|
236
|
+
const today = new Date();
|
|
237
|
+
today.setHours(0, 0, 0, 0);
|
|
238
|
+
deadlineDate.setHours(0, 0, 0, 0);
|
|
239
|
+
const diffDays = Math.ceil((deadlineDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
|
|
240
|
+
if (diffDays < 0)
|
|
241
|
+
return `OVERDUE ${Math.abs(diffDays)}d`;
|
|
242
|
+
if (diffDays === 0)
|
|
243
|
+
return 'TODAY';
|
|
244
|
+
if (diffDays === 1)
|
|
245
|
+
return 'tomorrow';
|
|
246
|
+
if (diffDays <= 7)
|
|
247
|
+
return `${diffDays}d left`;
|
|
248
|
+
return deadlineDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
249
|
+
}
|
|
250
|
+
// Get depth of current path relative to storage root
|
|
251
|
+
function getDepth(currentPath, storagePath) {
|
|
252
|
+
const relative = path.relative(storagePath, currentPath);
|
|
253
|
+
if (!relative)
|
|
254
|
+
return 0;
|
|
255
|
+
return relative.split(path.sep).length;
|
|
256
|
+
}
|
|
257
|
+
// Mark node as done recursively
|
|
258
|
+
function markDoneRecursive(nodePath, storagePath) {
|
|
259
|
+
let result = { xpGained: 0, tasksCompleted: 0, bossesDefeated: 0 };
|
|
260
|
+
const config = (0, nodeConfig_1.readNodeConfig)(nodePath);
|
|
261
|
+
if (!config || config.status === 'done') {
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
// First, mark all children as done
|
|
265
|
+
const entries = fs.readdirSync(nodePath, { withFileTypes: true });
|
|
266
|
+
for (const entry of entries) {
|
|
267
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
268
|
+
const childResult = markDoneRecursive(path.join(nodePath, entry.name), storagePath);
|
|
269
|
+
result.xpGained += childResult.xpGained;
|
|
270
|
+
result.tasksCompleted += childResult.tasksCompleted;
|
|
271
|
+
result.bossesDefeated += childResult.bossesDefeated;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Mark this node as done
|
|
275
|
+
const depth = getDepth(nodePath, storagePath);
|
|
276
|
+
const xp = config.xp || (0, nodeConfig_1.calculateXP)(depth, config.isBoss || false);
|
|
277
|
+
(0, nodeConfig_1.writeNodeConfig)(nodePath, {
|
|
278
|
+
...config,
|
|
279
|
+
status: 'done',
|
|
280
|
+
completedAt: new Date().toISOString(),
|
|
281
|
+
});
|
|
282
|
+
result.xpGained += xp;
|
|
283
|
+
result.tasksCompleted += 1;
|
|
284
|
+
if (config.isBoss)
|
|
285
|
+
result.bossesDefeated += 1;
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
207
288
|
exports.sessionState = {
|
|
208
289
|
pending: null,
|
|
209
290
|
history: []
|
|
210
291
|
};
|
|
211
|
-
// Format items in columns like native ls
|
|
212
292
|
function formatColumns(items, termWidth = 80) {
|
|
213
293
|
if (items.length === 0)
|
|
214
294
|
return '';
|
|
@@ -221,7 +301,6 @@ function formatColumns(items, termWidth = 80) {
|
|
|
221
301
|
}
|
|
222
302
|
return rows.join('\n');
|
|
223
303
|
}
|
|
224
|
-
// Copy to clipboard (cross-platform)
|
|
225
304
|
function copyToClipboard(text) {
|
|
226
305
|
const platform = process.platform;
|
|
227
306
|
try {
|
|
@@ -232,7 +311,6 @@ function copyToClipboard(text) {
|
|
|
232
311
|
(0, child_process_1.execSync)('clip', { input: text });
|
|
233
312
|
}
|
|
234
313
|
else {
|
|
235
|
-
// Linux - try xclip or xsel
|
|
236
314
|
try {
|
|
237
315
|
(0, child_process_1.execSync)('xclip -selection clipboard', { input: text });
|
|
238
316
|
}
|
|
@@ -242,10 +320,9 @@ function copyToClipboard(text) {
|
|
|
242
320
|
}
|
|
243
321
|
}
|
|
244
322
|
catch (e) {
|
|
245
|
-
// Silently fail
|
|
323
|
+
// Silently fail
|
|
246
324
|
}
|
|
247
325
|
}
|
|
248
|
-
// Helper function for recursive copy
|
|
249
326
|
function copyRecursive(src, dest) {
|
|
250
327
|
const stat = fs.statSync(src);
|
|
251
328
|
if (stat.isDirectory()) {
|
|
@@ -263,14 +340,60 @@ function copyRecursive(src, dest) {
|
|
|
263
340
|
fs.copyFileSync(src, dest);
|
|
264
341
|
}
|
|
265
342
|
}
|
|
343
|
+
// Build tree with status and deadline info
|
|
344
|
+
function getTreeWithStatus(dirPath, prefix = '', isRoot = true, maxDepth = 10, currentDepth = 0, showFiles = false) {
|
|
345
|
+
const lines = [];
|
|
346
|
+
if (!fs.existsSync(dirPath)) {
|
|
347
|
+
return lines;
|
|
348
|
+
}
|
|
349
|
+
if (currentDepth >= maxDepth) {
|
|
350
|
+
return lines;
|
|
351
|
+
}
|
|
352
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
353
|
+
const dirs = entries.filter(e => e.isDirectory() && !e.name.startsWith('.'));
|
|
354
|
+
const files = showFiles ? entries.filter(e => e.isFile() && !e.name.startsWith('.')) : [];
|
|
355
|
+
const allItems = [...dirs, ...files];
|
|
356
|
+
allItems.forEach((entry, index) => {
|
|
357
|
+
const isLast = index === allItems.length - 1;
|
|
358
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
359
|
+
const nextPrefix = isLast ? ' ' : '│ ';
|
|
360
|
+
if (entry.isDirectory()) {
|
|
361
|
+
const nodePath = path.join(dirPath, entry.name);
|
|
362
|
+
const config = (0, nodeConfig_1.readNodeConfig)(nodePath);
|
|
363
|
+
let displayName = config?.name || entry.name;
|
|
364
|
+
const tags = [];
|
|
365
|
+
// Add status tag
|
|
366
|
+
if (config?.status === 'done') {
|
|
367
|
+
tags.push('DONE');
|
|
368
|
+
}
|
|
369
|
+
else if (config?.status === 'blocked') {
|
|
370
|
+
tags.push('BLOCKED');
|
|
371
|
+
}
|
|
372
|
+
// Add boss tag
|
|
373
|
+
if (config?.isBoss) {
|
|
374
|
+
tags.push('BOSS');
|
|
375
|
+
}
|
|
376
|
+
// Add deadline tag
|
|
377
|
+
if (config?.deadline && config.status !== 'done') {
|
|
378
|
+
tags.push(formatDeadline(config.deadline));
|
|
379
|
+
}
|
|
380
|
+
const tagStr = tags.length > 0 ? ` [${tags.join('] [')}]` : '';
|
|
381
|
+
lines.push(`${prefix}${connector}${displayName}/${tagStr}`);
|
|
382
|
+
const childLines = getTreeWithStatus(nodePath, prefix + nextPrefix, false, maxDepth, currentDepth + 1, showFiles);
|
|
383
|
+
lines.push(...childLines);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
lines.push(`${prefix}${connector}${entry.name}`);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
return lines;
|
|
390
|
+
}
|
|
266
391
|
async function processCommand(input, currentPath, config, signal, rl) {
|
|
267
|
-
// Check for clipboard pipe
|
|
268
392
|
const clipboardPipe = /\s*\|\s*(pbcopy|copy|clip)\s*$/i;
|
|
269
393
|
const shouldCopy = clipboardPipe.test(input);
|
|
270
394
|
const cleanInput = input.replace(clipboardPipe, '').trim();
|
|
271
395
|
const parts = cleanInput.split(' ').filter(p => p.length > 0);
|
|
272
396
|
const command = parts[0].toLowerCase();
|
|
273
|
-
// Helper to wrap result with clipboard copy
|
|
274
397
|
const wrapResult = (result) => {
|
|
275
398
|
if (shouldCopy && result.output) {
|
|
276
399
|
copyToClipboard(result.output);
|
|
@@ -283,9 +406,155 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
283
406
|
const pkg = require('../../package.json');
|
|
284
407
|
return wrapResult({ output: `Roguelike CLI v${pkg.version}` });
|
|
285
408
|
}
|
|
409
|
+
// Stats command
|
|
410
|
+
if (command === 'stats') {
|
|
411
|
+
return wrapResult({ output: (0, profile_1.formatStats)() });
|
|
412
|
+
}
|
|
413
|
+
// Achievements command
|
|
414
|
+
if (command === 'achievements' || command === 'ach') {
|
|
415
|
+
return wrapResult({ output: (0, profile_1.formatAchievements)() });
|
|
416
|
+
}
|
|
417
|
+
// Done command - mark current node as completed
|
|
418
|
+
if (command === 'done') {
|
|
419
|
+
const nodeConfig = (0, nodeConfig_1.readNodeConfig)(currentPath);
|
|
420
|
+
if (!nodeConfig) {
|
|
421
|
+
return wrapResult({ output: 'No task at current location. Navigate to a task first.' });
|
|
422
|
+
}
|
|
423
|
+
if (nodeConfig.status === 'done') {
|
|
424
|
+
return wrapResult({ output: 'This task is already completed.' });
|
|
425
|
+
}
|
|
426
|
+
// Mark done recursively
|
|
427
|
+
const result = markDoneRecursive(currentPath, config.storagePath);
|
|
428
|
+
// Update profile with XP and achievements
|
|
429
|
+
const depth = getDepth(currentPath, config.storagePath);
|
|
430
|
+
const taskResult = (0, profile_1.completeTask)(result.xpGained, nodeConfig.isBoss || false, depth, nodeConfig.createdAt);
|
|
431
|
+
let output = `\n=== TASK COMPLETED ===\n`;
|
|
432
|
+
output += `\nTasks completed: ${result.tasksCompleted}`;
|
|
433
|
+
if (result.bossesDefeated > 0) {
|
|
434
|
+
output += `\nBosses defeated: ${result.bossesDefeated}`;
|
|
435
|
+
}
|
|
436
|
+
output += `\n+${result.xpGained} XP`;
|
|
437
|
+
if (taskResult.levelUp) {
|
|
438
|
+
output += `\n\n*** LEVEL UP! ***`;
|
|
439
|
+
output += `\nYou are now level ${taskResult.newLevel}!`;
|
|
440
|
+
}
|
|
441
|
+
if (taskResult.newAchievements.length > 0) {
|
|
442
|
+
output += `\n\n=== NEW ACHIEVEMENTS ===`;
|
|
443
|
+
for (const ach of taskResult.newAchievements) {
|
|
444
|
+
output += `\n[x] ${ach.name}: ${ach.description}`;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
output += '\n';
|
|
448
|
+
return wrapResult({ output });
|
|
449
|
+
}
|
|
450
|
+
// Deadline command
|
|
451
|
+
if (command === 'deadline') {
|
|
452
|
+
if (parts.length < 2) {
|
|
453
|
+
return wrapResult({ output: 'Usage: deadline <date>\nExamples: deadline today, deadline tomorrow, deadline +3d, deadline Jan 15' });
|
|
454
|
+
}
|
|
455
|
+
const dateStr = parts.slice(1).join(' ');
|
|
456
|
+
const deadline = parseDeadline(dateStr);
|
|
457
|
+
if (!deadline) {
|
|
458
|
+
return wrapResult({ output: `Could not parse date: ${dateStr}` });
|
|
459
|
+
}
|
|
460
|
+
const nodeConfig = (0, nodeConfig_1.readNodeConfig)(currentPath);
|
|
461
|
+
if (!nodeConfig) {
|
|
462
|
+
return wrapResult({ output: 'No task at current location.' });
|
|
463
|
+
}
|
|
464
|
+
(0, nodeConfig_1.writeNodeConfig)(currentPath, {
|
|
465
|
+
...nodeConfig,
|
|
466
|
+
deadline,
|
|
467
|
+
});
|
|
468
|
+
return wrapResult({ output: `Deadline set: ${formatDeadline(deadline)}` });
|
|
469
|
+
}
|
|
470
|
+
// Boss command - mark as boss/milestone
|
|
471
|
+
if (command === 'boss' || command === 'milestone') {
|
|
472
|
+
const nodeConfig = (0, nodeConfig_1.readNodeConfig)(currentPath);
|
|
473
|
+
if (!nodeConfig) {
|
|
474
|
+
return wrapResult({ output: 'No task at current location.' });
|
|
475
|
+
}
|
|
476
|
+
const newIsBoss = !nodeConfig.isBoss;
|
|
477
|
+
const depth = getDepth(currentPath, config.storagePath);
|
|
478
|
+
(0, nodeConfig_1.writeNodeConfig)(currentPath, {
|
|
479
|
+
...nodeConfig,
|
|
480
|
+
isBoss: newIsBoss,
|
|
481
|
+
xp: (0, nodeConfig_1.calculateXP)(depth, newIsBoss),
|
|
482
|
+
});
|
|
483
|
+
return wrapResult({ output: newIsBoss ? 'Marked as BOSS task (3x XP)' : 'Removed BOSS status' });
|
|
484
|
+
}
|
|
485
|
+
// Block command
|
|
486
|
+
if (command === 'block') {
|
|
487
|
+
const nodeConfig = (0, nodeConfig_1.readNodeConfig)(currentPath);
|
|
488
|
+
if (!nodeConfig) {
|
|
489
|
+
return wrapResult({ output: 'No task at current location.' });
|
|
490
|
+
}
|
|
491
|
+
const reason = parts.length > 1 ? parts.slice(1).join(' ') : undefined;
|
|
492
|
+
(0, nodeConfig_1.writeNodeConfig)(currentPath, {
|
|
493
|
+
...nodeConfig,
|
|
494
|
+
status: 'blocked',
|
|
495
|
+
blockedBy: reason ? [reason] : nodeConfig.blockedBy,
|
|
496
|
+
});
|
|
497
|
+
return wrapResult({ output: reason ? `Blocked: ${reason}` : 'Task marked as blocked' });
|
|
498
|
+
}
|
|
499
|
+
// Unblock command
|
|
500
|
+
if (command === 'unblock') {
|
|
501
|
+
const nodeConfig = (0, nodeConfig_1.readNodeConfig)(currentPath);
|
|
502
|
+
if (!nodeConfig) {
|
|
503
|
+
return wrapResult({ output: 'No task at current location.' });
|
|
504
|
+
}
|
|
505
|
+
(0, nodeConfig_1.writeNodeConfig)(currentPath, {
|
|
506
|
+
...nodeConfig,
|
|
507
|
+
status: 'open',
|
|
508
|
+
blockedBy: [],
|
|
509
|
+
});
|
|
510
|
+
return wrapResult({ output: 'Task unblocked' });
|
|
511
|
+
}
|
|
512
|
+
// Status command - show current task status
|
|
513
|
+
if (command === 'status') {
|
|
514
|
+
const nodeConfig = (0, nodeConfig_1.readNodeConfig)(currentPath);
|
|
515
|
+
if (!nodeConfig) {
|
|
516
|
+
return wrapResult({ output: 'No task at current location.' });
|
|
517
|
+
}
|
|
518
|
+
const lines = [
|
|
519
|
+
'',
|
|
520
|
+
`Task: ${nodeConfig.name}`,
|
|
521
|
+
`Status: ${nodeConfig.status.toUpperCase()}`,
|
|
522
|
+
`XP: ${nodeConfig.xp}`,
|
|
523
|
+
];
|
|
524
|
+
if (nodeConfig.isBoss) {
|
|
525
|
+
lines.push('Type: BOSS');
|
|
526
|
+
}
|
|
527
|
+
if (nodeConfig.deadline) {
|
|
528
|
+
lines.push(`Deadline: ${formatDeadline(nodeConfig.deadline)}`);
|
|
529
|
+
}
|
|
530
|
+
if (nodeConfig.completedAt) {
|
|
531
|
+
lines.push(`Completed: ${new Date(nodeConfig.completedAt).toLocaleDateString()}`);
|
|
532
|
+
}
|
|
533
|
+
if (nodeConfig.blockedBy && nodeConfig.blockedBy.length > 0) {
|
|
534
|
+
lines.push(`Blocked by: ${nodeConfig.blockedBy.join(', ')}`);
|
|
535
|
+
}
|
|
536
|
+
lines.push('');
|
|
537
|
+
return wrapResult({ output: lines.join('\n') });
|
|
538
|
+
}
|
|
286
539
|
// Map command - dungeon visualization
|
|
287
540
|
if (command === 'map') {
|
|
288
|
-
|
|
541
|
+
// Check for --ai flag to use AI generation
|
|
542
|
+
if (parts.includes('--ai') || parts.includes('-a')) {
|
|
543
|
+
const treeLines = getTreeWithStatus(currentPath, '', true, 10, 0, false);
|
|
544
|
+
const treeContent = treeLines.join('\n');
|
|
545
|
+
if (!treeContent) {
|
|
546
|
+
return wrapResult({ output: 'No tasks to visualize.' });
|
|
547
|
+
}
|
|
548
|
+
const mapContent = await (0, claude_1.generateDungeonMapWithAI)(treeContent, config, signal);
|
|
549
|
+
if (mapContent) {
|
|
550
|
+
// Save map to file
|
|
551
|
+
const folderName = path.basename(currentPath);
|
|
552
|
+
(0, nodeConfig_1.saveMapFile)(currentPath, folderName + '-map', mapContent);
|
|
553
|
+
return wrapResult({ output: mapContent + '\n\n[Map saved as .rlc.map]' });
|
|
554
|
+
}
|
|
555
|
+
return wrapResult({ output: 'Could not generate AI map. Using default.' });
|
|
556
|
+
}
|
|
557
|
+
const dungeonMap = generateDungeonMap(currentPath, config);
|
|
289
558
|
return wrapResult({ output: dungeonMap });
|
|
290
559
|
}
|
|
291
560
|
if (command === 'ls') {
|
|
@@ -298,7 +567,16 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
298
567
|
if (entry.name.startsWith('.'))
|
|
299
568
|
continue;
|
|
300
569
|
if (entry.isDirectory()) {
|
|
301
|
-
|
|
570
|
+
const nodePath = path.join(currentPath, entry.name);
|
|
571
|
+
const config = (0, nodeConfig_1.readNodeConfig)(nodePath);
|
|
572
|
+
let suffix = '/';
|
|
573
|
+
if (config?.status === 'done')
|
|
574
|
+
suffix = '/ [DONE]';
|
|
575
|
+
else if (config?.status === 'blocked')
|
|
576
|
+
suffix = '/ [BLOCKED]';
|
|
577
|
+
else if (config?.isBoss)
|
|
578
|
+
suffix = '/ [BOSS]';
|
|
579
|
+
items.push(entry.name + suffix);
|
|
302
580
|
}
|
|
303
581
|
else {
|
|
304
582
|
items.push(entry.name);
|
|
@@ -312,7 +590,6 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
312
590
|
}
|
|
313
591
|
if (command === 'tree') {
|
|
314
592
|
const showFiles = parts.includes('-A') || parts.includes('--all');
|
|
315
|
-
// Parse depth: --depth=N or -d N
|
|
316
593
|
let maxDepth = 10;
|
|
317
594
|
const depthFlag = parts.find(p => p.startsWith('--depth='));
|
|
318
595
|
if (depthFlag) {
|
|
@@ -324,22 +601,28 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
324
601
|
maxDepth = parseInt(parts[dIndex + 1]) || 10;
|
|
325
602
|
}
|
|
326
603
|
}
|
|
327
|
-
const treeLines = (
|
|
604
|
+
const treeLines = getTreeWithStatus(currentPath, '', true, maxDepth, 0, showFiles);
|
|
328
605
|
if (treeLines.length === 0) {
|
|
329
606
|
return wrapResult({ output: 'No items found.' });
|
|
330
607
|
}
|
|
331
608
|
return wrapResult({ output: treeLines.join('\n') });
|
|
332
609
|
}
|
|
333
|
-
// Handle navigation without 'cd' command
|
|
334
|
-
if (command
|
|
335
|
-
|
|
610
|
+
// Handle navigation without 'cd' command
|
|
611
|
+
if (/^\.{2,}$/.test(command)) {
|
|
612
|
+
const levels = command.length - 1;
|
|
336
613
|
let targetPath = currentPath;
|
|
614
|
+
if (targetPath === config.storagePath) {
|
|
615
|
+
return { output: 'Already at root.' };
|
|
616
|
+
}
|
|
337
617
|
for (let i = 0; i < levels; i++) {
|
|
338
618
|
const parentPath = path.dirname(targetPath);
|
|
339
|
-
if (
|
|
340
|
-
|
|
619
|
+
if (targetPath === config.storagePath || parentPath.length < config.storagePath.length) {
|
|
620
|
+
break;
|
|
341
621
|
}
|
|
342
622
|
targetPath = parentPath;
|
|
623
|
+
if (targetPath === config.storagePath) {
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
343
626
|
}
|
|
344
627
|
return { newPath: targetPath, output: '' };
|
|
345
628
|
}
|
|
@@ -392,7 +675,6 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
392
675
|
return { output: `Moved: ${source} -> ${dest}` };
|
|
393
676
|
}
|
|
394
677
|
catch (error) {
|
|
395
|
-
// If rename fails (cross-device), copy then delete
|
|
396
678
|
try {
|
|
397
679
|
copyRecursive(sourcePath, destPath);
|
|
398
680
|
fs.rmSync(sourcePath, { recursive: true, force: true });
|
|
@@ -405,23 +687,19 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
405
687
|
}
|
|
406
688
|
if (command === 'open') {
|
|
407
689
|
const { exec } = require('child_process');
|
|
408
|
-
// open or open . - open current folder in system file manager
|
|
409
690
|
if (parts.length < 2 || parts[1] === '.') {
|
|
410
691
|
exec(`open "${currentPath}"`);
|
|
411
692
|
return { output: `Opening: ${currentPath}` };
|
|
412
693
|
}
|
|
413
694
|
const name = parts.slice(1).join(' ');
|
|
414
695
|
const targetPath = path.join(currentPath, name);
|
|
415
|
-
// Check if target exists
|
|
416
696
|
if (fs.existsSync(targetPath)) {
|
|
417
697
|
const stat = fs.statSync(targetPath);
|
|
418
698
|
if (stat.isDirectory()) {
|
|
419
|
-
// It's a folder, open in file manager
|
|
420
699
|
exec(`open "${targetPath}"`);
|
|
421
700
|
return { output: `Opening: ${targetPath}` };
|
|
422
701
|
}
|
|
423
702
|
if (stat.isFile()) {
|
|
424
|
-
// It's a file, show its content (supports | pbcopy)
|
|
425
703
|
const content = fs.readFileSync(targetPath, 'utf-8');
|
|
426
704
|
return wrapResult({ output: content });
|
|
427
705
|
}
|
|
@@ -468,33 +746,43 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
468
746
|
return { output: 'Usage: cd <node> or cd .. or cd <path>' };
|
|
469
747
|
}
|
|
470
748
|
const target = parts.slice(1).join(' ');
|
|
471
|
-
if (target
|
|
472
|
-
const
|
|
473
|
-
|
|
749
|
+
if (/^\.{2,}$/.test(target)) {
|
|
750
|
+
const levels = target.length - 1;
|
|
751
|
+
let targetPath = currentPath;
|
|
752
|
+
if (targetPath === config.storagePath) {
|
|
474
753
|
return { output: 'Already at root.' };
|
|
475
754
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
755
|
+
for (let i = 0; i < levels; i++) {
|
|
756
|
+
const parentPath = path.dirname(targetPath);
|
|
757
|
+
if (targetPath === config.storagePath || parentPath.length < config.storagePath.length) {
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
targetPath = parentPath;
|
|
761
|
+
if (targetPath === config.storagePath) {
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
483
764
|
}
|
|
484
765
|
return { newPath: targetPath, output: '' };
|
|
485
766
|
}
|
|
486
|
-
// Handle paths like "cd bank/account" or "cd ../other"
|
|
487
767
|
if (target.includes('/')) {
|
|
488
768
|
let targetPath = currentPath;
|
|
489
769
|
const pathParts = target.split('/');
|
|
490
770
|
for (const part of pathParts) {
|
|
491
|
-
if (part
|
|
492
|
-
|
|
771
|
+
if (/^\.{2,}$/.test(part)) {
|
|
772
|
+
const levels = part.length - 1;
|
|
773
|
+
for (let i = 0; i < levels; i++) {
|
|
774
|
+
if (targetPath === config.storagePath)
|
|
775
|
+
break;
|
|
776
|
+
const parentPath = path.dirname(targetPath);
|
|
777
|
+
if (parentPath.length < config.storagePath.length)
|
|
778
|
+
break;
|
|
779
|
+
targetPath = parentPath;
|
|
780
|
+
}
|
|
493
781
|
}
|
|
494
782
|
else if (part === '.') {
|
|
495
783
|
continue;
|
|
496
784
|
}
|
|
497
|
-
else {
|
|
785
|
+
else if (part) {
|
|
498
786
|
const newPath = (0, storage_1.navigateToNode)(targetPath, part);
|
|
499
787
|
if (!newPath) {
|
|
500
788
|
return { output: `Path "${target}" not found.` };
|
|
@@ -546,120 +834,68 @@ Storage: ${config.storagePath}
|
|
|
546
834
|
}
|
|
547
835
|
if (command === 'help') {
|
|
548
836
|
return wrapResult({
|
|
549
|
-
output: `
|
|
550
|
-
|
|
551
|
-
ls - List all schemas, todos, and notes
|
|
552
|
-
tree - Show directory tree structure
|
|
553
|
-
tree -A - Show tree with files
|
|
554
|
-
tree --depth=N - Limit tree depth (e.g., --depth=2)
|
|
555
|
-
map - Dungeon map visualization
|
|
556
|
-
cd <node> - Navigate into a node
|
|
557
|
-
cd .. - Go back to parent
|
|
558
|
-
pwd - Show current path
|
|
559
|
-
open - Open current folder in Finder
|
|
560
|
-
open <folder> - Open specific folder in Finder
|
|
561
|
-
mkdir <name> - Create new folder
|
|
562
|
-
cp <src> <dest> - Copy file or folder
|
|
563
|
-
mv <src> <dest> - Move/rename file or folder
|
|
564
|
-
rm <name> - Delete file
|
|
565
|
-
rm -rf <name> - Delete folder recursively
|
|
566
|
-
config - Show configuration
|
|
567
|
-
config:apiKey=<key> - Set API key
|
|
568
|
-
v, version - Show version
|
|
569
|
-
<description> - Create schema/todo (AI generates preview)
|
|
570
|
-
save - Save pending schema to disk
|
|
571
|
-
cancel - Discard pending schema
|
|
572
|
-
clean - Show items to delete in current folder
|
|
573
|
-
clean --yes - Delete all items in current folder
|
|
574
|
-
exit/quit - Exit the program
|
|
575
|
-
|
|
576
|
-
Clipboard:
|
|
577
|
-
ls | pbcopy - Copy output to clipboard (macOS)
|
|
578
|
-
tree | pbcopy - Works with any command
|
|
579
|
-
config | copy - Alternative for Windows
|
|
837
|
+
output: `
|
|
838
|
+
=== ROGUELIKE CLI ===
|
|
580
839
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
840
|
+
Navigation:
|
|
841
|
+
ls List tasks and files
|
|
842
|
+
tree Show task tree with status
|
|
843
|
+
tree -A Include files
|
|
844
|
+
tree --depth=N Limit tree depth
|
|
845
|
+
cd <task> Navigate into task
|
|
846
|
+
cd .., ... Go back 1 or 2 levels
|
|
847
|
+
pwd Show current path
|
|
848
|
+
open Open folder in Finder
|
|
586
849
|
|
|
587
|
-
|
|
850
|
+
Task Management:
|
|
851
|
+
mkdir <name> Create new task
|
|
852
|
+
done Mark current task as completed (recursive)
|
|
853
|
+
deadline <date> Set deadline (today, tomorrow, +3d, Jan 15)
|
|
854
|
+
boss Toggle boss/milestone status (3x XP)
|
|
855
|
+
block [reason] Mark task as blocked
|
|
856
|
+
unblock Remove blocked status
|
|
857
|
+
status Show current task details
|
|
588
858
|
|
|
589
|
-
|
|
859
|
+
File Operations:
|
|
860
|
+
cp <src> <dest> Copy file or folder
|
|
861
|
+
mv <src> <dest> Move/rename
|
|
862
|
+
rm <name> Delete file
|
|
863
|
+
rm -rf <name> Delete folder
|
|
590
864
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
└── Branch: legal │
|
|
597
|
-
└── open business bank account │
|
|
598
|
-
│ │
|
|
599
|
-
└───────────────────────────────────────────────────────────────┘
|
|
865
|
+
Gamification:
|
|
866
|
+
stats Show XP, level, streaks
|
|
867
|
+
achievements Show achievement list
|
|
868
|
+
map Dungeon map view
|
|
869
|
+
map --ai AI-generated dungeon map
|
|
600
870
|
|
|
601
|
-
|
|
871
|
+
Schema Generation:
|
|
872
|
+
<description> AI generates todo/schema preview
|
|
873
|
+
save Save pending schema
|
|
874
|
+
cancel Discard pending schema
|
|
602
875
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
│ │ │
|
|
611
|
-
│ ├──────────────────┬─────────────────┐ │
|
|
612
|
-
│ │ │ │ │
|
|
613
|
-
│ ┌────────▼────────┐ ┌─────▼──────┐ ┌──────▼────────┐ │
|
|
614
|
-
│ │ PostgreSQL │ │ Redis │ │ Cloudflare │ │
|
|
615
|
-
│ │ (Existing DB) │ │ Cluster │ │ R2 Storage │ │
|
|
616
|
-
│ └─────────────────┘ └────────────┘ └───────────────┘ │
|
|
617
|
-
└─────────────────────────────────────────────────────────────┘
|
|
876
|
+
Utility:
|
|
877
|
+
init Setup wizard
|
|
878
|
+
config Show settings
|
|
879
|
+
clean --yes Clear current folder
|
|
880
|
+
v, version Show version
|
|
881
|
+
help This help
|
|
882
|
+
exit, quit Exit
|
|
618
883
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
│ │
|
|
623
|
-
├── load-balancer │
|
|
624
|
-
├── web-servers │
|
|
625
|
-
│ ├── app-server-1 │
|
|
626
|
-
│ ├── app-server-2 │
|
|
627
|
-
│ └── app-server-3 │
|
|
628
|
-
├── redis │
|
|
629
|
-
│ ├── cache-cluster │
|
|
630
|
-
│ └── session-store │
|
|
631
|
-
└── database │
|
|
632
|
-
├── postgres-primary │
|
|
633
|
-
└── postgres-replica │
|
|
634
|
-
│ │
|
|
635
|
-
└───────────────────────────────────────────────────────────────┘
|
|
636
|
-
|
|
637
|
-
> kubernetes cluster with clusters postgres and redis
|
|
884
|
+
Clipboard:
|
|
885
|
+
<cmd> | pbcopy Copy output (macOS)
|
|
886
|
+
<cmd> | clip Copy output (Windows)
|
|
638
887
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
│ │ │ │ │ │
|
|
645
|
-
│ │ primary-pod │ │ cache-pod-1 │ │
|
|
646
|
-
│ │ replica-pod-1│ │ cache-pod-2 │ │
|
|
647
|
-
│ │ replica-pod-2│ │ │ │
|
|
648
|
-
│ └──────┬───────┘ └──────┬───────┘ │
|
|
649
|
-
│ │ │ │
|
|
650
|
-
│ └──────────┬───────────┘ │
|
|
651
|
-
│ │ │
|
|
652
|
-
│ ┌───────▼────────┐ │
|
|
653
|
-
│ │ worker-zones │ │
|
|
654
|
-
│ │ zone-1 │ │
|
|
655
|
-
│ │ zone-2 │ │
|
|
656
|
-
│ └────────────────┘ │
|
|
657
|
-
└─────────────────────────────────────────────────────────────┘
|
|
888
|
+
Deadlines:
|
|
889
|
+
deadline today Due today
|
|
890
|
+
deadline tomorrow Due tomorrow
|
|
891
|
+
deadline +3d Due in 3 days
|
|
892
|
+
deadline Jan 15 Due on date
|
|
658
893
|
|
|
659
|
-
www.rlc.rocks
|
|
894
|
+
www.rlc.rocks
|
|
895
|
+
`.trim()
|
|
660
896
|
});
|
|
661
897
|
}
|
|
662
|
-
// Save command
|
|
898
|
+
// Save command
|
|
663
899
|
if (command === 'save') {
|
|
664
900
|
if (!exports.sessionState.pending) {
|
|
665
901
|
return wrapResult({ output: 'Nothing to save. Create a schema first.' });
|
|
@@ -667,25 +903,21 @@ www.rlc.rocks`
|
|
|
667
903
|
const pending = exports.sessionState.pending;
|
|
668
904
|
const safeName = pending.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
669
905
|
if (pending.format === 'tree') {
|
|
670
|
-
// Create folder structure from tree ASCII art
|
|
671
906
|
const rootPath = path.join(currentPath, safeName);
|
|
672
907
|
createFoldersFromTree(rootPath, pending.content);
|
|
673
|
-
// Clear session
|
|
674
908
|
exports.sessionState.pending = null;
|
|
675
909
|
exports.sessionState.history = [];
|
|
676
910
|
return wrapResult({ output: `Created todo folder: ${safeName}/` });
|
|
677
911
|
}
|
|
678
912
|
else {
|
|
679
|
-
// Save as .rlc.schema file
|
|
680
913
|
const schemaPath = (0, nodeConfig_1.saveSchemaFile)(currentPath, pending.title, pending.content);
|
|
681
914
|
const filename = path.basename(schemaPath);
|
|
682
|
-
// Clear session
|
|
683
915
|
exports.sessionState.pending = null;
|
|
684
916
|
exports.sessionState.history = [];
|
|
685
917
|
return wrapResult({ output: `Saved: ${filename}` });
|
|
686
918
|
}
|
|
687
919
|
}
|
|
688
|
-
// Cancel command
|
|
920
|
+
// Cancel command
|
|
689
921
|
if (command === 'cancel') {
|
|
690
922
|
if (!exports.sessionState.pending) {
|
|
691
923
|
return wrapResult({ output: 'Nothing to cancel.' });
|
|
@@ -694,14 +926,13 @@ www.rlc.rocks`
|
|
|
694
926
|
exports.sessionState.history = [];
|
|
695
927
|
return wrapResult({ output: 'Discarded pending schema.' });
|
|
696
928
|
}
|
|
697
|
-
// Clean command
|
|
929
|
+
// Clean command
|
|
698
930
|
if (command === 'clean') {
|
|
699
931
|
const entries = fs.readdirSync(currentPath);
|
|
700
932
|
const toDelete = entries.filter(e => !e.startsWith('.'));
|
|
701
933
|
if (toDelete.length === 0) {
|
|
702
934
|
return wrapResult({ output: 'Directory is already empty.' });
|
|
703
935
|
}
|
|
704
|
-
// Check for --yes flag to skip confirmation
|
|
705
936
|
if (!parts.includes('--yes') && !parts.includes('-y')) {
|
|
706
937
|
return wrapResult({
|
|
707
938
|
output: `Will delete ${toDelete.length} items:\n${toDelete.join('\n')}\n\nRun "clean --yes" to confirm.`
|
|
@@ -713,23 +944,20 @@ www.rlc.rocks`
|
|
|
713
944
|
}
|
|
714
945
|
return wrapResult({ output: `Deleted ${toDelete.length} items.` });
|
|
715
946
|
}
|
|
716
|
-
// AI generation
|
|
947
|
+
// AI generation
|
|
717
948
|
const fullInput = cleanInput;
|
|
718
|
-
// Add user message to history
|
|
719
949
|
exports.sessionState.history.push({ role: 'user', content: fullInput });
|
|
720
950
|
const schema = await (0, claude_1.generateSchemaWithAI)(fullInput, config, signal, exports.sessionState.history);
|
|
721
951
|
if (signal?.aborted) {
|
|
722
952
|
return { output: 'Command cancelled.' };
|
|
723
953
|
}
|
|
724
954
|
if (schema) {
|
|
725
|
-
// Store in pending
|
|
726
955
|
exports.sessionState.pending = {
|
|
727
956
|
title: schema.title,
|
|
728
957
|
content: schema.content,
|
|
729
958
|
format: schema.format,
|
|
730
959
|
tree: schema.tree
|
|
731
960
|
};
|
|
732
|
-
// Add assistant response to history
|
|
733
961
|
exports.sessionState.history.push({ role: 'assistant', content: schema.content });
|
|
734
962
|
const safeName = schema.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
735
963
|
const saveHint = schema.format === 'tree'
|