sapper-iq 1.1.1 → 1.1.3

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 +51 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
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,31 @@ 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
+
34
+ // Reset terminal state - fixes "ghost input" after shell commands or AI streaming
35
+ function resetTerminal() {
36
+ if (process.stdin.isTTY) {
37
+ try {
38
+ process.stdin.setRawMode(false); // Disable raw mode
39
+ process.stdin.pause(); // Pause the stream
40
+ process.stdin.resume(); // Resume to clear buffers
41
+ } catch (e) {
42
+ // Ignore errors if terminal is in weird state
43
+ }
44
+ }
45
+ }
46
+
22
47
  // Initialize versioning
23
48
  let CURRENT_VERSION = "1.1.0";
24
49
  try {
@@ -37,31 +62,27 @@ let rl = readline.createInterface({
37
62
  historySize: 100
38
63
  });
39
64
 
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
65
  function recreateReadline() {
58
- rl.close();
66
+ if (rl) rl.close();
59
67
  rl = readline.createInterface({
60
68
  input: process.stdin,
61
69
  output: process.stdout,
62
70
  terminal: true,
63
71
  historySize: 100
64
72
  });
73
+ // Force resume stdin to keep process alive
74
+ process.stdin.resume();
75
+ }
76
+
77
+ async function safeQuestion(query) {
78
+ resetTerminal(); // Clear terminal state before asking
79
+ if (rl.closed) recreateReadline();
80
+
81
+ return new Promise((resolve) => {
82
+ rl.question(query, (answer) => {
83
+ resolve(answer ? answer.trim() : '');
84
+ });
85
+ });
65
86
  }
66
87
 
67
88
  // Directories to ignore when listing files
@@ -128,8 +149,15 @@ const tools = {
128
149
  stdio: 'inherit', shell: useShell
129
150
  });
130
151
  proc.on('close', (code) => {
131
- recreateReadline();
132
- resolve(`Command completed with code ${code}`);
152
+ // Crucial: give control back to Node
153
+ if (process.stdin.isTTY) {
154
+ try { process.stdin.setRawMode(false); } catch (e) {}
155
+ }
156
+ // Delay slightly to let terminal settle
157
+ setTimeout(() => {
158
+ recreateReadline();
159
+ resolve(`Command completed with code ${code}`);
160
+ }, 200);
133
161
  });
134
162
  });
135
163
  }
@@ -428,4 +456,7 @@ WORKFLOW:
428
456
  }
429
457
  }
430
458
 
459
+ // Keep-alive interval - prevents Node from exiting when event loop is empty
460
+ setInterval(() => {}, 1000);
461
+
431
462
  runSapper();