@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,431 @@
1
+ /**
2
+ * Kickstart command for Node.js - Quick setup for a new Orca Node.js 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
+ spawnBackground,
14
+ ensurePortAvailable,
15
+ print
16
+ } = require('../utils');
17
+ const { GITHUB_REPOS } = require('../config');
18
+
19
+ const REPO_URL = GITHUB_REPOS.NODE_STARTER;
20
+
21
+ /**
22
+ * Check all prerequisites for Node.js
23
+ */
24
+ async function checkPrerequisites() {
25
+ const spinner = ora('Checking prerequisites...').start();
26
+ const missing = [];
27
+
28
+ try {
29
+ // Check Node.js
30
+ spinner.start('Checking Node.js...');
31
+ if (await commandExists('node')) {
32
+ spinner.succeed(chalk.green('Node.js found'));
33
+ } else {
34
+ missing.push('Node.js');
35
+ spinner.warn(chalk.yellow('Node.js not found'));
36
+ }
37
+
38
+ // Check npm
39
+ spinner.start('Checking npm...');
40
+ if (await commandExists('npm')) {
41
+ spinner.succeed(chalk.green('npm found'));
42
+ } else {
43
+ missing.push('npm');
44
+ spinner.warn(chalk.yellow('npm not found'));
45
+ }
46
+
47
+ // Check Git
48
+ spinner.start('Checking Git...');
49
+ if (await commandExists('git')) {
50
+ spinner.succeed(chalk.green('Git found'));
51
+ } else {
52
+ missing.push('Git');
53
+ spinner.warn(chalk.yellow('Git not found'));
54
+ }
55
+
56
+ // Check npx
57
+ spinner.start('Checking npx...');
58
+ if (await commandExists('npx')) {
59
+ spinner.succeed(chalk.green('npx found'));
60
+ } else {
61
+ missing.push('npx');
62
+ spinner.warn(chalk.yellow('npx not found'));
63
+ }
64
+
65
+ if (missing.length > 0) {
66
+ spinner.stop();
67
+ print.error('Missing prerequisites:');
68
+ missing.forEach(item => console.log(` - ${item}`));
69
+ console.log();
70
+ print.info('Please install the missing prerequisites:');
71
+ console.log(' - Node.js: https://nodejs.org/');
72
+ console.log(' - Git: https://git-scm.com/downloads\n');
73
+ process.exit(1);
74
+ }
75
+
76
+ spinner.stop();
77
+ return true;
78
+ } catch (error) {
79
+ spinner.fail('Failed to check prerequisites');
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Clone repository
86
+ */
87
+ async function cloneRepository(directory) {
88
+ const spinner = ora('Cloning Orca Node.js starter kit from GitHub...').start();
89
+
90
+ try {
91
+ const git = simpleGit();
92
+ await git.clone(REPO_URL, directory);
93
+ spinner.succeed(chalk.green('Repository cloned successfully'));
94
+ } catch (error) {
95
+ spinner.fail('Failed to clone repository');
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Fix package.json to use npm packages instead of local file references
102
+ */
103
+ async function fixPackageJson(projectPath) {
104
+ const packageJsonPath = path.join(projectPath, 'package.json');
105
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
106
+
107
+ // Replace local file reference with npm package
108
+ if (packageJson.dependencies && packageJson.dependencies['@orca/sdk']) {
109
+ if (packageJson.dependencies['@orca/sdk'].startsWith('file:')) {
110
+ packageJson.dependencies['@orca/sdk'] = '^1.0.0';
111
+ }
112
+ }
113
+
114
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
115
+ }
116
+
117
+ /**
118
+ * Create memory directory with required files (not tracked by git)
119
+ */
120
+ async function createMemoryModule(projectPath) {
121
+ const memoryDir = path.join(projectPath, 'memory');
122
+ await fs.mkdir(memoryDir, { recursive: true });
123
+
124
+ // Create index.js
125
+ const indexContent = `const { ConversationManager } = require('./conversation_manager');
126
+
127
+ module.exports = {
128
+ ConversationManager
129
+ };
130
+ `;
131
+ await fs.writeFile(path.join(memoryDir, 'index.js'), indexContent);
132
+
133
+ // Create conversation_manager.js
134
+ const conversationManagerContent = `/**
135
+ * Conversation Manager
136
+ * Handles conversation history and thread management
137
+ */
138
+
139
+ class ConversationManager {
140
+ constructor() {
141
+ this.conversations = new Map();
142
+ this.maxHistory = 20;
143
+ }
144
+
145
+ getConversation(threadId) {
146
+ if (!this.conversations.has(threadId)) {
147
+ this.conversations.set(threadId, []);
148
+ }
149
+ return this.conversations.get(threadId);
150
+ }
151
+
152
+ addMessage(threadId, role, content) {
153
+ const conversation = this.getConversation(threadId);
154
+ conversation.push({ role, content });
155
+
156
+ // Keep only last maxHistory messages
157
+ if (conversation.length > this.maxHistory) {
158
+ conversation.splice(0, conversation.length - this.maxHistory);
159
+ }
160
+ }
161
+
162
+ clearConversation(threadId) {
163
+ this.conversations.delete(threadId);
164
+ }
165
+
166
+ getAllConversations() {
167
+ return Array.from(this.conversations.keys());
168
+ }
169
+ }
170
+
171
+ module.exports = { ConversationManager };
172
+ `;
173
+ await fs.writeFile(path.join(memoryDir, 'conversation_manager.js'), conversationManagerContent);
174
+ }
175
+
176
+ /**
177
+ * Install Node.js dependencies
178
+ */
179
+ async function installDependencies(projectPath) {
180
+ const spinner = ora('Installing Node.js dependencies...').start();
181
+
182
+ try {
183
+ // Fix package.json first
184
+ await fixPackageJson(projectPath);
185
+
186
+ // Note: memory module is now included in the GitHub repo, no need to create it
187
+
188
+ const { spawn } = require('cross-spawn');
189
+
190
+ await new Promise((resolve, reject) => {
191
+ const npmProcess = spawn('npm', ['install'], {
192
+ cwd: projectPath,
193
+ stdio: 'pipe',
194
+ shell: false
195
+ });
196
+
197
+ let output = '';
198
+ let errorOutput = '';
199
+
200
+ npmProcess.stdout.on('data', (data) => {
201
+ output += data.toString();
202
+ });
203
+
204
+ npmProcess.stderr.on('data', (data) => {
205
+ errorOutput += data.toString();
206
+ });
207
+
208
+ npmProcess.on('close', (code) => {
209
+ if (code === 0) {
210
+ resolve();
211
+ } else {
212
+ reject(new Error(errorOutput || output));
213
+ }
214
+ });
215
+
216
+ npmProcess.on('error', (error) => {
217
+ reject(error);
218
+ });
219
+ });
220
+
221
+ spinner.succeed(chalk.green('Dependencies installed'));
222
+ } catch (error) {
223
+ spinner.fail('Failed to install dependencies');
224
+ throw error;
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Start Node.js agent server
230
+ */
231
+ async function startAgent(projectPath, agentPort) {
232
+ const spinner = ora(`Starting agent server on port ${agentPort}...`).start();
233
+
234
+ try {
235
+ const agentProcess = spawnBackground('node', ['main.js', '--dev'], {
236
+ cwd: projectPath,
237
+ env: { ...process.env, PORT: agentPort }
238
+ });
239
+
240
+ // Wait a bit for the server to start
241
+ await new Promise(resolve => setTimeout(resolve, 2000));
242
+
243
+ // Check if process is still running
244
+ if (agentProcess.exitCode !== null) {
245
+ throw new Error('Agent process exited immediately');
246
+ }
247
+
248
+ spinner.succeed(chalk.green(`Agent started (PID: ${agentProcess.pid})`));
249
+ return agentProcess;
250
+ } catch (error) {
251
+ spinner.fail('Failed to start agent');
252
+ throw error;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Start Orca-UI frontend server
258
+ */
259
+ async function startUI(projectPath, port, agentPort) {
260
+ const spinner = ora(`Starting Orca-UI server on port ${port}...`).start();
261
+
262
+ try {
263
+ const uiProcess = spawnBackground('npx', [
264
+ '-y',
265
+ '@orca/ui',
266
+ 'orca',
267
+ `--port=${port}`,
268
+ `--agent-port=${agentPort}`
269
+ ], {
270
+ cwd: projectPath
271
+ });
272
+
273
+ // Wait for UI to start
274
+ await new Promise(resolve => setTimeout(resolve, 3000));
275
+
276
+ // Check if process is still running
277
+ if (uiProcess.exitCode !== null) {
278
+ throw new Error('Orca-UI process exited immediately');
279
+ }
280
+
281
+ spinner.succeed(chalk.green(`Orca-UI started (PID: ${uiProcess.pid})`));
282
+ return uiProcess;
283
+ } catch (error) {
284
+ spinner.fail('Failed to start Orca-UI');
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Main kickstart command for Node.js
291
+ */
292
+ async function kickstartNode(options) {
293
+ try {
294
+ const { directory, port, agentPort, start } = options;
295
+
296
+ print.title('🚀 Orca Kickstart - Node.js');
297
+
298
+ // Check prerequisites
299
+ await checkPrerequisites();
300
+
301
+ // Check if directory exists
302
+ const projectPath = path.resolve(process.cwd(), directory);
303
+
304
+ try {
305
+ await fs.access(projectPath);
306
+ print.error(`Directory '${directory}' already exists`);
307
+
308
+ const { shouldRemove } = await inquirer.prompt([
309
+ {
310
+ type: 'confirm',
311
+ name: 'shouldRemove',
312
+ message: 'Do you want to remove it and continue?',
313
+ default: false
314
+ }
315
+ ]);
316
+
317
+ if (!shouldRemove) {
318
+ process.exit(1);
319
+ }
320
+
321
+ await fs.rm(projectPath, { recursive: true, force: true });
322
+ } catch (error) {
323
+ // Directory doesn't exist, which is fine
324
+ }
325
+
326
+ // Create directory
327
+ print.step(`Creating directory: ${directory}`);
328
+ await fs.mkdir(projectPath, { recursive: true });
329
+ print.success(`Created directory: ${projectPath}`);
330
+
331
+ // Clone repository
332
+ await cloneRepository(projectPath);
333
+
334
+ // Install dependencies
335
+ await installDependencies(projectPath);
336
+
337
+ // Setup complete
338
+ print.title('✓ Setup completed successfully!');
339
+
340
+ // Ask to start servers
341
+ const shouldStart = start !== false ? await inquirer.prompt([
342
+ {
343
+ type: 'confirm',
344
+ name: 'start',
345
+ message: 'Do you want to start the agent and Orca-UI servers now?',
346
+ default: true
347
+ }
348
+ ]).then(answers => answers.start) : false;
349
+
350
+ if (!shouldStart) {
351
+ console.log();
352
+ print.info('To start manually:');
353
+ console.log(chalk.gray(` cd ${directory}`));
354
+ console.log(chalk.gray(` node main.js --dev`));
355
+ console.log(chalk.gray(` # In another terminal:`));
356
+ console.log(chalk.gray(` npx -y @orca/ui orca --port=${port} --agent-port=${agentPort}`));
357
+ console.log();
358
+ return;
359
+ }
360
+
361
+ // Start servers
362
+ console.log();
363
+ // Ensure ports are available before starting
364
+ await ensurePortAvailable(agentPort, 'Agent');
365
+ await ensurePortAvailable(port, 'UI');
366
+
367
+ const agentProcess = await startAgent(projectPath, agentPort);
368
+ const uiProcess = await startUI(projectPath, port, agentPort);
369
+
370
+ // Display success message
371
+ print.title('🎉 Orca is running!');
372
+ print.url('Orca-UI', `http://localhost:${port}`);
373
+ print.url('Agent ', `http://localhost:${agentPort}`);
374
+ console.log();
375
+ print.warning('Press Ctrl+C to stop both servers');
376
+ console.log();
377
+
378
+ // Handle graceful shutdown
379
+ const shutdown = () => {
380
+ console.log();
381
+ print.step('Shutting down servers...');
382
+
383
+ if (agentProcess && !agentProcess.killed) {
384
+ agentProcess.kill();
385
+ }
386
+ if (uiProcess && !uiProcess.killed) {
387
+ uiProcess.kill();
388
+ }
389
+
390
+ setTimeout(() => {
391
+ print.success('Servers stopped');
392
+ process.exit(0);
393
+ }, 1000);
394
+ };
395
+
396
+ process.on('SIGINT', shutdown);
397
+ process.on('SIGTERM', shutdown);
398
+
399
+ // Monitor processes
400
+ agentProcess.on('exit', (code) => {
401
+ if (code !== 0 && code !== null) {
402
+ print.error('Agent stopped unexpectedly');
403
+ if (uiProcess && !uiProcess.killed) {
404
+ uiProcess.kill();
405
+ }
406
+ process.exit(1);
407
+ }
408
+ });
409
+
410
+ uiProcess.on('exit', (code) => {
411
+ if (code !== 0 && code !== null) {
412
+ print.error('Orca-UI stopped unexpectedly');
413
+ if (agentProcess && !agentProcess.killed) {
414
+ agentProcess.kill();
415
+ }
416
+ process.exit(1);
417
+ }
418
+ });
419
+
420
+ // Keep process alive
421
+ await new Promise(() => {});
422
+
423
+ } catch (error) {
424
+ print.error(`Failed: ${error.message}`);
425
+ console.error(error);
426
+ process.exit(1);
427
+ }
428
+ }
429
+
430
+ module.exports = kickstartNode;
431
+