nodewise 1.0.2 → 1.0.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.
- package/package.json +1 -1
- package/src/explainer/interactive.js +109 -20
- package/src/runner.js +12 -6
package/package.json
CHANGED
|
@@ -8,7 +8,14 @@ const chalk = require('chalk');
|
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
const { explain } = require('./index');
|
|
10
10
|
|
|
11
|
+
// Global state for cancellation
|
|
12
|
+
let cancelPrompt = false;
|
|
13
|
+
let activePromptResolver = null;
|
|
14
|
+
|
|
11
15
|
async function showInteractiveExplainer(errorText, config) {
|
|
16
|
+
// Reset cancel flag
|
|
17
|
+
cancelPrompt = false;
|
|
18
|
+
|
|
12
19
|
console.log('\n');
|
|
13
20
|
const summary = (errorText || '').toString().split('\n')[0] || 'Unknown error';
|
|
14
21
|
|
|
@@ -20,34 +27,105 @@ async function showInteractiveExplainer(errorText, config) {
|
|
|
20
27
|
console.log(chalk.white(' ' + summary));
|
|
21
28
|
console.log();
|
|
22
29
|
|
|
23
|
-
// Compact, modern prompt
|
|
24
|
-
console.log(chalk.cyan.bold(' ? ') + chalk.white('Would you like an AI explanation?'));
|
|
25
|
-
console.log(chalk.gray(' [y] Yes, explain it [n] No, just skip'));
|
|
26
|
-
console.log();
|
|
27
|
-
|
|
28
30
|
const answer = await getUserInput();
|
|
29
31
|
|
|
30
|
-
if (answer
|
|
32
|
+
if (answer === 'yes') {
|
|
31
33
|
await explainErrorInteractively(errorText, config);
|
|
34
|
+
} else if (answer === 'cancelled') {
|
|
35
|
+
// Silently skip on cancellation
|
|
32
36
|
} else {
|
|
33
37
|
console.log(chalk.gray(' Skipped.\n'));
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
function getUserInput() {
|
|
41
|
+
async function getUserInput() {
|
|
38
42
|
return new Promise((resolve) => {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
const options = [
|
|
44
|
+
{ label: chalk.green('✓ Yes, explain it'), value: 'yes' },
|
|
45
|
+
{ label: chalk.gray('✗ No, just skip'), value: 'no' }
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
let selectedIndex = 0;
|
|
49
|
+
let isResolved = false;
|
|
50
|
+
|
|
51
|
+
// Store resolver for cancellation
|
|
52
|
+
activePromptResolver = () => {
|
|
53
|
+
if (!isResolved) {
|
|
54
|
+
cleanup();
|
|
55
|
+
isResolved = true;
|
|
56
|
+
resolve('cancelled');
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Display the question
|
|
61
|
+
console.log(chalk.cyan(' ? ') + chalk.white('Would you like an AI explanation?'));
|
|
62
|
+
|
|
63
|
+
const renderOptions = () => {
|
|
64
|
+
options.forEach((option, index) => {
|
|
65
|
+
const prefix = index === selectedIndex ? chalk.cyan('❯ ') : ' ';
|
|
66
|
+
console.log(` ${prefix}${option.label}`);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
72
|
+
if (process.stdin.isTTY) {
|
|
73
|
+
process.stdin.setRawMode(false);
|
|
74
|
+
}
|
|
75
|
+
process.stdin.pause();
|
|
76
|
+
activePromptResolver = null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Initial render
|
|
80
|
+
renderOptions();
|
|
81
|
+
|
|
82
|
+
// Setup readline for keypress
|
|
83
|
+
readline.emitKeypressEvents(process.stdin);
|
|
84
|
+
if (process.stdin.isTTY) {
|
|
85
|
+
process.stdin.setRawMode(true);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const onKeypress = (str, key) => {
|
|
89
|
+
// Check if cancelled
|
|
90
|
+
if (cancelPrompt) {
|
|
91
|
+
cleanup();
|
|
92
|
+
if (!isResolved) {
|
|
93
|
+
isResolved = true;
|
|
94
|
+
console.log(chalk.yellow('\n (Skipped due to restart)\n'));
|
|
95
|
+
resolve('cancelled');
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (key.name === 'up') {
|
|
101
|
+
// Clear previous options
|
|
102
|
+
process.stdout.write('\x1b[' + options.length + 'A'); // Move cursor up
|
|
103
|
+
process.stdout.write('\x1b[0J'); // Clear from cursor down
|
|
104
|
+
|
|
105
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1;
|
|
106
|
+
renderOptions();
|
|
107
|
+
} else if (key.name === 'down') {
|
|
108
|
+
// Clear previous options
|
|
109
|
+
process.stdout.write('\x1b[' + options.length + 'A'); // Move cursor up
|
|
110
|
+
process.stdout.write('\x1b[0J'); // Clear from cursor down
|
|
111
|
+
|
|
112
|
+
selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0;
|
|
113
|
+
renderOptions();
|
|
114
|
+
} else if (key.name === 'return') {
|
|
115
|
+
cleanup();
|
|
116
|
+
if (!isResolved) {
|
|
117
|
+
isResolved = true;
|
|
118
|
+
console.log(); // Add newline after selection
|
|
119
|
+
resolve(options[selectedIndex].value);
|
|
120
|
+
}
|
|
121
|
+
} else if (key.ctrl && key.name === 'c') {
|
|
122
|
+
cleanup();
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
process.stdin.on('keypress', onKeypress);
|
|
128
|
+
process.stdin.resume();
|
|
51
129
|
});
|
|
52
130
|
}
|
|
53
131
|
|
|
@@ -69,6 +147,16 @@ function clearThinking(interval) {
|
|
|
69
147
|
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
70
148
|
}
|
|
71
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Cancel any active prompt
|
|
152
|
+
*/
|
|
153
|
+
function cancelActivePrompt() {
|
|
154
|
+
cancelPrompt = true;
|
|
155
|
+
if (activePromptResolver) {
|
|
156
|
+
activePromptResolver();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
72
160
|
function displayExplanation(explanation) {
|
|
73
161
|
console.log('\n');
|
|
74
162
|
|
|
@@ -119,5 +207,6 @@ async function explainErrorInteractively(errorText, config) {
|
|
|
119
207
|
|
|
120
208
|
module.exports = {
|
|
121
209
|
showInteractiveExplainer,
|
|
122
|
-
displayExplanation
|
|
210
|
+
displayExplanation,
|
|
211
|
+
cancelActivePrompt
|
|
123
212
|
};
|
package/src/runner.js
CHANGED
|
@@ -12,7 +12,7 @@ const { spawn } = require('child_process');
|
|
|
12
12
|
const chokidar = require('chokidar');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const { explain } = require('./explainer');
|
|
15
|
-
const { showInteractiveExplainer } = require('./explainer/interactive');
|
|
15
|
+
const { showInteractiveExplainer, cancelActivePrompt } = require('./explainer/interactive');
|
|
16
16
|
|
|
17
17
|
class Runner {
|
|
18
18
|
constructor(scriptPath, args = [], config = {}) {
|
|
@@ -57,7 +57,7 @@ class Runner {
|
|
|
57
57
|
const text = data.toString();
|
|
58
58
|
process.stderr.write(text);
|
|
59
59
|
errorBuffer += text;
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
// Detect errors in stderr and explain them immediately
|
|
62
62
|
if (this.isErrorOutput(text)) {
|
|
63
63
|
this.explainErrorWithDebounce(text);
|
|
@@ -85,7 +85,7 @@ class Runner {
|
|
|
85
85
|
|
|
86
86
|
this.child.on('exit', (code, signal) => {
|
|
87
87
|
this.isProcessExiting = true;
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
if (code !== 0 && code !== null) {
|
|
90
90
|
// Process exited with error
|
|
91
91
|
if (!this.isRestarting && errorBuffer) {
|
|
@@ -148,15 +148,18 @@ class Runner {
|
|
|
148
148
|
|
|
149
149
|
this.isRestarting = true;
|
|
150
150
|
|
|
151
|
+
// Cancel any active interactive prompt
|
|
152
|
+
cancelActivePrompt();
|
|
153
|
+
|
|
151
154
|
if (this.child) {
|
|
152
155
|
this.child.kill('SIGTERM');
|
|
153
|
-
|
|
156
|
+
|
|
154
157
|
// Wait a bit for graceful shutdown
|
|
155
158
|
setTimeout(() => {
|
|
156
159
|
if (this.child && !this.child.killed) {
|
|
157
160
|
this.child.kill('SIGKILL');
|
|
158
161
|
}
|
|
159
|
-
|
|
162
|
+
|
|
160
163
|
setTimeout(() => {
|
|
161
164
|
this.isRestarting = false;
|
|
162
165
|
if (reason) {
|
|
@@ -175,6 +178,9 @@ class Runner {
|
|
|
175
178
|
* Stop the process and watcher
|
|
176
179
|
*/
|
|
177
180
|
stop() {
|
|
181
|
+
// Cancel any active interactive prompt
|
|
182
|
+
cancelActivePrompt();
|
|
183
|
+
|
|
178
184
|
if (this.watcher) {
|
|
179
185
|
this.watcher.close();
|
|
180
186
|
this.watcher = null;
|
|
@@ -228,7 +234,7 @@ class Runner {
|
|
|
228
234
|
return;
|
|
229
235
|
}
|
|
230
236
|
this.lastErrorExplanationTime = now;
|
|
231
|
-
|
|
237
|
+
|
|
232
238
|
// Don't explain startup errors that already caused process exit
|
|
233
239
|
if (this.isProcessExiting) {
|
|
234
240
|
return;
|