roguelike-cli 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/init.js +102 -110
- package/dist/config/config.js +1 -1
- package/dist/interactive/commands.js +2 -2
- package/dist/interactive/index.js +1 -1
- package/package.json +1 -2
- package/src/commands/init.ts +115 -123
- package/src/config/config.ts +1 -1
- package/src/interactive/commands.ts +4 -2
- package/src/interactive/index.ts +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -37,34 +37,13 @@ exports.initCommand = initCommand;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
|
+
const readline = __importStar(require("readline"));
|
|
40
41
|
const config_1 = require("../config/config");
|
|
41
|
-
function question(query) {
|
|
42
|
+
function question(rl, query) {
|
|
42
43
|
return new Promise((resolve) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const str = chunk.toString();
|
|
47
|
-
for (const char of str) {
|
|
48
|
-
if (char === '\n' || char === '\r') {
|
|
49
|
-
process.stdin.removeListener('data', onData);
|
|
50
|
-
process.stdout.write('\n');
|
|
51
|
-
resolve(input);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
else if (char === '\x7f' || char === '\b') {
|
|
55
|
-
// Backspace
|
|
56
|
-
if (input.length > 0) {
|
|
57
|
-
input = input.slice(0, -1);
|
|
58
|
-
process.stdout.write('\b \b');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else if (char >= ' ') {
|
|
62
|
-
input += char;
|
|
63
|
-
process.stdout.write(char);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
process.stdin.on('data', onData);
|
|
44
|
+
rl.question(query, (answer) => {
|
|
45
|
+
resolve(answer);
|
|
46
|
+
});
|
|
68
47
|
});
|
|
69
48
|
}
|
|
70
49
|
// Recursive copy function
|
|
@@ -83,93 +62,106 @@ function copyRecursive(src, dest) {
|
|
|
83
62
|
fs.copyFileSync(src, dest);
|
|
84
63
|
}
|
|
85
64
|
}
|
|
86
|
-
async function initCommand() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
65
|
+
async function initCommand(existingRl) {
|
|
66
|
+
// Create our own readline if not provided, or use existing one
|
|
67
|
+
const rl = existingRl || readline.createInterface({
|
|
68
|
+
input: process.stdin,
|
|
69
|
+
output: process.stdout,
|
|
70
|
+
});
|
|
71
|
+
const shouldCloseRl = !existingRl;
|
|
72
|
+
try {
|
|
73
|
+
console.log('\n╔═══════════════════════════════════════╗');
|
|
74
|
+
console.log('║ ROGUELIKE CLI INITIALIZATION WIZARD ║');
|
|
75
|
+
console.log('╚═══════════════════════════════════════╝\n');
|
|
76
|
+
// Get existing config if any
|
|
77
|
+
const existingConfig = await (0, config_1.initConfig)();
|
|
78
|
+
const oldStoragePath = existingConfig?.storagePath;
|
|
79
|
+
// 1. Root directory
|
|
80
|
+
const defaultRoot = path.join(os.homedir(), '.rlc', 'workspace');
|
|
81
|
+
const rootDirAnswer = await question(rl, `Root directory [${defaultRoot}]: `);
|
|
82
|
+
const rootDir = rootDirAnswer.trim() || defaultRoot;
|
|
83
|
+
// Check if we need to migrate data
|
|
84
|
+
if (oldStoragePath && oldStoragePath !== rootDir && fs.existsSync(oldStoragePath)) {
|
|
85
|
+
const entries = fs.readdirSync(oldStoragePath);
|
|
86
|
+
if (entries.length > 0) {
|
|
87
|
+
const migrateAnswer = await question(rl, `\nMigrate existing data from ${oldStoragePath}? [Y/n]: `);
|
|
88
|
+
if (migrateAnswer.toLowerCase() !== 'n') {
|
|
89
|
+
if (!fs.existsSync(rootDir)) {
|
|
90
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
console.log(`Migrating data...`);
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
const srcPath = path.join(oldStoragePath, entry);
|
|
95
|
+
const destPath = path.join(rootDir, entry);
|
|
96
|
+
copyRecursive(srcPath, destPath);
|
|
97
|
+
}
|
|
98
|
+
console.log(`Migrated ${entries.length} items to ${rootDir}`);
|
|
111
99
|
}
|
|
112
|
-
console.log(`Migrated ${entries.length} items to ${rootDir}`);
|
|
113
100
|
}
|
|
114
101
|
}
|
|
102
|
+
if (!fs.existsSync(rootDir)) {
|
|
103
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
104
|
+
console.log(`Created directory: ${rootDir}`);
|
|
105
|
+
}
|
|
106
|
+
// 2. AI Provider selection
|
|
107
|
+
console.log('\nSelect AI Provider:');
|
|
108
|
+
console.log(' 1. Claude Sonnet 4.5');
|
|
109
|
+
console.log(' 2. Claude Opus 4.5');
|
|
110
|
+
console.log(' 3. GPT-4o (latest)');
|
|
111
|
+
console.log(' 4. Gemini 3 Pro');
|
|
112
|
+
console.log(' 5. Grok (latest)');
|
|
113
|
+
const aiChoice = await question(rl, '\nEnter choice [1-5] (default: 1): ');
|
|
114
|
+
const aiProviders = [
|
|
115
|
+
{ name: 'claude', model: 'claude-sonnet-4-20250514', apiUrl: 'https://api.anthropic.com' },
|
|
116
|
+
{ name: 'claude-opus', model: 'claude-opus-4-20250514', apiUrl: 'https://api.anthropic.com' },
|
|
117
|
+
{ name: 'openai', model: 'gpt-4o', apiUrl: 'https://api.openai.com' },
|
|
118
|
+
{ name: 'gemini', model: 'gemini-3-pro', apiUrl: 'https://generativelanguage.googleapis.com' },
|
|
119
|
+
{ name: 'grok', model: 'grok-beta', apiUrl: 'https://api.x.ai' },
|
|
120
|
+
];
|
|
121
|
+
const selectedIndex = parseInt(aiChoice.trim()) - 1 || 0;
|
|
122
|
+
const selectedProvider = aiProviders[selectedIndex] || aiProviders[0];
|
|
123
|
+
console.log(`Selected: ${selectedProvider.name} (${selectedProvider.model})`);
|
|
124
|
+
// 3. API Key - reuse existing if not provided
|
|
125
|
+
const existingApiKey = existingConfig?.apiKey || '';
|
|
126
|
+
const hasExistingKey = existingApiKey.length > 0;
|
|
127
|
+
const keyPrompt = hasExistingKey
|
|
128
|
+
? `\nAPI key for ${selectedProvider.name} [Enter to keep existing]: `
|
|
129
|
+
: `\nEnter API key for ${selectedProvider.name}: `;
|
|
130
|
+
const apiKeyInput = await question(rl, keyPrompt);
|
|
131
|
+
const apiKey = apiKeyInput.trim() || existingApiKey;
|
|
132
|
+
if (!apiKey) {
|
|
133
|
+
console.log('Warning: API key not set. You can set it later with config:apiKey=<key>');
|
|
134
|
+
}
|
|
135
|
+
else if (apiKeyInput.trim()) {
|
|
136
|
+
console.log('API key saved');
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.log('Using existing API key');
|
|
140
|
+
}
|
|
141
|
+
// Save config
|
|
142
|
+
const config = {
|
|
143
|
+
aiProvider: selectedProvider.name,
|
|
144
|
+
apiKey: apiKey,
|
|
145
|
+
apiUrl: selectedProvider.apiUrl,
|
|
146
|
+
storagePath: rootDir,
|
|
147
|
+
currentPath: rootDir,
|
|
148
|
+
model: selectedProvider.model,
|
|
149
|
+
};
|
|
150
|
+
(0, config_1.saveConfig)(config);
|
|
151
|
+
// Ensure storage directory exists
|
|
152
|
+
if (!fs.existsSync(rootDir)) {
|
|
153
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
console.log('\n╔═══════════════════════════════════════╗');
|
|
156
|
+
console.log('║ INITIALIZATION COMPLETE ║');
|
|
157
|
+
console.log('╚═══════════════════════════════════════╝\n');
|
|
158
|
+
console.log(`Root directory: ${rootDir}`);
|
|
159
|
+
console.log(`AI Provider: ${selectedProvider.name}`);
|
|
160
|
+
console.log(`Model: ${selectedProvider.model}\n`);
|
|
115
161
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// 2. AI Provider selection
|
|
121
|
-
console.log('\nSelect AI Provider:');
|
|
122
|
-
console.log(' 1. Claude Sonnet 4.5');
|
|
123
|
-
console.log(' 2. Claude Opus 4.5');
|
|
124
|
-
console.log(' 3. GPT-4o (latest)');
|
|
125
|
-
console.log(' 4. Gemini 3 Pro');
|
|
126
|
-
console.log(' 5. Grok (latest)');
|
|
127
|
-
const aiChoice = await question('\nEnter choice [1-5] (default: 1): ');
|
|
128
|
-
const aiProviders = [
|
|
129
|
-
{ name: 'claude', model: 'claude-sonnet-4-20250514', apiUrl: 'https://api.anthropic.com' },
|
|
130
|
-
{ name: 'claude-opus', model: 'claude-opus-4-20250514', apiUrl: 'https://api.anthropic.com' },
|
|
131
|
-
{ name: 'openai', model: 'gpt-4o', apiUrl: 'https://api.openai.com' },
|
|
132
|
-
{ name: 'gemini', model: 'gemini-3-pro', apiUrl: 'https://generativelanguage.googleapis.com' },
|
|
133
|
-
{ name: 'grok', model: 'grok-beta', apiUrl: 'https://api.x.ai' },
|
|
134
|
-
];
|
|
135
|
-
const selectedIndex = parseInt(aiChoice.trim()) - 1 || 0;
|
|
136
|
-
const selectedProvider = aiProviders[selectedIndex] || aiProviders[0];
|
|
137
|
-
console.log(`Selected: ${selectedProvider.name} (${selectedProvider.model})`);
|
|
138
|
-
// 3. API Key - reuse existing if not provided
|
|
139
|
-
const existingApiKey = existingConfig?.apiKey || '';
|
|
140
|
-
const hasExistingKey = existingApiKey.length > 0;
|
|
141
|
-
const keyPrompt = hasExistingKey
|
|
142
|
-
? `\nAPI key for ${selectedProvider.name} [press Enter to keep existing]: `
|
|
143
|
-
: `\nEnter API key for ${selectedProvider.name}: `;
|
|
144
|
-
const apiKeyInput = await question(keyPrompt);
|
|
145
|
-
const apiKey = apiKeyInput.trim() || existingApiKey;
|
|
146
|
-
if (!apiKey) {
|
|
147
|
-
console.log('Warning: API key not set. You can set it later with config:apiKey=<key>');
|
|
148
|
-
}
|
|
149
|
-
else if (apiKeyInput.trim()) {
|
|
150
|
-
console.log('API key saved');
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
console.log('Using existing API key');
|
|
154
|
-
}
|
|
155
|
-
// Save config
|
|
156
|
-
const config = {
|
|
157
|
-
aiProvider: selectedProvider.name,
|
|
158
|
-
apiKey: apiKey,
|
|
159
|
-
apiUrl: selectedProvider.apiUrl,
|
|
160
|
-
storagePath: rootDir,
|
|
161
|
-
currentPath: rootDir,
|
|
162
|
-
model: selectedProvider.model,
|
|
163
|
-
};
|
|
164
|
-
(0, config_1.saveConfig)(config);
|
|
165
|
-
// Ensure storage directory exists
|
|
166
|
-
if (!fs.existsSync(rootDir)) {
|
|
167
|
-
fs.mkdirSync(rootDir, { recursive: true });
|
|
162
|
+
finally {
|
|
163
|
+
if (shouldCloseRl) {
|
|
164
|
+
rl.close();
|
|
165
|
+
}
|
|
168
166
|
}
|
|
169
|
-
console.log('\n╔═══════════════════════════════════════╗');
|
|
170
|
-
console.log('║ INITIALIZATION COMPLETE ║');
|
|
171
|
-
console.log('╚═══════════════════════════════════════╝\n');
|
|
172
|
-
console.log(`Root directory: ${rootDir}`);
|
|
173
|
-
console.log(`AI Provider: ${selectedProvider.name}`);
|
|
174
|
-
console.log(`Model: ${selectedProvider.model}\n`);
|
|
175
167
|
}
|
package/dist/config/config.js
CHANGED
|
@@ -41,7 +41,7 @@ const fs = __importStar(require("fs"));
|
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
const CONFIG_FILE = path.join(os.homedir(), '.rlc', 'config.json');
|
|
44
|
-
const DEFAULT_STORAGE = path.join(os.homedir(), '.rlc', '
|
|
44
|
+
const DEFAULT_STORAGE = path.join(os.homedir(), '.rlc', 'workspace');
|
|
45
45
|
async function initConfig() {
|
|
46
46
|
const configDir = path.dirname(CONFIG_FILE);
|
|
47
47
|
if (!fs.existsSync(configDir)) {
|
|
@@ -263,7 +263,7 @@ function copyRecursive(src, dest) {
|
|
|
263
263
|
fs.copyFileSync(src, dest);
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
-
async function processCommand(input, currentPath, config, signal) {
|
|
266
|
+
async function processCommand(input, currentPath, config, signal, rl) {
|
|
267
267
|
// Check for clipboard pipe
|
|
268
268
|
const clipboardPipe = /\s*\|\s*(pbcopy|copy|clip)\s*$/i;
|
|
269
269
|
const shouldCopy = clipboardPipe.test(input);
|
|
@@ -460,7 +460,7 @@ async function processCommand(input, currentPath, config, signal) {
|
|
|
460
460
|
}
|
|
461
461
|
if (command === 'init') {
|
|
462
462
|
const { initCommand } = await Promise.resolve().then(() => __importStar(require('../commands/init')));
|
|
463
|
-
await initCommand();
|
|
463
|
+
await initCommand(rl);
|
|
464
464
|
return { output: 'Initialization complete. You can now use rlc.\n', reloadConfig: true };
|
|
465
465
|
}
|
|
466
466
|
if (command === 'cd') {
|
|
@@ -136,7 +136,7 @@ async function startInteractive(initialConfig) {
|
|
|
136
136
|
isProcessingCommand = true;
|
|
137
137
|
const abortController = currentCommandAbortController;
|
|
138
138
|
try {
|
|
139
|
-
const result = await (0, commands_1.processCommand)(trimmed, state.currentPath, config, abortController.signal);
|
|
139
|
+
const result = await (0, commands_1.processCommand)(trimmed, state.currentPath, config, abortController.signal, rl);
|
|
140
140
|
if (abortController.signal.aborted) {
|
|
141
141
|
rl.prompt();
|
|
142
142
|
return;
|
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -1,36 +1,14 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
|
+
import * as readline from 'readline';
|
|
4
5
|
import { Config, saveConfig, initConfig } from '../config/config';
|
|
5
6
|
|
|
6
|
-
function question(query: string): Promise<string> {
|
|
7
|
+
function question(rl: readline.Interface, query: string): Promise<string> {
|
|
7
8
|
return new Promise((resolve) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const onData = (chunk: Buffer) => {
|
|
12
|
-
const str = chunk.toString();
|
|
13
|
-
|
|
14
|
-
for (const char of str) {
|
|
15
|
-
if (char === '\n' || char === '\r') {
|
|
16
|
-
process.stdin.removeListener('data', onData);
|
|
17
|
-
process.stdout.write('\n');
|
|
18
|
-
resolve(input);
|
|
19
|
-
return;
|
|
20
|
-
} else if (char === '\x7f' || char === '\b') {
|
|
21
|
-
// Backspace
|
|
22
|
-
if (input.length > 0) {
|
|
23
|
-
input = input.slice(0, -1);
|
|
24
|
-
process.stdout.write('\b \b');
|
|
25
|
-
}
|
|
26
|
-
} else if (char >= ' ') {
|
|
27
|
-
input += char;
|
|
28
|
-
process.stdout.write(char);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
process.stdin.on('data', onData);
|
|
9
|
+
rl.question(query, (answer) => {
|
|
10
|
+
resolve(answer);
|
|
11
|
+
});
|
|
34
12
|
});
|
|
35
13
|
}
|
|
36
14
|
|
|
@@ -52,108 +30,122 @@ function copyRecursive(src: string, dest: string): void {
|
|
|
52
30
|
}
|
|
53
31
|
}
|
|
54
32
|
|
|
55
|
-
export async function initCommand(): Promise<void> {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
33
|
+
export async function initCommand(existingRl?: readline.Interface): Promise<void> {
|
|
34
|
+
// Create our own readline if not provided, or use existing one
|
|
35
|
+
const rl = existingRl || readline.createInterface({
|
|
36
|
+
input: process.stdin,
|
|
37
|
+
output: process.stdout,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const shouldCloseRl = !existingRl;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
console.log('\n╔═══════════════════════════════════════╗');
|
|
44
|
+
console.log('║ ROGUELIKE CLI INITIALIZATION WIZARD ║');
|
|
45
|
+
console.log('╚═══════════════════════════════════════╝\n');
|
|
46
|
+
|
|
47
|
+
// Get existing config if any
|
|
48
|
+
const existingConfig = await initConfig();
|
|
49
|
+
const oldStoragePath = existingConfig?.storagePath;
|
|
50
|
+
|
|
51
|
+
// 1. Root directory
|
|
52
|
+
const defaultRoot = path.join(os.homedir(), '.rlc', 'workspace');
|
|
53
|
+
const rootDirAnswer = await question(rl, `Root directory [${defaultRoot}]: `);
|
|
54
|
+
const rootDir = rootDirAnswer.trim() || defaultRoot;
|
|
55
|
+
|
|
56
|
+
// Check if we need to migrate data
|
|
57
|
+
if (oldStoragePath && oldStoragePath !== rootDir && fs.existsSync(oldStoragePath)) {
|
|
58
|
+
const entries = fs.readdirSync(oldStoragePath);
|
|
59
|
+
if (entries.length > 0) {
|
|
60
|
+
const migrateAnswer = await question(rl, `\nMigrate existing data from ${oldStoragePath}? [Y/n]: `);
|
|
61
|
+
if (migrateAnswer.toLowerCase() !== 'n') {
|
|
62
|
+
if (!fs.existsSync(rootDir)) {
|
|
63
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`Migrating data...`);
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const srcPath = path.join(oldStoragePath, entry);
|
|
69
|
+
const destPath = path.join(rootDir, entry);
|
|
70
|
+
copyRecursive(srcPath, destPath);
|
|
71
|
+
}
|
|
72
|
+
console.log(`Migrated ${entries.length} items to ${rootDir}`);
|
|
84
73
|
}
|
|
85
|
-
console.log(`Migrated ${entries.length} items to ${rootDir}`);
|
|
86
74
|
}
|
|
87
75
|
}
|
|
88
|
-
}
|
|
89
76
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
if (!fs.existsSync(rootDir)) {
|
|
78
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
79
|
+
console.log(`Created directory: ${rootDir}`);
|
|
80
|
+
}
|
|
94
81
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
82
|
+
// 2. AI Provider selection
|
|
83
|
+
console.log('\nSelect AI Provider:');
|
|
84
|
+
console.log(' 1. Claude Sonnet 4.5');
|
|
85
|
+
console.log(' 2. Claude Opus 4.5');
|
|
86
|
+
console.log(' 3. GPT-4o (latest)');
|
|
87
|
+
console.log(' 4. Gemini 3 Pro');
|
|
88
|
+
console.log(' 5. Grok (latest)');
|
|
89
|
+
|
|
90
|
+
const aiChoice = await question(rl, '\nEnter choice [1-5] (default: 1): ');
|
|
91
|
+
const aiProviders = [
|
|
92
|
+
{ name: 'claude', model: 'claude-sonnet-4-20250514', apiUrl: 'https://api.anthropic.com' },
|
|
93
|
+
{ name: 'claude-opus', model: 'claude-opus-4-20250514', apiUrl: 'https://api.anthropic.com' },
|
|
94
|
+
{ name: 'openai', model: 'gpt-4o', apiUrl: 'https://api.openai.com' },
|
|
95
|
+
{ name: 'gemini', model: 'gemini-3-pro', apiUrl: 'https://generativelanguage.googleapis.com' },
|
|
96
|
+
{ name: 'grok', model: 'grok-beta', apiUrl: 'https://api.x.ai' },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const selectedIndex = parseInt(aiChoice.trim()) - 1 || 0;
|
|
100
|
+
const selectedProvider = aiProviders[selectedIndex] || aiProviders[0];
|
|
101
|
+
|
|
102
|
+
console.log(`Selected: ${selectedProvider.name} (${selectedProvider.model})`);
|
|
103
|
+
|
|
104
|
+
// 3. API Key - reuse existing if not provided
|
|
105
|
+
const existingApiKey = existingConfig?.apiKey || '';
|
|
106
|
+
const hasExistingKey = existingApiKey.length > 0;
|
|
107
|
+
const keyPrompt = hasExistingKey
|
|
108
|
+
? `\nAPI key for ${selectedProvider.name} [Enter to keep existing]: `
|
|
109
|
+
: `\nEnter API key for ${selectedProvider.name}: `;
|
|
110
|
+
|
|
111
|
+
const apiKeyInput = await question(rl, keyPrompt);
|
|
112
|
+
const apiKey = apiKeyInput.trim() || existingApiKey;
|
|
113
|
+
|
|
114
|
+
if (!apiKey) {
|
|
115
|
+
console.log('Warning: API key not set. You can set it later with config:apiKey=<key>');
|
|
116
|
+
} else if (apiKeyInput.trim()) {
|
|
117
|
+
console.log('API key saved');
|
|
118
|
+
} else {
|
|
119
|
+
console.log('Using existing API key');
|
|
120
|
+
}
|
|
134
121
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
saveConfig(config);
|
|
146
|
-
|
|
147
|
-
// Ensure storage directory exists
|
|
148
|
-
if (!fs.existsSync(rootDir)) {
|
|
149
|
-
fs.mkdirSync(rootDir, { recursive: true });
|
|
150
|
-
}
|
|
122
|
+
// Save config
|
|
123
|
+
const config: Config = {
|
|
124
|
+
aiProvider: selectedProvider.name as any,
|
|
125
|
+
apiKey: apiKey,
|
|
126
|
+
apiUrl: selectedProvider.apiUrl,
|
|
127
|
+
storagePath: rootDir,
|
|
128
|
+
currentPath: rootDir,
|
|
129
|
+
model: selectedProvider.model,
|
|
130
|
+
};
|
|
151
131
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
132
|
+
saveConfig(config);
|
|
133
|
+
|
|
134
|
+
// Ensure storage directory exists
|
|
135
|
+
if (!fs.existsSync(rootDir)) {
|
|
136
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log('\n╔═══════════════════════════════════════╗');
|
|
140
|
+
console.log('║ INITIALIZATION COMPLETE ║');
|
|
141
|
+
console.log('╚═══════════════════════════════════════╝\n');
|
|
142
|
+
console.log(`Root directory: ${rootDir}`);
|
|
143
|
+
console.log(`AI Provider: ${selectedProvider.name}`);
|
|
144
|
+
console.log(`Model: ${selectedProvider.model}\n`);
|
|
145
|
+
} finally {
|
|
146
|
+
if (shouldCloseRl) {
|
|
147
|
+
rl.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
158
150
|
}
|
|
159
151
|
|
package/src/config/config.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface Config {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const CONFIG_FILE = path.join(os.homedir(), '.rlc', 'config.json');
|
|
15
|
-
const DEFAULT_STORAGE = path.join(os.homedir(), '.rlc', '
|
|
15
|
+
const DEFAULT_STORAGE = path.join(os.homedir(), '.rlc', 'workspace');
|
|
16
16
|
|
|
17
17
|
export async function initConfig(): Promise<Config | null> {
|
|
18
18
|
const configDir = path.dirname(CONFIG_FILE);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
|
+
import * as readline from 'readline';
|
|
3
4
|
import { execSync } from 'child_process';
|
|
4
5
|
import { Config } from '../config/config';
|
|
5
6
|
import { listSchemas, navigateToNode, getTree } from '../storage/storage';
|
|
@@ -286,7 +287,8 @@ export async function processCommand(
|
|
|
286
287
|
input: string,
|
|
287
288
|
currentPath: string,
|
|
288
289
|
config: Config,
|
|
289
|
-
signal?: AbortSignal
|
|
290
|
+
signal?: AbortSignal,
|
|
291
|
+
rl?: readline.Interface
|
|
290
292
|
): Promise<CommandResult> {
|
|
291
293
|
// Check for clipboard pipe
|
|
292
294
|
const clipboardPipe = /\s*\|\s*(pbcopy|copy|clip)\s*$/i;
|
|
@@ -518,7 +520,7 @@ export async function processCommand(
|
|
|
518
520
|
|
|
519
521
|
if (command === 'init') {
|
|
520
522
|
const { initCommand } = await import('../commands/init');
|
|
521
|
-
await initCommand();
|
|
523
|
+
await initCommand(rl);
|
|
522
524
|
return { output: 'Initialization complete. You can now use rlc.\n', reloadConfig: true };
|
|
523
525
|
}
|
|
524
526
|
|
package/src/interactive/index.ts
CHANGED
|
@@ -124,7 +124,7 @@ export async function startInteractive(initialConfig: Config): Promise<void> {
|
|
|
124
124
|
const abortController = currentCommandAbortController;
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
|
-
const result = await processCommand(trimmed, state.currentPath, config, abortController.signal);
|
|
127
|
+
const result = await processCommand(trimmed, state.currentPath, config, abortController.signal, rl);
|
|
128
128
|
|
|
129
129
|
if (abortController.signal.aborted) {
|
|
130
130
|
rl.prompt();
|