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.
Files changed (2) hide show
  1. package/dist/index.js +73 -130
  2. 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 { setRl } from './commands/ReadlineSingleton.js';
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
- // SHELL
69
- // =====================================================
70
- async function startShell() {
71
- if (inShell)
72
- return;
73
- inShell = true;
74
- // Ensure cursor is visible & blinking
75
- process.stdout.write('\x1b[?25h');
76
- const rl = readline.createInterface({
77
- input: process.stdin,
78
- output: process.stdout,
79
- prompt: 'scai> ',
80
- historySize: 200,
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
- const showCursor = () => {
103
- process.stdout.write('\x1b[?25h');
104
- };
105
- rl.prompt();
106
- showCursor();
107
- rl.on('line', async (line) => {
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
- showCursor();
110
- // =====================================================
111
- // Explicit multiline (""" … """)
112
- // =====================================================
113
- if (multilineBuffer !== null) {
114
- if (line.trim() === '"""') {
115
- const fullQuery = multilineBuffer.join('\n');
116
- multilineBuffer = null;
117
- await runQuery(fullQuery);
118
- rl.prompt();
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
- // Empty line = EXECUTE buffered input
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
- rl.prompt();
153
- showCursor();
154
- });
155
- return;
111
+ await new Promise((resolve) => child.on('exit', resolve));
112
+ continue;
156
113
  }
157
- // =====================================================
158
- // Slash commands
159
- // =====================================================
160
- if (line.trim().startsWith('/')) {
114
+ // --- Slash commands (/)
115
+ if (trimmed.startsWith('/')) {
161
116
  const argvParts = shellQuote
162
- .parse(line.trim().slice(1))
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
- rl.prompt();
175
- showCursor();
176
- return;
136
+ continue;
177
137
  }
178
- // =====================================================
179
- // Otherwise → accumulate input
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.134",
3
+ "version": "0.1.135",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"