scai 0.1.134 → 0.1.135
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/index.js +73 -130
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import readline from 'readline';
|
|
3
2
|
import { spawn } from 'child_process';
|
|
4
3
|
import { createRequire } from 'module';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import path from 'path';
|
|
5
7
|
const require = createRequire(import.meta.url);
|
|
6
8
|
const shellQuote = require('shell-quote');
|
|
7
9
|
import { createProgram as cmdFactory, withContext } from './commands/factory.js';
|
|
8
10
|
import { runAskCommand } from './commands/AskCmd.js';
|
|
9
|
-
import
|
|
11
|
+
import enquirer from 'enquirer';
|
|
12
|
+
const { prompt } = enquirer;
|
|
10
13
|
const program = cmdFactory();
|
|
11
|
-
let inShell = false;
|
|
12
14
|
const customCommands = {};
|
|
13
|
-
//
|
|
14
|
-
// TEST QUERIES
|
|
15
|
-
// =====================================================
|
|
15
|
+
// ---------------- Test Queries ----------------
|
|
16
16
|
const testQueries = [
|
|
17
17
|
'please write me comprehensive comments for spatialmap.js and typescript.ts files',
|
|
18
18
|
'refactor spatialmap.js to improve readability and reduce nesting',
|
|
@@ -32,18 +32,14 @@ const testQueries = [
|
|
|
32
32
|
'Are there any security vulnerabilities in our dependencies?',
|
|
33
33
|
'Is there any dead code we can safely remove?'
|
|
34
34
|
];
|
|
35
|
-
//
|
|
36
|
-
// HELPERS
|
|
37
|
-
// =====================================================
|
|
35
|
+
// ---------------- Helpers ----------------
|
|
38
36
|
function pickRandom(items) {
|
|
39
37
|
return items[Math.floor(Math.random() * items.length)];
|
|
40
38
|
}
|
|
41
39
|
async function runQuery(query) {
|
|
42
40
|
await withContext(() => runAskCommand(query));
|
|
43
41
|
}
|
|
44
|
-
//
|
|
45
|
-
// BUILT-IN COMMANDS
|
|
46
|
-
// =====================================================
|
|
42
|
+
// ---------------- Built-in Commands ----------------
|
|
47
43
|
customCommands.test = async () => {
|
|
48
44
|
await runQuery(testQueries[0]);
|
|
49
45
|
};
|
|
@@ -58,148 +54,96 @@ customCommands.exit = async () => {
|
|
|
58
54
|
};
|
|
59
55
|
customCommands.q = customCommands.exit;
|
|
60
56
|
customCommands.quit = customCommands.exit;
|
|
61
|
-
//
|
|
62
|
-
// EXTENSION API
|
|
63
|
-
// =====================================================
|
|
57
|
+
// ---------------- Extension API ----------------
|
|
64
58
|
export function registerCommand(name, fn) {
|
|
65
59
|
customCommands[name] = fn;
|
|
66
60
|
}
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
setRl(rl);
|
|
83
|
-
// Explicit """ multiline
|
|
84
|
-
let multilineBuffer = null;
|
|
85
|
-
// Normal buffered input (enter-to-execute)
|
|
86
|
-
let inputBuffer = [];
|
|
87
|
-
// --- Buffered paste flush to fix "double Enter" issue
|
|
88
|
-
let flushHandle = null;
|
|
89
|
-
const flushInputBuffer = () => {
|
|
90
|
-
if (flushHandle)
|
|
91
|
-
return; // already scheduled
|
|
92
|
-
flushHandle = setImmediate(async () => {
|
|
93
|
-
flushHandle = null;
|
|
94
|
-
const fullQuery = inputBuffer.join('\n').trim();
|
|
95
|
-
inputBuffer = [];
|
|
96
|
-
if (fullQuery) {
|
|
97
|
-
await runQuery(fullQuery);
|
|
61
|
+
// ---------------- Editor Helper ----------------
|
|
62
|
+
function editInEditorAsync(initialContent = '') {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const tmpFile = path.join(os.tmpdir(), `scai_input_${Date.now()}.txt`);
|
|
65
|
+
fs.writeFileSync(tmpFile, initialContent);
|
|
66
|
+
const editor = process.env.EDITOR || 'vi';
|
|
67
|
+
const child = spawn(editor, [tmpFile], { stdio: 'inherit' });
|
|
68
|
+
child.on('exit', (code) => {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(tmpFile, 'utf-8');
|
|
71
|
+
fs.unlinkSync(tmpFile);
|
|
72
|
+
resolve(content);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
reject(err);
|
|
98
76
|
}
|
|
99
|
-
rl.prompt();
|
|
100
77
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
78
|
+
child.on('error', (err) => reject(err));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// ---------------- REPL / Shell ----------------
|
|
82
|
+
async function startShell() {
|
|
83
|
+
console.log(`
|
|
84
|
+
Welcome to SCAI shell!
|
|
85
|
+
- Type your query directly for short commands or questions.
|
|
86
|
+
- Use /command for built-in or custom commands.
|
|
87
|
+
- /edit for pasting long queries, multi-line code, or anything complex,
|
|
88
|
+
to open your configured editor (from $EDITOR) where you can freely paste,
|
|
89
|
+
edit, and save your input before executing.
|
|
90
|
+
- Use !command to run terminal commands.
|
|
91
|
+
`);
|
|
92
|
+
while (true) {
|
|
108
93
|
try {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
showCursor();
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
multilineBuffer.push(line);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (line.trim() === '"""') {
|
|
126
|
-
multilineBuffer = [];
|
|
127
|
-
console.log('(multiline input — end with """)');
|
|
128
|
-
return;
|
|
94
|
+
const { line } = (await prompt({
|
|
95
|
+
type: 'input',
|
|
96
|
+
name: 'line',
|
|
97
|
+
message: '\nWrite your query:\n',
|
|
98
|
+
validate: (input) => !!input.trim() || 'Please enter something',
|
|
99
|
+
}));
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
// --- Exit immediately
|
|
102
|
+
if (['exit', 'quit', 'q'].includes(trimmed)) {
|
|
103
|
+
await customCommands.exit();
|
|
129
104
|
}
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (line.trim() === '') {
|
|
134
|
-
const fullQuery = inputBuffer.join('\n').trim();
|
|
135
|
-
inputBuffer = [];
|
|
136
|
-
if (fullQuery) {
|
|
137
|
-
await runQuery(fullQuery);
|
|
138
|
-
}
|
|
139
|
-
rl.prompt();
|
|
140
|
-
showCursor();
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
// =====================================================
|
|
144
|
-
// Shell command
|
|
145
|
-
// =====================================================
|
|
146
|
-
if (line.trim().startsWith('!')) {
|
|
147
|
-
const child = spawn(line.trim().slice(1), {
|
|
105
|
+
// --- Shell commands (!)
|
|
106
|
+
if (trimmed.startsWith('!')) {
|
|
107
|
+
const child = spawn(trimmed.slice(1), {
|
|
148
108
|
shell: true,
|
|
149
109
|
stdio: 'inherit',
|
|
150
110
|
});
|
|
151
|
-
child.on('exit',
|
|
152
|
-
|
|
153
|
-
showCursor();
|
|
154
|
-
});
|
|
155
|
-
return;
|
|
111
|
+
await new Promise((resolve) => child.on('exit', resolve));
|
|
112
|
+
continue;
|
|
156
113
|
}
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
// =====================================================
|
|
160
|
-
if (line.trim().startsWith('/')) {
|
|
114
|
+
// --- Slash commands (/)
|
|
115
|
+
if (trimmed.startsWith('/')) {
|
|
161
116
|
const argvParts = shellQuote
|
|
162
|
-
.parse(
|
|
163
|
-
.map((tok) => typeof tok === 'object'
|
|
164
|
-
? tok.op ?? tok.pattern ?? ''
|
|
165
|
-
: String(tok))
|
|
117
|
+
.parse(trimmed.slice(1))
|
|
118
|
+
.map((tok) => typeof tok === 'object' ? tok.op ?? tok.pattern ?? '' : String(tok))
|
|
166
119
|
.filter(Boolean);
|
|
167
120
|
const cmdName = argvParts[0];
|
|
121
|
+
// Special case: open editor
|
|
122
|
+
if (cmdName === 'edit') {
|
|
123
|
+
const content = await editInEditorAsync();
|
|
124
|
+
const trimmedContent = content.trim();
|
|
125
|
+
if (trimmedContent) {
|
|
126
|
+
await withContext(() => runAskCommand(trimmedContent));
|
|
127
|
+
}
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
168
130
|
if (customCommands[cmdName]) {
|
|
169
131
|
await customCommands[cmdName]();
|
|
170
132
|
}
|
|
171
133
|
else {
|
|
172
134
|
await program.parseAsync(argvParts, { from: 'user' });
|
|
173
135
|
}
|
|
174
|
-
|
|
175
|
-
showCursor();
|
|
176
|
-
return;
|
|
136
|
+
continue;
|
|
177
137
|
}
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
// =====================================================
|
|
181
|
-
inputBuffer.push(line);
|
|
182
|
-
flushInputBuffer(); // schedule auto-flush after paste ends
|
|
138
|
+
// --- Otherwise → treat as normal query
|
|
139
|
+
await runQuery(trimmed);
|
|
183
140
|
}
|
|
184
141
|
catch (err) {
|
|
185
142
|
console.error('REPL error:', err instanceof Error ? err.stack : err);
|
|
186
|
-
rl.prompt();
|
|
187
|
-
showCursor();
|
|
188
143
|
}
|
|
189
|
-
}
|
|
190
|
-
rl.on('close', () => {
|
|
191
|
-
showCursor();
|
|
192
|
-
console.log('Bye!');
|
|
193
|
-
process.exit(0);
|
|
194
|
-
});
|
|
195
|
-
process.on('SIGINT', () => {
|
|
196
|
-
console.log('\nExiting REPL...');
|
|
197
|
-
showCursor();
|
|
198
|
-
rl.close();
|
|
199
|
-
});
|
|
200
|
-
process.on('exit', showCursor);
|
|
144
|
+
}
|
|
201
145
|
}
|
|
202
|
-
// ---------------- Main
|
|
146
|
+
// ---------------- Main ----------------
|
|
203
147
|
async function main() {
|
|
204
148
|
process.on('unhandledRejection', (reason) => console.error('Unhandled Rejection:', reason));
|
|
205
149
|
process.on('uncaughtException', (err) => {
|
|
@@ -208,8 +152,7 @@ async function main() {
|
|
|
208
152
|
});
|
|
209
153
|
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
210
154
|
const args = process.argv.slice(2);
|
|
211
|
-
if (isInteractive &&
|
|
212
|
-
(args.length === 0 || (args.length === 1 && args[0] === 'shell'))) {
|
|
155
|
+
if (isInteractive && (args.length === 0 || (args.length === 1 && args[0] === 'shell'))) {
|
|
213
156
|
await startShell();
|
|
214
157
|
return;
|
|
215
158
|
}
|