roguelike-cli 1.0.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.
@@ -0,0 +1,552 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.sessionState = void 0;
37
+ exports.processCommand = processCommand;
38
+ const path = __importStar(require("path"));
39
+ const fs = __importStar(require("fs"));
40
+ const child_process_1 = require("child_process");
41
+ const storage_1 = require("../storage/storage");
42
+ const nodeConfig_1 = require("../storage/nodeConfig");
43
+ const claude_1 = require("../ai/claude");
44
+ // Global session state
45
+ exports.sessionState = {
46
+ pending: null,
47
+ history: []
48
+ };
49
+ // Format items in columns like native ls
50
+ function formatColumns(items, termWidth = 80) {
51
+ if (items.length === 0)
52
+ return '';
53
+ const maxLen = Math.max(...items.map(s => s.length)) + 2;
54
+ const cols = Math.max(1, Math.floor(termWidth / maxLen));
55
+ const rows = [];
56
+ for (let i = 0; i < items.length; i += cols) {
57
+ const row = items.slice(i, i + cols);
58
+ rows.push(row.map(item => item.padEnd(maxLen)).join('').trimEnd());
59
+ }
60
+ return rows.join('\n');
61
+ }
62
+ // Copy to clipboard (cross-platform)
63
+ function copyToClipboard(text) {
64
+ const platform = process.platform;
65
+ try {
66
+ if (platform === 'darwin') {
67
+ (0, child_process_1.execSync)('pbcopy', { input: text });
68
+ }
69
+ else if (platform === 'win32') {
70
+ (0, child_process_1.execSync)('clip', { input: text });
71
+ }
72
+ else {
73
+ // Linux - try xclip or xsel
74
+ try {
75
+ (0, child_process_1.execSync)('xclip -selection clipboard', { input: text });
76
+ }
77
+ catch {
78
+ (0, child_process_1.execSync)('xsel --clipboard --input', { input: text });
79
+ }
80
+ }
81
+ }
82
+ catch (e) {
83
+ // Silently fail if clipboard not available
84
+ }
85
+ }
86
+ // Helper function for recursive copy
87
+ function copyRecursive(src, dest) {
88
+ const stat = fs.statSync(src);
89
+ if (stat.isDirectory()) {
90
+ if (!fs.existsSync(dest)) {
91
+ fs.mkdirSync(dest, { recursive: true });
92
+ }
93
+ const entries = fs.readdirSync(src);
94
+ for (const entry of entries) {
95
+ const srcPath = path.join(src, entry);
96
+ const destPath = path.join(dest, entry);
97
+ copyRecursive(srcPath, destPath);
98
+ }
99
+ }
100
+ else {
101
+ fs.copyFileSync(src, dest);
102
+ }
103
+ }
104
+ async function processCommand(input, currentPath, config, signal) {
105
+ // Check for clipboard pipe
106
+ const clipboardPipe = /\s*\|\s*(pbcopy|copy|clip)\s*$/i;
107
+ const shouldCopy = clipboardPipe.test(input);
108
+ const cleanInput = input.replace(clipboardPipe, '').trim();
109
+ const parts = cleanInput.split(' ').filter(p => p.length > 0);
110
+ const command = parts[0].toLowerCase();
111
+ // Helper to wrap result with clipboard copy
112
+ const wrapResult = (result) => {
113
+ if (shouldCopy && result.output) {
114
+ copyToClipboard(result.output);
115
+ result.output += '\n\n[copied to clipboard]';
116
+ }
117
+ return result;
118
+ };
119
+ if (command === 'ls') {
120
+ if (!fs.existsSync(currentPath)) {
121
+ return wrapResult({ output: 'Directory does not exist.' });
122
+ }
123
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
124
+ const items = [];
125
+ for (const entry of entries) {
126
+ if (entry.name.startsWith('.'))
127
+ continue;
128
+ if (entry.isDirectory()) {
129
+ items.push(entry.name + '/');
130
+ }
131
+ else {
132
+ items.push(entry.name);
133
+ }
134
+ }
135
+ if (items.length === 0) {
136
+ return wrapResult({ output: '' });
137
+ }
138
+ const termWidth = process.stdout.columns || 80;
139
+ return wrapResult({ output: formatColumns(items, termWidth) });
140
+ }
141
+ if (command === 'tree') {
142
+ const showFiles = parts.includes('-A') || parts.includes('--all');
143
+ // Parse depth: --depth=N or -d N
144
+ let maxDepth = 10;
145
+ const depthFlag = parts.find(p => p.startsWith('--depth='));
146
+ if (depthFlag) {
147
+ maxDepth = parseInt(depthFlag.split('=')[1]) || 10;
148
+ }
149
+ else {
150
+ const dIndex = parts.indexOf('-d');
151
+ if (dIndex !== -1 && parts[dIndex + 1]) {
152
+ maxDepth = parseInt(parts[dIndex + 1]) || 10;
153
+ }
154
+ }
155
+ const treeLines = (0, storage_1.getTree)(currentPath, '', true, maxDepth, 0, showFiles);
156
+ if (treeLines.length === 0) {
157
+ return wrapResult({ output: 'No items found.' });
158
+ }
159
+ return wrapResult({ output: treeLines.join('\n') });
160
+ }
161
+ // Handle navigation without 'cd' command (.., ...)
162
+ if (command === '..' || command === '...') {
163
+ let levels = command === '...' ? 2 : 1;
164
+ let targetPath = currentPath;
165
+ for (let i = 0; i < levels; i++) {
166
+ const parentPath = path.dirname(targetPath);
167
+ if (parentPath === config.storagePath || parentPath.length < config.storagePath.length) {
168
+ return { output: 'Already at root.' };
169
+ }
170
+ targetPath = parentPath;
171
+ }
172
+ return { newPath: targetPath, output: '' };
173
+ }
174
+ if (command === 'mkdir') {
175
+ if (parts.length < 2) {
176
+ return { output: 'Usage: mkdir <name>' };
177
+ }
178
+ const name = parts.slice(1).join(' ');
179
+ const { createNode } = require('../storage/nodeConfig');
180
+ try {
181
+ const nodePath = createNode(currentPath, name);
182
+ return { output: `Created: ${name}` };
183
+ }
184
+ catch (error) {
185
+ return { output: `Error: ${error.message}` };
186
+ }
187
+ }
188
+ if (command === 'cp') {
189
+ if (parts.length < 3) {
190
+ return { output: 'Usage: cp <source> <destination>' };
191
+ }
192
+ const source = parts[1];
193
+ const dest = parts[2];
194
+ const sourcePath = path.isAbsolute(source) ? source : path.join(currentPath, source);
195
+ const destPath = path.isAbsolute(dest) ? dest : path.join(currentPath, dest);
196
+ if (!fs.existsSync(sourcePath)) {
197
+ return { output: `Source not found: ${source}` };
198
+ }
199
+ try {
200
+ copyRecursive(sourcePath, destPath);
201
+ return { output: `Copied: ${source} -> ${dest}` };
202
+ }
203
+ catch (error) {
204
+ return { output: `Error: ${error.message}` };
205
+ }
206
+ }
207
+ if (command === 'mv' || command === 'move') {
208
+ if (parts.length < 3) {
209
+ return { output: 'Usage: mv <source> <destination>' };
210
+ }
211
+ const source = parts[1];
212
+ const dest = parts[2];
213
+ const sourcePath = path.isAbsolute(source) ? source : path.join(currentPath, source);
214
+ const destPath = path.isAbsolute(dest) ? dest : path.join(currentPath, dest);
215
+ if (!fs.existsSync(sourcePath)) {
216
+ return { output: `Source not found: ${source}` };
217
+ }
218
+ try {
219
+ fs.renameSync(sourcePath, destPath);
220
+ return { output: `Moved: ${source} -> ${dest}` };
221
+ }
222
+ catch (error) {
223
+ // If rename fails (cross-device), copy then delete
224
+ try {
225
+ copyRecursive(sourcePath, destPath);
226
+ fs.rmSync(sourcePath, { recursive: true, force: true });
227
+ return { output: `Moved: ${source} -> ${dest}` };
228
+ }
229
+ catch (e) {
230
+ return { output: `Error: ${e.message}` };
231
+ }
232
+ }
233
+ }
234
+ if (command === 'open') {
235
+ const { exec } = require('child_process');
236
+ // open or open . - open current folder in system file manager
237
+ if (parts.length < 2 || parts[1] === '.') {
238
+ exec(`open "${currentPath}"`);
239
+ return { output: `Opening: ${currentPath}` };
240
+ }
241
+ const name = parts.slice(1).join(' ');
242
+ const targetPath = path.join(currentPath, name);
243
+ // Check if target exists
244
+ if (fs.existsSync(targetPath)) {
245
+ const stat = fs.statSync(targetPath);
246
+ if (stat.isDirectory()) {
247
+ // It's a folder, open in file manager
248
+ exec(`open "${targetPath}"`);
249
+ return { output: `Opening: ${targetPath}` };
250
+ }
251
+ if (stat.isFile()) {
252
+ // It's a file, show its content (supports | pbcopy)
253
+ const content = fs.readFileSync(targetPath, 'utf-8');
254
+ return wrapResult({ output: content });
255
+ }
256
+ }
257
+ return wrapResult({ output: `Not found: ${name}` });
258
+ }
259
+ if (command === 'rm') {
260
+ if (parts.length < 2) {
261
+ return { output: 'Usage: rm <name> or rm -rf <name>' };
262
+ }
263
+ const isRecursive = parts[1] === '-rf' || parts[1] === '-r';
264
+ const targetName = isRecursive ? parts.slice(2).join(' ') : parts.slice(1).join(' ');
265
+ if (!targetName) {
266
+ return { output: 'Usage: rm <name> or rm -rf <name>' };
267
+ }
268
+ const targetPath = path.isAbsolute(targetName) ? targetName : path.join(currentPath, targetName);
269
+ if (!fs.existsSync(targetPath)) {
270
+ return { output: `Not found: ${targetName}` };
271
+ }
272
+ try {
273
+ if (isRecursive) {
274
+ fs.rmSync(targetPath, { recursive: true, force: true });
275
+ }
276
+ else {
277
+ const stat = fs.statSync(targetPath);
278
+ if (stat.isDirectory()) {
279
+ return { output: `Error: ${targetName} is a directory. Use rm -rf to remove directories.` };
280
+ }
281
+ fs.unlinkSync(targetPath);
282
+ }
283
+ return { output: `Removed: ${targetName}` };
284
+ }
285
+ catch (error) {
286
+ return { output: `Error: ${error.message}` };
287
+ }
288
+ }
289
+ if (command === 'init') {
290
+ const { initCommand } = await Promise.resolve().then(() => __importStar(require('../commands/init')));
291
+ await initCommand();
292
+ return { output: 'Initialization complete. You can now use rlc.\n', reloadConfig: true };
293
+ }
294
+ if (command === 'cd') {
295
+ if (parts.length < 2) {
296
+ return { output: 'Usage: cd <node> or cd .. or cd <path>' };
297
+ }
298
+ const target = parts.slice(1).join(' ');
299
+ if (target === '..') {
300
+ const parentPath = path.dirname(currentPath);
301
+ if (parentPath === config.storagePath || parentPath.length < config.storagePath.length) {
302
+ return { output: 'Already at root.' };
303
+ }
304
+ return { newPath: parentPath, output: '' };
305
+ }
306
+ if (target === '...') {
307
+ let targetPath = path.dirname(currentPath);
308
+ targetPath = path.dirname(targetPath);
309
+ if (targetPath.length < config.storagePath.length) {
310
+ return { output: 'Already at root.' };
311
+ }
312
+ return { newPath: targetPath, output: '' };
313
+ }
314
+ // Handle paths like "cd bank/account" or "cd ../other"
315
+ if (target.includes('/')) {
316
+ let targetPath = currentPath;
317
+ const pathParts = target.split('/');
318
+ for (const part of pathParts) {
319
+ if (part === '..') {
320
+ targetPath = path.dirname(targetPath);
321
+ }
322
+ else if (part === '.') {
323
+ continue;
324
+ }
325
+ else {
326
+ const newPath = (0, storage_1.navigateToNode)(targetPath, part);
327
+ if (!newPath) {
328
+ return { output: `Path "${target}" not found.` };
329
+ }
330
+ targetPath = newPath;
331
+ }
332
+ }
333
+ return { newPath: targetPath, output: '' };
334
+ }
335
+ const newPath = (0, storage_1.navigateToNode)(currentPath, target);
336
+ if (!newPath) {
337
+ return { output: `Node "${target}" not found.` };
338
+ }
339
+ return { newPath, output: '' };
340
+ }
341
+ if (command === 'pwd') {
342
+ return wrapResult({ output: currentPath });
343
+ }
344
+ if (command === 'config') {
345
+ const maskedKey = config.apiKey
346
+ ? config.apiKey.slice(0, 8) + '...' + config.apiKey.slice(-4)
347
+ : '(not set)';
348
+ const output = `
349
+ Provider: ${config.aiProvider}
350
+ Model: ${config.model || '(default)'}
351
+ API Key: ${maskedKey}
352
+ Storage: ${config.storagePath}
353
+ `.trim();
354
+ return wrapResult({ output });
355
+ }
356
+ if (command.startsWith('config:')) {
357
+ const configParts = input.split(':').slice(1).join(':').trim().split('=');
358
+ if (configParts.length !== 2) {
359
+ return { output: 'Usage: config:key=value' };
360
+ }
361
+ const key = configParts[0].trim();
362
+ const value = configParts[1].trim();
363
+ if (key === 'apiKey') {
364
+ const { updateConfig } = await Promise.resolve().then(() => __importStar(require('../config/config')));
365
+ updateConfig({ apiKey: value });
366
+ return { output: 'API key updated.' };
367
+ }
368
+ if (key === 'storagePath') {
369
+ const { updateConfig } = await Promise.resolve().then(() => __importStar(require('../config/config')));
370
+ updateConfig({ storagePath: value, currentPath: value });
371
+ return { output: `Storage path updated to: ${value}` };
372
+ }
373
+ return { output: `Unknown config key: ${key}` };
374
+ }
375
+ if (command === 'help') {
376
+ return wrapResult({
377
+ output: `Commands:
378
+ init - Initialize rlc (first time setup)
379
+ ls - List all schemas, todos, and notes
380
+ tree - Show directory tree structure
381
+ tree -A - Show tree with files
382
+ tree --depth=N - Limit tree depth (e.g., --depth=2)
383
+ cd <node> - Navigate into a node
384
+ cd .. - Go back to parent
385
+ pwd - Show current path
386
+ open - Open current folder in Finder
387
+ open <folder> - Open specific folder in Finder
388
+ mkdir <name> - Create new folder
389
+ cp <src> <dest> - Copy file or folder
390
+ mv <src> <dest> - Move/rename file or folder
391
+ rm <name> - Delete file
392
+ rm -rf <name> - Delete folder recursively
393
+ config - Show configuration
394
+ config:apiKey=<key> - Set API key
395
+ <description> - Create schema/todo (AI generates preview)
396
+ save - Save pending schema to disk
397
+ cancel - Discard pending schema
398
+ clean - Show items to delete in current folder
399
+ clean --yes - Delete all items in current folder
400
+ exit/quit - Exit the program
401
+
402
+ Clipboard:
403
+ ls | pbcopy - Copy output to clipboard (macOS)
404
+ tree | pbcopy - Works with any command
405
+ config | copy - Alternative for Windows
406
+
407
+ Workflow:
408
+ 1. Type description (e.g., "todo: deploy app")
409
+ 2. AI generates schema preview
410
+ 3. Refine with more instructions if needed
411
+ 4. Type "save" to save or "cancel" to discard
412
+
413
+ Examples:
414
+
415
+ > todo opening company in delaware
416
+
417
+ ┌─ TODO opening company in delaware ───────────────────────────┐
418
+ │ │
419
+ ├── register business name │
420
+ ├── file incorporation papers │
421
+ ├── get EIN number │
422
+ └── Branch: legal │
423
+ └── open business bank account │
424
+ │ │
425
+ └───────────────────────────────────────────────────────────────┘
426
+
427
+ > yandex cloud production infrastructure
428
+
429
+ ┌─────────────────────────────────────────────────────────────┐
430
+ │ Yandex Cloud │
431
+ │ │
432
+ │ ┌──────────────────┐ ┌──────────────────┐ │
433
+ │ │ back-fastapi │ │ admin-next │ │
434
+ │ │ (VM) │ │ (VM) │ │
435
+ │ └────────┬─────────┘ └──────────────────┘ │
436
+ │ │ │
437
+ │ ├──────────────────┬─────────────────┐ │
438
+ │ │ │ │ │
439
+ │ ┌────────▼────────┐ ┌─────▼──────┐ ┌──────▼────────┐ │
440
+ │ │ PostgreSQL │ │ Redis │ │ Cloudflare │ │
441
+ │ │ (Existing DB) │ │ Cluster │ │ R2 Storage │ │
442
+ │ └─────────────────┘ └────────────┘ └───────────────┘ │
443
+ └─────────────────────────────────────────────────────────────┘
444
+
445
+ > architecture production redis web application
446
+
447
+ ┌─ Architecture production redis web application ────────────┐
448
+ │ │
449
+ ├── load-balancer │
450
+ ├── web-servers │
451
+ │ ├── app-server-1 │
452
+ │ ├── app-server-2 │
453
+ │ └── app-server-3 │
454
+ ├── redis │
455
+ │ ├── cache-cluster │
456
+ │ └── session-store │
457
+ └── database │
458
+ ├── postgres-primary │
459
+ └── postgres-replica │
460
+ │ │
461
+ └───────────────────────────────────────────────────────────────┘
462
+
463
+ > kubernetes cluster with clusters postgres and redis
464
+
465
+ ┌─────────────────────────────────────────────────────────────┐
466
+ │ Kubernetes cluster with clusters postgres │
467
+ │ │
468
+ │ ┌──────────────┐ ┌──────────────┐ │
469
+ │ │ postgres │ │ redis │ │
470
+ │ │ │ │ │ │
471
+ │ │ primary-pod │ │ cache-pod-1 │ │
472
+ │ │ replica-pod-1│ │ cache-pod-2 │ │
473
+ │ │ replica-pod-2│ │ │ │
474
+ │ └──────┬───────┘ └──────┬───────┘ │
475
+ │ │ │ │
476
+ │ └──────────┬───────────┘ │
477
+ │ │ │
478
+ │ ┌───────▼────────┐ │
479
+ │ │ worker-zones │ │
480
+ │ │ zone-1 │ │
481
+ │ │ zone-2 │ │
482
+ │ └────────────────┘ │
483
+ └─────────────────────────────────────────────────────────────┘
484
+
485
+ www.rlc.rocks`
486
+ });
487
+ }
488
+ // Save command - save pending schema to .rlc.schema file
489
+ if (command === 'save') {
490
+ if (!exports.sessionState.pending) {
491
+ return wrapResult({ output: 'Nothing to save. Create a schema first.' });
492
+ }
493
+ const schemaPath = (0, nodeConfig_1.saveSchemaFile)(currentPath, exports.sessionState.pending.title, exports.sessionState.pending.content);
494
+ const relativePath = path.relative(config.storagePath, schemaPath);
495
+ const filename = path.basename(schemaPath);
496
+ // Clear session
497
+ exports.sessionState.pending = null;
498
+ exports.sessionState.history = [];
499
+ return wrapResult({ output: `Saved: ${filename}` });
500
+ }
501
+ // Cancel command - discard pending schema
502
+ if (command === 'cancel') {
503
+ if (!exports.sessionState.pending) {
504
+ return wrapResult({ output: 'Nothing to cancel.' });
505
+ }
506
+ exports.sessionState.pending = null;
507
+ exports.sessionState.history = [];
508
+ return wrapResult({ output: 'Discarded pending schema.' });
509
+ }
510
+ // Clean command - clear current directory
511
+ if (command === 'clean') {
512
+ const entries = fs.readdirSync(currentPath);
513
+ const toDelete = entries.filter(e => !e.startsWith('.'));
514
+ if (toDelete.length === 0) {
515
+ return wrapResult({ output: 'Directory is already empty.' });
516
+ }
517
+ // Check for --yes flag to skip confirmation
518
+ if (!parts.includes('--yes') && !parts.includes('-y')) {
519
+ return wrapResult({
520
+ output: `Will delete ${toDelete.length} items:\n${toDelete.join('\n')}\n\nRun "clean --yes" to confirm.`
521
+ });
522
+ }
523
+ for (const entry of toDelete) {
524
+ const entryPath = path.join(currentPath, entry);
525
+ fs.rmSync(entryPath, { recursive: true, force: true });
526
+ }
527
+ return wrapResult({ output: `Deleted ${toDelete.length} items.` });
528
+ }
529
+ // AI generation - store in pending, don't save immediately
530
+ const fullInput = cleanInput;
531
+ // Add user message to history
532
+ exports.sessionState.history.push({ role: 'user', content: fullInput });
533
+ const schema = await (0, claude_1.generateSchemaWithAI)(fullInput, config, signal, exports.sessionState.history);
534
+ if (signal?.aborted) {
535
+ return { output: 'Command cancelled.' };
536
+ }
537
+ if (schema) {
538
+ // Store in pending
539
+ exports.sessionState.pending = {
540
+ title: schema.title,
541
+ content: schema.content,
542
+ tree: schema.tree
543
+ };
544
+ // Add assistant response to history
545
+ exports.sessionState.history.push({ role: 'assistant', content: schema.content });
546
+ const filename = schema.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
547
+ return wrapResult({
548
+ output: `\n${schema.content}\n\n[Type "save" to save as ${filename}.rlc.schema, or refine with more instructions]`
549
+ });
550
+ }
551
+ return wrapResult({ output: 'Could not generate schema. Make sure API key is set.' });
552
+ }