@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,360 @@
1
+ /**
2
+ * Kickstart command - Quick setup for a new Orca project
3
+ */
4
+
5
+ const path = require('path');
6
+ const fs = require('fs').promises;
7
+ const chalk = require('chalk');
8
+ const inquirer = require('inquirer');
9
+ const ora = require('ora');
10
+ const simpleGit = require('simple-git');
11
+ const {
12
+ commandExists,
13
+ getPythonCommand,
14
+ getVenvPaths,
15
+ runCommand,
16
+ runCommandSilent,
17
+ spawnBackground,
18
+ waitForPort,
19
+ ensurePortAvailable,
20
+ print
21
+ } = require('../utils');
22
+ const { GITHUB_REPOS } = require('../config');
23
+
24
+ const REPO_URL = GITHUB_REPOS.PYTHON_STARTER;
25
+
26
+ /**
27
+ * Check all prerequisites
28
+ */
29
+ async function checkPrerequisites() {
30
+ const spinner = ora('Checking prerequisites...').start();
31
+ const missing = [];
32
+
33
+ try {
34
+ // Check Python
35
+ const pythonCmd = await getPythonCommand();
36
+ if (!pythonCmd) {
37
+ missing.push('Python 3.8+');
38
+ spinner.warn(chalk.yellow('Python not found'));
39
+ } else {
40
+ spinner.succeed(chalk.green(`Python found: ${pythonCmd}`));
41
+ }
42
+
43
+ // Check Git
44
+ spinner.start('Checking Git...');
45
+ if (await commandExists('git')) {
46
+ spinner.succeed(chalk.green('Git found'));
47
+ } else {
48
+ missing.push('Git');
49
+ spinner.warn(chalk.yellow('Git not found'));
50
+ }
51
+
52
+ // Check Node.js/npm
53
+ spinner.start('Checking Node.js...');
54
+ if (await commandExists('node') && await commandExists('npm')) {
55
+ spinner.succeed(chalk.green('Node.js/npm found'));
56
+ } else {
57
+ missing.push('Node.js/npm');
58
+ spinner.warn(chalk.yellow('Node.js/npm not found'));
59
+ }
60
+
61
+ // Check npx
62
+ spinner.start('Checking npx...');
63
+ if (await commandExists('npx')) {
64
+ spinner.succeed(chalk.green('npx found'));
65
+ } else {
66
+ missing.push('npx');
67
+ spinner.warn(chalk.yellow('npx not found'));
68
+ }
69
+
70
+ if (missing.length > 0) {
71
+ spinner.stop();
72
+ print.error('Missing prerequisites:');
73
+ missing.forEach(item => console.log(` - ${item}`));
74
+ console.log();
75
+ print.info('Please install the missing prerequisites:');
76
+ console.log(' - Python: https://www.python.org/downloads/');
77
+ console.log(' - Git: https://git-scm.com/downloads');
78
+ console.log(' - Node.js: https://nodejs.org/\n');
79
+ process.exit(1);
80
+ }
81
+
82
+ spinner.stop();
83
+ return await getPythonCommand();
84
+ } catch (error) {
85
+ spinner.fail('Failed to check prerequisites');
86
+ throw error;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Clone repository
92
+ */
93
+ async function cloneRepository(directory) {
94
+ const spinner = ora('Cloning Orca starter kit from GitHub...').start();
95
+
96
+ try {
97
+ const git = simpleGit();
98
+ await git.clone(REPO_URL, directory);
99
+ spinner.succeed(chalk.green('Repository cloned successfully'));
100
+ } catch (error) {
101
+ spinner.fail('Failed to clone repository');
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Create virtual environment
108
+ */
109
+ async function createVirtualEnv(projectPath, pythonCmd) {
110
+ const spinner = ora('Creating Python virtual environment...').start();
111
+
112
+ try {
113
+ await runCommandSilent(pythonCmd, ['-m', 'venv', 'orca_env'], {
114
+ cwd: projectPath
115
+ });
116
+ spinner.succeed(chalk.green('Virtual environment created'));
117
+ } catch (error) {
118
+ spinner.fail('Failed to create virtual environment');
119
+ throw error;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Install dependencies
125
+ */
126
+ async function installDependencies(projectPath) {
127
+ const spinner = ora('Installing Python dependencies...').start();
128
+
129
+ try {
130
+ const venvPaths = getVenvPaths();
131
+ const pipPath = path.join(projectPath, venvPaths.pip);
132
+
133
+ await runCommandSilent(pipPath, ['install', '-r', 'requirements.txt'], {
134
+ cwd: projectPath
135
+ });
136
+
137
+ spinner.succeed(chalk.green('Dependencies installed'));
138
+ } catch (error) {
139
+ spinner.fail('Failed to install dependencies');
140
+ throw error;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Start backend server
146
+ */
147
+ async function startBackend(projectPath, agentPort) {
148
+ const spinner = ora(`Starting agent server on port ${agentPort}...`).start();
149
+
150
+ try {
151
+ const venvPaths = getVenvPaths();
152
+ const pythonPath = path.join(projectPath, venvPaths.python);
153
+
154
+ const backendProcess = spawnBackground(pythonPath, ['main.py', '--dev'], {
155
+ cwd: projectPath,
156
+ env: { ...process.env, PORT: agentPort }
157
+ });
158
+
159
+ // Wait a bit for the server to start
160
+ await new Promise(resolve => setTimeout(resolve, 2000));
161
+
162
+ // Check if process is still running
163
+ if (backendProcess.exitCode !== null) {
164
+ throw new Error('Backend process exited immediately');
165
+ }
166
+
167
+ spinner.succeed(chalk.green(`Agent started (PID: ${backendProcess.pid})`));
168
+ return backendProcess;
169
+ } catch (error) {
170
+ spinner.fail('Failed to start agent');
171
+ throw error;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Start frontend server
177
+ */
178
+ async function startFrontend(projectPath, port, agentPort) {
179
+ const spinner = ora(`Starting Orca-UI server on port ${port}...`).start();
180
+
181
+ try {
182
+ const tryStart = async (pkgOrBin) => {
183
+ const proc = spawnBackground('npx', [
184
+ '-y',
185
+ pkgOrBin,
186
+ `--port=${port}`,
187
+ `--agent-port=${agentPort}`
188
+ ], { cwd: projectPath });
189
+ // Wait for frontend to start
190
+ await new Promise(resolve => setTimeout(resolve, 3000));
191
+ if (proc.exitCode !== null) {
192
+ throw new Error(`Frontend process exited immediately (${pkgOrBin})`);
193
+ }
194
+ return proc;
195
+ };
196
+
197
+ let frontendProcess;
198
+ try {
199
+ // Prefer new bin name
200
+ frontendProcess = await tryStart('orca-ui');
201
+ } catch (_) {
202
+ // Fallback to package name
203
+ frontendProcess = await tryStart('@orca/ui');
204
+ }
205
+
206
+ spinner.succeed(chalk.green(`Orca-UI started (PID: ${frontendProcess.pid})`));
207
+ return frontendProcess;
208
+ } catch (error) {
209
+ spinner.fail('Failed to start Orca-UI');
210
+ throw error;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Main kickstart command
216
+ */
217
+ async function kickstart(options) {
218
+ try {
219
+ const { directory, port, agentPort, start } = options;
220
+
221
+ print.title('🚀 Orca Kickstart - Python');
222
+
223
+ // Check prerequisites
224
+ const pythonCmd = await checkPrerequisites();
225
+
226
+ // Check if directory exists
227
+ const projectPath = path.resolve(process.cwd(), directory);
228
+
229
+ try {
230
+ await fs.access(projectPath);
231
+ print.error(`Directory '${directory}' already exists`);
232
+
233
+ const { shouldRemove } = await inquirer.prompt([
234
+ {
235
+ type: 'confirm',
236
+ name: 'shouldRemove',
237
+ message: 'Do you want to remove it and continue?',
238
+ default: false
239
+ }
240
+ ]);
241
+
242
+ if (!shouldRemove) {
243
+ process.exit(1);
244
+ }
245
+
246
+ await fs.rm(projectPath, { recursive: true, force: true });
247
+ } catch (error) {
248
+ // Directory doesn't exist, which is fine
249
+ }
250
+
251
+ // Create directory
252
+ print.step(`Creating directory: ${directory}`);
253
+ await fs.mkdir(projectPath, { recursive: true });
254
+ print.success(`Created directory: ${projectPath}`);
255
+
256
+ // Clone repository
257
+ await cloneRepository(projectPath);
258
+
259
+ // Create virtual environment
260
+ await createVirtualEnv(projectPath, pythonCmd);
261
+
262
+ // Install dependencies
263
+ await installDependencies(projectPath);
264
+
265
+ // Setup complete
266
+ print.title('✓ Setup completed successfully!');
267
+
268
+ // Ask to start servers
269
+ const shouldStart = start !== false ? await inquirer.prompt([
270
+ {
271
+ type: 'confirm',
272
+ name: 'start',
273
+ message: 'Do you want to start the backend and frontend servers now?',
274
+ default: true
275
+ }
276
+ ]).then(answers => answers.start) : false;
277
+
278
+ if (!shouldStart) {
279
+ console.log();
280
+ print.info('To start manually:');
281
+ console.log(chalk.gray(` cd ${directory}`));
282
+ console.log(chalk.gray(` ${getVenvPaths().activate}`));
283
+ console.log(chalk.gray(` python main.py --dev`));
284
+ console.log(chalk.gray(` # In another terminal:`));
285
+ console.log(chalk.gray(` npx -y @orca/ui orca --port=${port} --agent-port=${agentPort}`));
286
+ console.log();
287
+ return;
288
+ }
289
+
290
+ // Start servers
291
+ console.log();
292
+ // Ensure ports are available before starting
293
+ await ensurePortAvailable(agentPort, 'Agent');
294
+ await ensurePortAvailable(port, 'UI');
295
+
296
+ const backendProcess = await startBackend(projectPath, agentPort);
297
+ const frontendProcess = await startFrontend(projectPath, port, agentPort);
298
+
299
+ // Display success message
300
+ print.title('🎉 Orca is running!');
301
+ print.url('Orca-UI', `http://localhost:${port}`);
302
+ print.url('Agent ', `http://localhost:${agentPort}`);
303
+ console.log();
304
+ print.warning('Press Ctrl+C to stop both servers');
305
+ console.log();
306
+
307
+ // Handle graceful shutdown
308
+ const shutdown = () => {
309
+ console.log();
310
+ print.step('Shutting down servers...');
311
+
312
+ if (backendProcess && !backendProcess.killed) {
313
+ backendProcess.kill();
314
+ }
315
+ if (frontendProcess && !frontendProcess.killed) {
316
+ frontendProcess.kill();
317
+ }
318
+
319
+ setTimeout(() => {
320
+ print.success('Servers stopped');
321
+ process.exit(0);
322
+ }, 1000);
323
+ };
324
+
325
+ process.on('SIGINT', shutdown);
326
+ process.on('SIGTERM', shutdown);
327
+
328
+ // Monitor processes
329
+ backendProcess.on('exit', (code) => {
330
+ if (code !== 0 && code !== null) {
331
+ print.error('Agent stopped unexpectedly');
332
+ if (frontendProcess && !frontendProcess.killed) {
333
+ frontendProcess.kill();
334
+ }
335
+ process.exit(1);
336
+ }
337
+ });
338
+
339
+ frontendProcess.on('exit', (code) => {
340
+ if (code !== 0 && code !== null) {
341
+ print.error('Orca-UI stopped unexpectedly');
342
+ if (backendProcess && !backendProcess.killed) {
343
+ backendProcess.kill();
344
+ }
345
+ process.exit(1);
346
+ }
347
+ });
348
+
349
+ // Keep process alive
350
+ await new Promise(() => {});
351
+
352
+ } catch (error) {
353
+ print.error(`Failed: ${error.message}`);
354
+ console.error(error);
355
+ process.exit(1);
356
+ }
357
+ }
358
+
359
+ module.exports = kickstart;
360
+