sapper-iq 1.1.0 → 1.1.2

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/package.json +1 -1
  2. package/sapper.mjs +72 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "AI-powered development assistant that executes commands and builds projects",
5
5
  "main": "sapper.mjs",
6
6
  "bin": {
package/sapper.mjs CHANGED
@@ -19,6 +19,18 @@ process.on('unhandledRejection', (reason) => {
19
19
  console.error(chalk.red('\n❌ Unhandled rejection:'), reason);
20
20
  });
21
21
 
22
+ // Prevent Ctrl+C from killing the whole process
23
+ let ctrlCCount = 0;
24
+ process.on('SIGINT', () => {
25
+ ctrlCCount++;
26
+ if (ctrlCCount >= 2) {
27
+ console.log(chalk.red('\nForce quitting...'));
28
+ process.exit(1);
29
+ }
30
+ console.log(chalk.yellow('\n\nUse "exit" to close Sapper safely, or Ctrl+C again to force quit.'));
31
+ setTimeout(() => { ctrlCCount = 0; }, 2000); // Reset after 2 seconds
32
+ });
33
+
22
34
  // Initialize versioning
23
35
  let CURRENT_VERSION = "1.1.0";
24
36
  try {
@@ -37,31 +49,27 @@ let rl = readline.createInterface({
37
49
  historySize: 100
38
50
  });
39
51
 
40
- async function safeQuestion(query) {
41
- return new Promise((resolve, reject) => {
42
- process.stdout.write(query);
43
- rl.once('line', (answer) => { resolve(answer.trim()); });
44
- rl.once('close', () => {
45
- // Readline was closed - recreate it and try again
46
- recreateReadline();
47
- resolve(''); // Return empty string to continue the loop
48
- });
49
- rl.once('error', (err) => {
50
- console.error(chalk.red('Readline error:'), err.message);
51
- recreateReadline();
52
- resolve('');
53
- });
54
- });
55
- }
56
-
57
52
  function recreateReadline() {
58
- rl.close();
53
+ if (rl) rl.close();
59
54
  rl = readline.createInterface({
60
55
  input: process.stdin,
61
56
  output: process.stdout,
62
57
  terminal: true,
63
58
  historySize: 100
64
59
  });
60
+ // Force resume stdin to keep process alive
61
+ process.stdin.resume();
62
+ }
63
+
64
+ async function safeQuestion(query) {
65
+ // Ensure we are ready to receive input
66
+ if (rl.closed) recreateReadline();
67
+
68
+ return new Promise((resolve) => {
69
+ rl.question(query, (answer) => {
70
+ resolve(answer ? answer.trim() : '');
71
+ });
72
+ });
65
73
  }
66
74
 
67
75
  // Directories to ignore when listing files
@@ -128,8 +136,11 @@ const tools = {
128
136
  stdio: 'inherit', shell: useShell
129
137
  });
130
138
  proc.on('close', (code) => {
131
- recreateReadline();
132
- resolve(`Command completed with code ${code}`);
139
+ // Delay slightly to let terminal settle
140
+ setTimeout(() => {
141
+ recreateReadline();
142
+ resolve(`Command completed with code ${code}`);
143
+ }, 100);
133
144
  });
134
145
  });
135
146
  }
@@ -148,6 +159,27 @@ const tools = {
148
159
  });
149
160
  return filtered.length > 0 ? filtered.join('\n') : '(empty or all files filtered)';
150
161
  } catch (e) { return `Error: ${e.message}`; }
162
+ },
163
+ search: (pattern) => {
164
+ return new Promise((resolve) => {
165
+ const excludeDirs = Array.from(IGNORE_DIRS).join(',');
166
+ // Use grep to search for pattern, excluding ignored directories
167
+ const cmd = `grep -rEin "${pattern.replace(/"/g, '\\"')}" . --exclude-dir={${excludeDirs}} --include="*.{js,ts,jsx,tsx,py,java,go,rs,rb,php,c,cpp,h,css,scss,html,json,md,txt,yml,yaml,toml,sh}" 2>/dev/null | head -50`;
168
+
169
+ const proc = spawn('sh', ['-c', cmd], { cwd: process.cwd() });
170
+ let output = '';
171
+
172
+ proc.stdout.on('data', (data) => { output += data.toString(); });
173
+ proc.stderr.on('data', (data) => { output += data.toString(); });
174
+
175
+ proc.on('close', () => {
176
+ if (output.trim()) {
177
+ resolve(`Found matches:\n${output.trim()}`);
178
+ } else {
179
+ resolve(`No matches found for: ${pattern}`);
180
+ }
181
+ });
182
+ });
151
183
  }
152
184
  };
153
185
 
@@ -221,16 +253,31 @@ READING GUIDELINES:
221
253
  TOOL FORMAT (CRITICAL - FOLLOW EXACTLY):
222
254
  ✅ CORRECT: [TOOL:LIST].[/TOOL]
223
255
  ✅ CORRECT: [TOOL:READ]./file.js[/TOOL]
256
+ ✅ CORRECT: [TOOL:SEARCH]functionName[/TOOL]
224
257
  ✅ CORRECT: [TOOL:WRITE]./file.js]full content here[/TOOL]
225
258
  ✅ CORRECT: [TOOL:PATCH]./file.js]old code|||new code[/TOOL]
226
259
  ❌ WRONG: [TOOL:LIST].[/] - missing TOOL at end!
227
260
 
261
+ AVAILABLE TOOLS:
262
+ - LIST: List directory contents
263
+ - READ: Read file contents
264
+ - SEARCH: Find text/code across all files (grep-like, returns file:line:match)
265
+ - WRITE: Create or overwrite entire file (requires confirmation)
266
+ - PATCH: Make small edits to existing file (requires confirmation)
267
+ - MKDIR: Create directory
268
+ - SHELL: Run terminal command (requires confirmation)
269
+
270
+ SMART WORKFLOW:
271
+ 1. For unknown codebases: [TOOL:SEARCH]main|index|app[/TOOL] to find entry points
272
+ 2. To find where something is defined: [TOOL:SEARCH]function myFunc[/TOOL]
273
+ 3. SEARCH returns file paths + line numbers - then READ specific files
274
+
228
275
  PATCH vs WRITE:
229
276
  - Use PATCH for small changes (1-10 lines): [TOOL:PATCH]path]old|||new[/TOOL]
230
277
  - Use WRITE only for new files or complete rewrites
231
278
 
232
279
  WORKFLOW:
233
- 1. LIST directory → 2. READ files (as many as needed) → 3. ANALYZE and RESPOND`
280
+ 1. LIST or SEARCH → 2. READ relevant files → 3. ANALYZE and RESPOND`
234
281
  }];
235
282
  }
236
283
 
@@ -360,6 +407,7 @@ WORKFLOW:
360
407
  result = 'Error: PATCH requires format [TOOL:PATCH]path]OLD_TEXT|||NEW_TEXT[/TOOL]';
361
408
  }
362
409
  }
410
+ else if (type.toLowerCase() === 'search') result = await tools.search(path);
363
411
  else if (type.toLowerCase() === 'shell') result = await tools.shell(path);
364
412
 
365
413
  messages.push({ role: 'user', content: `RESULT (${path}): ${result}` });
@@ -391,4 +439,7 @@ WORKFLOW:
391
439
  }
392
440
  }
393
441
 
442
+ // Keep-alive interval - prevents Node from exiting when event loop is empty
443
+ setInterval(() => {}, 1000);
444
+
394
445
  runSapper();