felo-ai 0.2.2 → 0.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "felo-ai",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Felo AI CLI - real-time search from the terminal",
5
5
  "type": "module",
6
6
  "main": "src/cli.js",
package/src/cli.js CHANGED
@@ -9,6 +9,31 @@ import * as config from './config.js';
9
9
  const require = createRequire(import.meta.url);
10
10
  const pkg = require('../package.json');
11
11
 
12
+ /** Delay (ms) before process.exit to let Windows libuv finish handle cleanup. */
13
+ const EXIT_DELAY_MS = 50;
14
+
15
+ /**
16
+ * Flush stdout then stderr, then exit after a short delay. Avoids Node.js
17
+ * Windows UV_HANDLE_CLOSING assertion when process.exit() runs while streams
18
+ * or other handles are still closing.
19
+ * @param {number} code - Exit code.
20
+ */
21
+ function flushStdioThenExit(code) {
22
+ const doExit = () => setTimeout(() => process.exit(code), EXIT_DELAY_MS);
23
+ const flushStderr = () => {
24
+ if (process.stderr?.writable && !process.stderr.destroyed) {
25
+ process.stderr.write('', () => doExit());
26
+ } else {
27
+ doExit();
28
+ }
29
+ };
30
+ if (process.stdout?.writable && !process.stdout.destroyed) {
31
+ process.stdout.write('', () => flushStderr());
32
+ } else {
33
+ flushStderr();
34
+ }
35
+ }
36
+
12
37
  const program = new Command();
13
38
 
14
39
  program
@@ -31,8 +56,7 @@ program
31
56
  timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
32
57
  });
33
58
  process.exitCode = code;
34
- // Defer exit so stdout/stderr can flush; avoids Node.js Windows UV_HANDLE_CLOSING assertion
35
- setTimeout(() => process.exit(code), 0);
59
+ flushStdioThenExit(code);
36
60
  });
37
61
 
38
62
  program
@@ -53,8 +77,7 @@ program
53
77
  pollTimeoutMs: Number.isNaN(pollTimeoutMs) ? 1_200_000 : pollTimeoutMs,
54
78
  });
55
79
  process.exitCode = code;
56
- // Defer exit so stderr can flush; reduces Node.js Windows assertion (UV_HANDLE_CLOSING)
57
- setTimeout(() => process.exit(code), 0);
80
+ flushStdioThenExit(code);
58
81
  });
59
82
 
60
83
  const configCmd = program
@@ -68,9 +91,10 @@ configCmd
68
91
  try {
69
92
  await config.setConfig(key, value);
70
93
  console.log(`Set ${key}`);
94
+ flushStdioThenExit(0);
71
95
  } catch (e) {
72
96
  console.error('Error:', e.message);
73
- process.exit(1);
97
+ flushStdioThenExit(1);
74
98
  }
75
99
  });
76
100
 
@@ -85,9 +109,10 @@ configCmd
85
109
  } else {
86
110
  console.log(config.maskValueForDisplay(key, value));
87
111
  }
112
+ flushStdioThenExit(0);
88
113
  } catch (e) {
89
114
  console.error('Error:', e.message);
90
- process.exit(1);
115
+ flushStdioThenExit(1);
91
116
  }
92
117
  });
93
118
 
@@ -100,12 +125,13 @@ configCmd
100
125
  const keys = Object.keys(c);
101
126
  if (keys.length === 0) {
102
127
  console.log('No config set. Use: felo config set FELO_API_KEY <key>');
103
- return;
128
+ } else {
129
+ keys.forEach((k) => console.log(k));
104
130
  }
105
- keys.forEach((k) => console.log(k));
131
+ flushStdioThenExit(0);
106
132
  } catch (e) {
107
133
  console.error('Error:', e.message);
108
- process.exit(1);
134
+ flushStdioThenExit(1);
109
135
  }
110
136
  });
111
137
 
@@ -116,9 +142,10 @@ configCmd
116
142
  try {
117
143
  await config.unsetConfig(key);
118
144
  console.log(`Unset ${key}`);
145
+ flushStdioThenExit(0);
119
146
  } catch (e) {
120
147
  console.error('Error:', e.message);
121
- process.exit(1);
148
+ flushStdioThenExit(1);
122
149
  }
123
150
  });
124
151
 
@@ -127,6 +154,7 @@ configCmd
127
154
  .description('Show config file path')
128
155
  .action(() => {
129
156
  console.log(config.getConfigPath());
157
+ flushStdioThenExit(0);
130
158
  });
131
159
 
132
160
  program
@@ -135,7 +163,7 @@ program
135
163
  .argument('[input]', 'text or URL to summarize')
136
164
  .action(() => {
137
165
  console.error('summarize: not yet implemented. Use felo search for now.');
138
- process.exit(1);
166
+ flushStdioThenExit(1);
139
167
  });
140
168
 
141
169
  program
@@ -144,7 +172,7 @@ program
144
172
  .argument('[text]', 'text to translate')
145
173
  .action(() => {
146
174
  console.error('translate: not yet implemented. Use felo search for now.');
147
- process.exit(1);
175
+ flushStdioThenExit(1);
148
176
  });
149
177
 
150
178
  program.parse();
package/src/search.js CHANGED
@@ -38,7 +38,6 @@ async function fetchWithTimeoutAndRetry(url, options, timeoutMs = DEFAULT_TIMEOU
38
38
  ...options,
39
39
  signal: controller.signal,
40
40
  });
41
- clearTimeout(timeoutId);
42
41
  // Retry on 5xx (server errors)
43
42
  if (res.status >= 500 && attempt < MAX_RETRIES) {
44
43
  const delay = RETRY_BASE_MS * Math.pow(2, attempt);
@@ -47,7 +46,6 @@ async function fetchWithTimeoutAndRetry(url, options, timeoutMs = DEFAULT_TIMEOU
47
46
  }
48
47
  return res;
49
48
  } catch (err) {
50
- clearTimeout(timeoutId);
51
49
  lastError = err;
52
50
  if (err.name === 'AbortError') {
53
51
  throw new Error(`Request timed out after ${timeoutMs / 1000}s`);
@@ -58,6 +56,8 @@ async function fetchWithTimeoutAndRetry(url, options, timeoutMs = DEFAULT_TIMEOU
58
56
  continue;
59
57
  }
60
58
  throw lastError;
59
+ } finally {
60
+ clearTimeout(timeoutId);
61
61
  }
62
62
  }
63
63
  throw lastError;