roguelike-cli 1.2.6 → 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 +383 -181
- 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 +470 -187
- 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,29 +601,25 @@ 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
|
|
610
|
+
// Handle navigation without 'cd' command
|
|
334
611
|
if (/^\.{2,}$/.test(command)) {
|
|
335
|
-
// Count dots: .. = 1 level, ... = 2 levels, .... = 3 levels, etc
|
|
336
612
|
const levels = command.length - 1;
|
|
337
613
|
let targetPath = currentPath;
|
|
338
|
-
// Already at root?
|
|
339
614
|
if (targetPath === config.storagePath) {
|
|
340
615
|
return { output: 'Already at root.' };
|
|
341
616
|
}
|
|
342
617
|
for (let i = 0; i < levels; i++) {
|
|
343
618
|
const parentPath = path.dirname(targetPath);
|
|
344
|
-
// Stop at storage root
|
|
345
619
|
if (targetPath === config.storagePath || parentPath.length < config.storagePath.length) {
|
|
346
620
|
break;
|
|
347
621
|
}
|
|
348
622
|
targetPath = parentPath;
|
|
349
|
-
// If we reached root, stop
|
|
350
623
|
if (targetPath === config.storagePath) {
|
|
351
624
|
break;
|
|
352
625
|
}
|
|
@@ -402,7 +675,6 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
402
675
|
return { output: `Moved: ${source} -> ${dest}` };
|
|
403
676
|
}
|
|
404
677
|
catch (error) {
|
|
405
|
-
// If rename fails (cross-device), copy then delete
|
|
406
678
|
try {
|
|
407
679
|
copyRecursive(sourcePath, destPath);
|
|
408
680
|
fs.rmSync(sourcePath, { recursive: true, force: true });
|
|
@@ -415,23 +687,19 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
415
687
|
}
|
|
416
688
|
if (command === 'open') {
|
|
417
689
|
const { exec } = require('child_process');
|
|
418
|
-
// open or open . - open current folder in system file manager
|
|
419
690
|
if (parts.length < 2 || parts[1] === '.') {
|
|
420
691
|
exec(`open "${currentPath}"`);
|
|
421
692
|
return { output: `Opening: ${currentPath}` };
|
|
422
693
|
}
|
|
423
694
|
const name = parts.slice(1).join(' ');
|
|
424
695
|
const targetPath = path.join(currentPath, name);
|
|
425
|
-
// Check if target exists
|
|
426
696
|
if (fs.existsSync(targetPath)) {
|
|
427
697
|
const stat = fs.statSync(targetPath);
|
|
428
698
|
if (stat.isDirectory()) {
|
|
429
|
-
// It's a folder, open in file manager
|
|
430
699
|
exec(`open "${targetPath}"`);
|
|
431
700
|
return { output: `Opening: ${targetPath}` };
|
|
432
701
|
}
|
|
433
702
|
if (stat.isFile()) {
|
|
434
|
-
// It's a file, show its content (supports | pbcopy)
|
|
435
703
|
const content = fs.readFileSync(targetPath, 'utf-8');
|
|
436
704
|
return wrapResult({ output: content });
|
|
437
705
|
}
|
|
@@ -478,35 +746,29 @@ async function processCommand(input, currentPath, config, signal, rl) {
|
|
|
478
746
|
return { output: 'Usage: cd <node> or cd .. or cd <path>' };
|
|
479
747
|
}
|
|
480
748
|
const target = parts.slice(1).join(' ');
|
|
481
|
-
// Handle cd .., cd ..., cd ...., etc
|
|
482
749
|
if (/^\.{2,}$/.test(target)) {
|
|
483
750
|
const levels = target.length - 1;
|
|
484
751
|
let targetPath = currentPath;
|
|
485
|
-
// Already at root?
|
|
486
752
|
if (targetPath === config.storagePath) {
|
|
487
753
|
return { output: 'Already at root.' };
|
|
488
754
|
}
|
|
489
755
|
for (let i = 0; i < levels; i++) {
|
|
490
756
|
const parentPath = path.dirname(targetPath);
|
|
491
|
-
// Stop at storage root
|
|
492
757
|
if (targetPath === config.storagePath || parentPath.length < config.storagePath.length) {
|
|
493
758
|
break;
|
|
494
759
|
}
|
|
495
760
|
targetPath = parentPath;
|
|
496
|
-
// If we reached root, stop
|
|
497
761
|
if (targetPath === config.storagePath) {
|
|
498
762
|
break;
|
|
499
763
|
}
|
|
500
764
|
}
|
|
501
765
|
return { newPath: targetPath, output: '' };
|
|
502
766
|
}
|
|
503
|
-
// Handle paths like "cd bank/account" or "cd ../other"
|
|
504
767
|
if (target.includes('/')) {
|
|
505
768
|
let targetPath = currentPath;
|
|
506
769
|
const pathParts = target.split('/');
|
|
507
770
|
for (const part of pathParts) {
|
|
508
771
|
if (/^\.{2,}$/.test(part)) {
|
|
509
|
-
// Handle .., ..., ...., etc in path
|
|
510
772
|
const levels = part.length - 1;
|
|
511
773
|
for (let i = 0; i < levels; i++) {
|
|
512
774
|
if (targetPath === config.storagePath)
|
|
@@ -572,120 +834,68 @@ Storage: ${config.storagePath}
|
|
|
572
834
|
}
|
|
573
835
|
if (command === 'help') {
|
|
574
836
|
return wrapResult({
|
|
575
|
-
output: `
|
|
576
|
-
|
|
577
|
-
ls - List all schemas, todos, and notes
|
|
578
|
-
tree - Show directory tree structure
|
|
579
|
-
tree -A - Show tree with files
|
|
580
|
-
tree --depth=N - Limit tree depth (e.g., --depth=2)
|
|
581
|
-
map - Dungeon map visualization
|
|
582
|
-
cd <node> - Navigate into a node
|
|
583
|
-
cd .. - Go back to parent
|
|
584
|
-
pwd - Show current path
|
|
585
|
-
open - Open current folder in Finder
|
|
586
|
-
open <folder> - Open specific folder in Finder
|
|
587
|
-
mkdir <name> - Create new folder
|
|
588
|
-
cp <src> <dest> - Copy file or folder
|
|
589
|
-
mv <src> <dest> - Move/rename file or folder
|
|
590
|
-
rm <name> - Delete file
|
|
591
|
-
rm -rf <name> - Delete folder recursively
|
|
592
|
-
config - Show configuration
|
|
593
|
-
config:apiKey=<key> - Set API key
|
|
594
|
-
v, version - Show version
|
|
595
|
-
<description> - Create schema/todo (AI generates preview)
|
|
596
|
-
save - Save pending schema to disk
|
|
597
|
-
cancel - Discard pending schema
|
|
598
|
-
clean - Show items to delete in current folder
|
|
599
|
-
clean --yes - Delete all items in current folder
|
|
600
|
-
exit/quit - Exit the program
|
|
601
|
-
|
|
602
|
-
Clipboard:
|
|
603
|
-
ls | pbcopy - Copy output to clipboard (macOS)
|
|
604
|
-
tree | pbcopy - Works with any command
|
|
605
|
-
config | copy - Alternative for Windows
|
|
837
|
+
output: `
|
|
838
|
+
=== ROGUELIKE CLI ===
|
|
606
839
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
|
612
849
|
|
|
613
|
-
|
|
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
|
|
614
858
|
|
|
615
|
-
|
|
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
|
|
616
864
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
└── Branch: legal │
|
|
623
|
-
└── open business bank account │
|
|
624
|
-
│ │
|
|
625
|
-
└───────────────────────────────────────────────────────────────┘
|
|
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
|
|
626
870
|
|
|
627
|
-
|
|
871
|
+
Schema Generation:
|
|
872
|
+
<description> AI generates todo/schema preview
|
|
873
|
+
save Save pending schema
|
|
874
|
+
cancel Discard pending schema
|
|
628
875
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
│ │ │
|
|
637
|
-
│ ├──────────────────┬─────────────────┐ │
|
|
638
|
-
│ │ │ │ │
|
|
639
|
-
│ ┌────────▼────────┐ ┌─────▼──────┐ ┌──────▼────────┐ │
|
|
640
|
-
│ │ PostgreSQL │ │ Redis │ │ Cloudflare │ │
|
|
641
|
-
│ │ (Existing DB) │ │ Cluster │ │ R2 Storage │ │
|
|
642
|
-
│ └─────────────────┘ └────────────┘ └───────────────┘ │
|
|
643
|
-
└─────────────────────────────────────────────────────────────┘
|
|
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
|
|
644
883
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
│ │
|
|
649
|
-
├── load-balancer │
|
|
650
|
-
├── web-servers │
|
|
651
|
-
│ ├── app-server-1 │
|
|
652
|
-
│ ├── app-server-2 │
|
|
653
|
-
│ └── app-server-3 │
|
|
654
|
-
├── redis │
|
|
655
|
-
│ ├── cache-cluster │
|
|
656
|
-
│ └── session-store │
|
|
657
|
-
└── database │
|
|
658
|
-
├── postgres-primary │
|
|
659
|
-
└── postgres-replica │
|
|
660
|
-
│ │
|
|
661
|
-
└───────────────────────────────────────────────────────────────┘
|
|
662
|
-
|
|
663
|
-
> kubernetes cluster with clusters postgres and redis
|
|
884
|
+
Clipboard:
|
|
885
|
+
<cmd> | pbcopy Copy output (macOS)
|
|
886
|
+
<cmd> | clip Copy output (Windows)
|
|
664
887
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
│ │ │ │ │ │
|
|
671
|
-
│ │ primary-pod │ │ cache-pod-1 │ │
|
|
672
|
-
│ │ replica-pod-1│ │ cache-pod-2 │ │
|
|
673
|
-
│ │ replica-pod-2│ │ │ │
|
|
674
|
-
│ └──────┬───────┘ └──────┬───────┘ │
|
|
675
|
-
│ │ │ │
|
|
676
|
-
│ └──────────┬───────────┘ │
|
|
677
|
-
│ │ │
|
|
678
|
-
│ ┌───────▼────────┐ │
|
|
679
|
-
│ │ worker-zones │ │
|
|
680
|
-
│ │ zone-1 │ │
|
|
681
|
-
│ │ zone-2 │ │
|
|
682
|
-
│ └────────────────┘ │
|
|
683
|
-
└─────────────────────────────────────────────────────────────┘
|
|
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
|
|
684
893
|
|
|
685
|
-
www.rlc.rocks
|
|
894
|
+
www.rlc.rocks
|
|
895
|
+
`.trim()
|
|
686
896
|
});
|
|
687
897
|
}
|
|
688
|
-
// Save command
|
|
898
|
+
// Save command
|
|
689
899
|
if (command === 'save') {
|
|
690
900
|
if (!exports.sessionState.pending) {
|
|
691
901
|
return wrapResult({ output: 'Nothing to save. Create a schema first.' });
|
|
@@ -693,25 +903,21 @@ www.rlc.rocks`
|
|
|
693
903
|
const pending = exports.sessionState.pending;
|
|
694
904
|
const safeName = pending.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
695
905
|
if (pending.format === 'tree') {
|
|
696
|
-
// Create folder structure from tree ASCII art
|
|
697
906
|
const rootPath = path.join(currentPath, safeName);
|
|
698
907
|
createFoldersFromTree(rootPath, pending.content);
|
|
699
|
-
// Clear session
|
|
700
908
|
exports.sessionState.pending = null;
|
|
701
909
|
exports.sessionState.history = [];
|
|
702
910
|
return wrapResult({ output: `Created todo folder: ${safeName}/` });
|
|
703
911
|
}
|
|
704
912
|
else {
|
|
705
|
-
// Save as .rlc.schema file
|
|
706
913
|
const schemaPath = (0, nodeConfig_1.saveSchemaFile)(currentPath, pending.title, pending.content);
|
|
707
914
|
const filename = path.basename(schemaPath);
|
|
708
|
-
// Clear session
|
|
709
915
|
exports.sessionState.pending = null;
|
|
710
916
|
exports.sessionState.history = [];
|
|
711
917
|
return wrapResult({ output: `Saved: ${filename}` });
|
|
712
918
|
}
|
|
713
919
|
}
|
|
714
|
-
// Cancel command
|
|
920
|
+
// Cancel command
|
|
715
921
|
if (command === 'cancel') {
|
|
716
922
|
if (!exports.sessionState.pending) {
|
|
717
923
|
return wrapResult({ output: 'Nothing to cancel.' });
|
|
@@ -720,14 +926,13 @@ www.rlc.rocks`
|
|
|
720
926
|
exports.sessionState.history = [];
|
|
721
927
|
return wrapResult({ output: 'Discarded pending schema.' });
|
|
722
928
|
}
|
|
723
|
-
// Clean command
|
|
929
|
+
// Clean command
|
|
724
930
|
if (command === 'clean') {
|
|
725
931
|
const entries = fs.readdirSync(currentPath);
|
|
726
932
|
const toDelete = entries.filter(e => !e.startsWith('.'));
|
|
727
933
|
if (toDelete.length === 0) {
|
|
728
934
|
return wrapResult({ output: 'Directory is already empty.' });
|
|
729
935
|
}
|
|
730
|
-
// Check for --yes flag to skip confirmation
|
|
731
936
|
if (!parts.includes('--yes') && !parts.includes('-y')) {
|
|
732
937
|
return wrapResult({
|
|
733
938
|
output: `Will delete ${toDelete.length} items:\n${toDelete.join('\n')}\n\nRun "clean --yes" to confirm.`
|
|
@@ -739,23 +944,20 @@ www.rlc.rocks`
|
|
|
739
944
|
}
|
|
740
945
|
return wrapResult({ output: `Deleted ${toDelete.length} items.` });
|
|
741
946
|
}
|
|
742
|
-
// AI generation
|
|
947
|
+
// AI generation
|
|
743
948
|
const fullInput = cleanInput;
|
|
744
|
-
// Add user message to history
|
|
745
949
|
exports.sessionState.history.push({ role: 'user', content: fullInput });
|
|
746
950
|
const schema = await (0, claude_1.generateSchemaWithAI)(fullInput, config, signal, exports.sessionState.history);
|
|
747
951
|
if (signal?.aborted) {
|
|
748
952
|
return { output: 'Command cancelled.' };
|
|
749
953
|
}
|
|
750
954
|
if (schema) {
|
|
751
|
-
// Store in pending
|
|
752
955
|
exports.sessionState.pending = {
|
|
753
956
|
title: schema.title,
|
|
754
957
|
content: schema.content,
|
|
755
958
|
format: schema.format,
|
|
756
959
|
tree: schema.tree
|
|
757
960
|
};
|
|
758
|
-
// Add assistant response to history
|
|
759
961
|
exports.sessionState.history.push({ role: 'assistant', content: schema.content });
|
|
760
962
|
const safeName = schema.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
761
963
|
const saveHint = schema.format === 'tree'
|