@orcapt/cli 1.0.0

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.
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Utility functions for Orca CLI
3
+ */
4
+
5
+ const { spawn } = require('cross-spawn');
6
+ const chalk = require('chalk');
7
+
8
+ /**
9
+ * Check if a command exists in the system
10
+ * @param {string} command - Command to check
11
+ * @returns {Promise<boolean>}
12
+ */
13
+ async function commandExists(command) {
14
+ return new Promise((resolve) => {
15
+ const child = spawn(command, ['--version'], {
16
+ stdio: 'ignore',
17
+ shell: false
18
+ });
19
+
20
+ child.on('close', (code) => {
21
+ resolve(code === 0);
22
+ });
23
+
24
+ child.on('error', () => {
25
+ resolve(false);
26
+ });
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Get Python command (python3 or python)
32
+ * @returns {Promise<string|null>}
33
+ */
34
+ async function getPythonCommand() {
35
+ if (await commandExists('python3')) {
36
+ return 'python3';
37
+ }
38
+ if (await commandExists('python')) {
39
+ return 'python';
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Get the virtual environment paths based on OS
46
+ * @returns {Object}
47
+ */
48
+ function getVenvPaths() {
49
+ const isWindows = process.platform === 'win32';
50
+
51
+ return {
52
+ python: isWindows ? 'orca_env\\Scripts\\python.exe' : 'orca_env/bin/python',
53
+ pip: isWindows ? 'orca_env\\Scripts\\pip.exe' : 'orca_env/bin/pip',
54
+ activate: isWindows ? 'orca_env\\Scripts\\activate' : 'source orca_env/bin/activate'
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Run a command and stream output
60
+ * @param {string} command - Command to run
61
+ * @param {Array} args - Command arguments
62
+ * @param {Object} options - Spawn options
63
+ * @returns {Promise<number>} Exit code
64
+ */
65
+ function runCommand(command, args = [], options = {}) {
66
+ return new Promise((resolve, reject) => {
67
+ const child = spawn(command, args, {
68
+ stdio: 'inherit',
69
+ shell: false,
70
+ ...options
71
+ });
72
+
73
+ child.on('close', (code) => {
74
+ if (code === 0) {
75
+ resolve(code);
76
+ } else {
77
+ reject(new Error(`Command failed with exit code ${code}`));
78
+ }
79
+ });
80
+
81
+ child.on('error', (error) => {
82
+ reject(error);
83
+ });
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Run a command silently and return output
89
+ * @param {string} command - Command to run
90
+ * @param {Array} args - Command arguments
91
+ * @param {Object} options - Spawn options
92
+ * @returns {Promise<string>} Output
93
+ */
94
+ function runCommandSilent(command, args = [], options = {}) {
95
+ return new Promise((resolve, reject) => {
96
+ const child = spawn(command, args, {
97
+ stdio: 'pipe',
98
+ shell: false,
99
+ ...options
100
+ });
101
+
102
+ let output = '';
103
+ let errorOutput = '';
104
+
105
+ if (child.stdout) {
106
+ child.stdout.on('data', (data) => {
107
+ output += data.toString();
108
+ });
109
+ }
110
+
111
+ if (child.stderr) {
112
+ child.stderr.on('data', (data) => {
113
+ errorOutput += data.toString();
114
+ });
115
+ }
116
+
117
+ child.on('close', (code) => {
118
+ if (code === 0) {
119
+ resolve(output);
120
+ } else {
121
+ reject(new Error(errorOutput || output));
122
+ }
123
+ });
124
+
125
+ child.on('error', (error) => {
126
+ reject(error);
127
+ });
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Spawn a background process
133
+ * @param {string} command - Command to run
134
+ * @param {Array} args - Command arguments
135
+ * @param {Object} options - Spawn options
136
+ * @returns {ChildProcess}
137
+ */
138
+ function spawnBackground(command, args = [], options = {}) {
139
+ return spawn(command, args, {
140
+ stdio: 'pipe',
141
+ shell: false,
142
+ detached: false,
143
+ ...options
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Wait for a port to be ready
149
+ * @param {number} port - Port to check
150
+ * @param {number} timeout - Timeout in ms
151
+ * @returns {Promise<boolean>}
152
+ */
153
+ async function waitForPort(port, timeout = 10000) {
154
+ const net = require('net');
155
+ const startTime = Date.now();
156
+
157
+ return new Promise((resolve) => {
158
+ const checkPort = () => {
159
+ if (Date.now() - startTime > timeout) {
160
+ resolve(false);
161
+ return;
162
+ }
163
+
164
+ const socket = new net.Socket();
165
+
166
+ socket.setTimeout(500);
167
+ socket.on('connect', () => {
168
+ socket.destroy();
169
+ resolve(true);
170
+ });
171
+
172
+ socket.on('timeout', () => {
173
+ socket.destroy();
174
+ setTimeout(checkPort, 500);
175
+ });
176
+
177
+ socket.on('error', () => {
178
+ socket.destroy();
179
+ setTimeout(checkPort, 500);
180
+ });
181
+
182
+ socket.connect(port, '127.0.0.1');
183
+ };
184
+
185
+ checkPort();
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Check if a port is in use
191
+ * @param {number} port - Port to check
192
+ * @returns {Promise<boolean>}
193
+ */
194
+ async function isPortInUse(port) {
195
+ const net = require('net');
196
+
197
+ return new Promise((resolve) => {
198
+ const server = net.createServer();
199
+
200
+ server.once('error', (err) => {
201
+ if (err.code === 'EADDRINUSE') {
202
+ resolve(true);
203
+ } else {
204
+ resolve(false);
205
+ }
206
+ });
207
+
208
+ server.once('listening', () => {
209
+ server.close();
210
+ resolve(false);
211
+ });
212
+
213
+ server.listen(port, '127.0.0.1');
214
+ });
215
+ }
216
+
217
+ /**
218
+ * Kill process using a specific port
219
+ * @param {number} port - Port number
220
+ * @returns {Promise<boolean>} True if a process was killed
221
+ */
222
+ async function killPort(port) {
223
+ const { exec } = require('child_process');
224
+ const util = require('util');
225
+ const execPromise = util.promisify(exec);
226
+
227
+ try {
228
+ const isWindows = process.platform === 'win32';
229
+ const isMac = process.platform === 'darwin';
230
+
231
+ if (isWindows) {
232
+ // Windows: Find and kill process using the port
233
+ try {
234
+ const { stdout } = await execPromise(`netstat -ano | findstr :${port}`);
235
+ const lines = stdout.trim().split('\n');
236
+ const pids = new Set();
237
+
238
+ for (const line of lines) {
239
+ const parts = line.trim().split(/\s+/);
240
+ const pid = parts[parts.length - 1];
241
+ if (pid && pid !== '0') {
242
+ pids.add(pid);
243
+ }
244
+ }
245
+
246
+ for (const pid of pids) {
247
+ try {
248
+ await execPromise(`taskkill /F /PID ${pid}`);
249
+ } catch (e) {
250
+ // Process might already be dead
251
+ }
252
+ }
253
+
254
+ return pids.size > 0;
255
+ } catch (error) {
256
+ return false;
257
+ }
258
+ } else if (isMac) {
259
+ // macOS: Use lsof to find and kill process
260
+ try {
261
+ const { stdout } = await execPromise(`lsof -ti:${port}`);
262
+ const pids = stdout.trim().split('\n').filter(pid => pid);
263
+
264
+ for (const pid of pids) {
265
+ try {
266
+ await execPromise(`kill -9 ${pid}`);
267
+ } catch (e) {
268
+ // Process might already be dead
269
+ }
270
+ }
271
+
272
+ return pids.length > 0;
273
+ } catch (error) {
274
+ return false;
275
+ }
276
+ } else {
277
+ // Linux: Use fuser or lsof
278
+ try {
279
+ const { stdout } = await execPromise(`lsof -ti:${port} 2>/dev/null || fuser ${port}/tcp 2>/dev/null`);
280
+ const pids = stdout.trim().split(/\s+/).filter(pid => pid);
281
+
282
+ for (const pid of pids) {
283
+ try {
284
+ await execPromise(`kill -9 ${pid}`);
285
+ } catch (e) {
286
+ // Process might already be dead
287
+ }
288
+ }
289
+
290
+ return pids.length > 0;
291
+ } catch (error) {
292
+ return false;
293
+ }
294
+ }
295
+ } catch (error) {
296
+ return false;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Ensure port is available by killing any process using it
302
+ * @param {number} port - Port to check and free
303
+ * @param {string} portName - Name for logging (e.g., 'UI', 'Agent')
304
+ * @returns {Promise<void>}
305
+ */
306
+ async function ensurePortAvailable(port, portName = 'Port') {
307
+ const chalk = require('chalk');
308
+
309
+ if (await isPortInUse(port)) {
310
+ console.log(chalk.yellow(`⚠ ${portName} port ${port} is in use, killing the process...`));
311
+ const killed = await killPort(port);
312
+
313
+ if (killed) {
314
+ console.log(chalk.green(`✓ Freed port ${port}`));
315
+ // Wait a moment for the port to be fully released
316
+ await new Promise(resolve => setTimeout(resolve, 1000));
317
+ } else {
318
+ console.log(chalk.yellow(`⚠ Could not kill process on port ${port}, will try to start anyway...`));
319
+ }
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Print formatted messages
325
+ */
326
+ const print = {
327
+ title: (text) => console.log(chalk.magenta.bold(`\n${'='.repeat(60)}\n${text}\n${'='.repeat(60)}\n`)),
328
+ step: (text) => console.log(chalk.cyan(`► ${text}`)),
329
+ success: (text) => console.log(chalk.green(`✓ ${text}`)),
330
+ error: (text) => console.log(chalk.red(`✗ ${text}`)),
331
+ warning: (text) => console.log(chalk.yellow(`⚠ ${text}`)),
332
+ info: (text) => console.log(chalk.blue(`ℹ ${text}`)),
333
+ url: (label, url) => console.log(chalk.cyan(`${label}:`), chalk.underline(url))
334
+ };
335
+
336
+ module.exports = {
337
+ commandExists,
338
+ getPythonCommand,
339
+ getVenvPaths,
340
+ runCommand,
341
+ runCommandSilent,
342
+ spawnBackground,
343
+ waitForPort,
344
+ isPortInUse,
345
+ killPort,
346
+ ensurePortAvailable,
347
+ print
348
+ };
349
+