@niyantrilabs/spiritai 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Niyantri Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Spirit AI
2
+
3
+ **Mobile Autonomous Developer for Multi-Platform App Development**
4
+
5
+ Spirit AI is an intelligent CLI agent that connects to your development server and enables autonomous mobile application development across Flutter, Android, iOS, React Native, and all major mobile frameworks.
6
+
7
+ ## Features
8
+
9
+ - šŸ¤– Autonomous mobile development across all platforms
10
+ - šŸ”§ Full file system operations with surgical precision
11
+ - šŸ“± Support for Flutter, Android (Kotlin/Java), iOS (Swift), React Native
12
+ - šŸ”Œ Real-time connection to development server
13
+ - šŸŽÆ Smart command execution with system awareness
14
+ - šŸ”Ŗ Advanced code editing capabilities (line-level precision)
15
+
16
+ ## Installation
17
+ ```bash
18
+ npm install -g @niyantrilabs/spiritai
19
+ ```
20
+
21
+ ## Usage
22
+ ```bash
23
+ # Connect to your development server
24
+ spiritai connect <your-connection-code>
25
+
26
+ # Show help
27
+ spiritai --help
28
+
29
+ # Show version
30
+ spiritai --version
31
+
32
+ # Using the shorter alias
33
+ spirit connect <code>
34
+ ```
35
+
36
+ ## Requirements
37
+
38
+ - Node.js >= 16.0.0
39
+ - Internet connection for server communication
40
+
41
+ ## Supported Platforms
42
+
43
+ - macOS (x64, arm64)
44
+ - Linux (x64, arm64)
45
+ - Windows (x64, arm64)
46
+
47
+ ## Development Tools Detection
48
+
49
+ Spirit AI automatically detects and reports:
50
+ - Java, ADB (Android development)
51
+ - Flutter, Dart
52
+ - Git, npm, Python, pip
53
+
54
+ ## Commands
55
+
56
+ Once connected:
57
+ - `status` - Show connection status
58
+ - `help` - Show available commands
59
+ - `exit` - Exit the agent
60
+ - `clear` - Clear screen
61
+
62
+ ## Security Notice
63
+
64
+ Spirit AI has full access to your file system and can execute shell commands. Only connect to trusted servers with valid connection codes.
65
+
66
+ ## License
67
+
68
+ MIT
69
+
70
+ ## Author
71
+
72
+ Niyantri Labs
73
+
74
+ ## Repository
75
+
76
+ https://github.com/niyantrilabs/spiritai
package/index.js ADDED
@@ -0,0 +1,960 @@
1
+ #!/usr/bin/env node
2
+
3
+ const yargs = require('yargs/yargs');
4
+ const { hideBin } = require('yargs/helpers');
5
+ const { io } = require("socket.io-client");
6
+ const readline = require('readline');
7
+ const os = require('os');
8
+ const { exec } = require('child_process');
9
+ const { promisify } = require('util');
10
+ const fs = require('fs').promises;
11
+ const path = require('path');
12
+
13
+ const execAsync = promisify(exec);
14
+
15
+ let currentWorkingDirectory = process.cwd();
16
+
17
+ const argv = yargs(hideBin(process.argv))
18
+ .scriptName('spiritai')
19
+ .command('connect <code>', 'Connect to session using connection code', (yargs) => {
20
+ return yargs.positional('code', { describe: 'Connection code from web UI', type: 'string' })
21
+ })
22
+ .demandCommand(1, 'You must provide the "connect" command with a connection code.')
23
+ .argv;
24
+
25
+ const connectionCode = argv.code;
26
+ const serverUrl ="https://thespiritai.com";
27
+ let socket;
28
+
29
+ const rl = readline.createInterface({
30
+ input: process.stdin,
31
+ output: process.stdout,
32
+ prompt: 'spiritai> ' // CHANGED: 'flutter-agent>' to 'v6-agent>'
33
+ });
34
+
35
+ async function getSystemInfo() {
36
+ const info = {
37
+ platform: os.platform(),
38
+ arch: os.arch(),
39
+ cwd: currentWorkingDirectory,
40
+ node_version: process.version,
41
+ user: os.userInfo().username,
42
+ home_directory: os.homedir(),
43
+ total_memory: Math.round(os.totalmem() / (1024 * 1024 * 1024)) + 'GB'
44
+ };
45
+
46
+ // Check for development tools
47
+ const tools = [
48
+ { name: 'java', command: 'java -version' },
49
+ { name: 'adb', command: 'adb version' },
50
+ { name: 'git', command: 'git --version' },
51
+ { name: 'npm', command: 'npm --version' },
52
+ { name: 'python', command: 'python --version' },
53
+ { name: 'pip', command: 'pip --version' },
54
+ { name: 'flutter', command: 'flutter --version' },
55
+ { name: 'dart', command: 'dart --version' }
56
+ ];
57
+
58
+ for (const tool of tools) {
59
+ try {
60
+ const { stdout, stderr } = await execAsync(tool.command, { timeout: 3000 });
61
+ info[`${tool.name}_available`] = true;
62
+ info[`${tool.name}_version`] = (stdout || stderr).trim().split('\n')[0];
63
+ } catch {
64
+ info[`${tool.name}_available`] = false;
65
+ }
66
+ }
67
+
68
+ return info;
69
+ }
70
+
71
+ class ToolExecutor {
72
+ static async executeFileSystemTool(command, workingDir) {
73
+ // console.log(`šŸ“ File System Tool: ${command.substring(0, 60)}...`); // REMOVED
74
+ // console.log(`šŸ“‚ Working Directory: ${workingDir || currentWorkingDirectory}`); // REMOVED
75
+
76
+ try {
77
+ // SURGICAL PRECISION COMMANDS (NEW!)
78
+ if (command.startsWith('DELETE_LINE:')) {
79
+ return await this.handleDeleteLine(command, workingDir);
80
+ }
81
+
82
+ if (command.startsWith('REPLACE_LINE:')) {
83
+ return await this.handleReplaceLine(command, workingDir);
84
+ }
85
+
86
+ if (command.startsWith('INSERT_BEFORE:')) {
87
+ return await this.handleInsertBefore(command, workingDir);
88
+ }
89
+
90
+ if (command.startsWith('INSERT_AFTER:')) {
91
+ return await this.handleInsertAfter(command, workingDir);
92
+ }
93
+
94
+ if (command.startsWith('REMOVE_IMPORT:')) {
95
+ return await this.handleRemoveImport(command, workingDir);
96
+ }
97
+
98
+ if (command.startsWith('ADD_IMPORT:')) {
99
+ return await this.handleAddImport(command, workingDir);
100
+ }
101
+
102
+ // EXISTING COMMANDS
103
+ if (command.startsWith('WRITE_FILE:')) {
104
+ return await this.handleDirectFileWrite(command, workingDir);
105
+ }
106
+
107
+ if (command.startsWith('READ_FILE:')) {
108
+ return await this.handleDirectFileRead(command, workingDir);
109
+ }
110
+
111
+ // Handle special file operations
112
+ if (command.includes('create file') || command.includes('write file')) {
113
+ return await this.handleFileOperations(command, workingDir);
114
+ } else {
115
+ return await this.executeSystemCommand(command, workingDir);
116
+ }
117
+ } catch (error) {
118
+ return {
119
+ output: `File system error: ${error.message}`,
120
+ success: false,
121
+ cwd: workingDir || currentWorkingDirectory
122
+ };
123
+ }
124
+ }
125
+
126
+ // ========================================================================
127
+ // SURGICAL PRECISION COMMANDS (NEW!)
128
+ // ========================================================================
129
+
130
+ static async handleDeleteLine(command, workingDir) {
131
+ const targetDir = workingDir || currentWorkingDirectory;
132
+
133
+ try {
134
+ // Parse: DELETE_LINE:filepath:line_number
135
+ const parts = command.substring(12).split(':');
136
+
137
+ if (parts.length < 2) {
138
+ throw new Error('Invalid DELETE_LINE format. Expected: DELETE_LINE:filepath:line_number');
139
+ }
140
+
141
+ const fileName = parts[0].trim();
142
+ const lineNumber = parseInt(parts[1].trim());
143
+
144
+ if (isNaN(lineNumber) || lineNumber < 1) {
145
+ throw new Error('Invalid line number');
146
+ }
147
+
148
+ const filePath = path.resolve(targetDir, fileName);
149
+
150
+ // console.log(` šŸ”Ŗ DELETE_LINE: ${fileName}`); // REMOVED
151
+ // console.log(` šŸ“ Line: ${lineNumber}`); // REMOVED
152
+
153
+ // Read file
154
+ const content = await fs.readFile(filePath, 'utf8');
155
+ const lines = content.split('\n');
156
+
157
+ if (lineNumber > lines.length) {
158
+ throw new Error(`Line ${lineNumber} does not exist (file has ${lines.length} lines)`);
159
+ }
160
+
161
+ // Delete the line (line numbers are 1-indexed)
162
+ const deletedLine = lines[lineNumber - 1];
163
+ lines.splice(lineNumber - 1, 1);
164
+
165
+ // Write back
166
+ await fs.writeFile(filePath, lines.join('\n'), 'utf8');
167
+
168
+ return {
169
+ output: `āœ… Deleted line ${lineNumber} from ${fileName}\nDeleted: "${deletedLine.trim()}"`,
170
+ success: true,
171
+ cwd: targetDir
172
+ };
173
+
174
+ } catch (error) {
175
+ return {
176
+ output: `āŒ DELETE_LINE failed: ${error.message}`,
177
+ success: false,
178
+ cwd: targetDir
179
+ };
180
+ }
181
+ }
182
+
183
+ static async handleReplaceLine(command, workingDir) {
184
+ const targetDir = workingDir || currentWorkingDirectory;
185
+
186
+ try {
187
+ // Parse: REPLACE_LINE:filepath:line_number:new_content
188
+ const parts = command.substring(13).split(':');
189
+
190
+ if (parts.length < 3) {
191
+ throw new Error('Invalid REPLACE_LINE format. Expected: REPLACE_LINE:filepath:line_number:new_content');
192
+ }
193
+
194
+ const fileName = parts[0].trim();
195
+ const lineNumber = parseInt(parts[1].trim());
196
+ const newContent = parts.slice(2).join(':'); // Rejoin in case content has colons
197
+
198
+ if (isNaN(lineNumber) || lineNumber < 1) {
199
+ throw new Error('Invalid line number');
200
+ }
201
+
202
+ const filePath = path.resolve(targetDir, fileName);
203
+
204
+ // console.log(` šŸ”„ REPLACE_LINE: ${fileName}`); // REMOVED
205
+ // console.log(` šŸ“ Line: ${lineNumber}`); // REMOVED
206
+ // console.log(` ✨ New: ${newContent.substring(0, 60)}...`); // REMOVED
207
+
208
+ // Read file
209
+ const content = await fs.readFile(filePath, 'utf8');
210
+ const lines = content.split('\n');
211
+
212
+ if (lineNumber > lines.length) {
213
+ throw new Error(`Line ${lineNumber} does not exist (file has ${lines.length} lines)`);
214
+ }
215
+
216
+ // Replace the line
217
+ const oldLine = lines[lineNumber - 1];
218
+ lines[lineNumber - 1] = newContent;
219
+
220
+ // Write back
221
+ await fs.writeFile(filePath, lines.join('\n'), 'utf8');
222
+
223
+ return {
224
+ output: `āœ… Replaced line ${lineNumber} in ${fileName}\nOld: "${oldLine.trim()}"\nNew: "${newContent.trim()}"`,
225
+ success: true,
226
+ cwd: targetDir
227
+ };
228
+
229
+ } catch (error) {
230
+ return {
231
+ output: `āŒ REPLACE_LINE failed: ${error.message}`,
232
+ success: false,
233
+ cwd: targetDir
234
+ };
235
+ }
236
+ }
237
+
238
+ static async handleInsertBefore(command, workingDir) {
239
+ const targetDir = workingDir || currentWorkingDirectory;
240
+
241
+ try {
242
+ // Parse: INSERT_BEFORE:filepath:line_number:new_content
243
+ const parts = command.substring(14).split(':');
244
+
245
+ if (parts.length < 3) {
246
+ throw new Error('Invalid INSERT_BEFORE format. Expected: INSERT_BEFORE:filepath:line_number:new_content');
247
+ }
248
+
249
+ const fileName = parts[0].trim();
250
+ const lineNumber = parseInt(parts[1].trim());
251
+ const newContent = parts.slice(2).join(':');
252
+
253
+ if (isNaN(lineNumber) || lineNumber < 1) {
254
+ throw new Error('Invalid line number');
255
+ }
256
+
257
+ const filePath = path.resolve(targetDir, fileName);
258
+
259
+ // console.log(` āž• INSERT_BEFORE: ${fileName}`); // REMOVED
260
+ // console.log(` šŸ“ Line: ${lineNumber}`); // REMOVED
261
+ // console.log(` ✨ Content: ${newContent.substring(0, 60)}...`); // REMOVED
262
+
263
+ // Read file
264
+ const content = await fs.readFile(filePath, 'utf8');
265
+ const lines = content.split('\n');
266
+
267
+ // Insert before the line (line numbers are 1-indexed)
268
+ lines.splice(lineNumber - 1, 0, newContent);
269
+
270
+ // Write back
271
+ await fs.writeFile(filePath, lines.join('\n'), 'utf8');
272
+
273
+ return {
274
+ output: `āœ… Inserted line before line ${lineNumber} in ${fileName}\nInserted: "${newContent.trim()}"`,
275
+ success: true,
276
+ cwd: targetDir
277
+ };
278
+
279
+ } catch (error) {
280
+ return {
281
+ output: `āŒ INSERT_BEFORE failed: ${error.message}`,
282
+ success: false,
283
+ cwd: targetDir
284
+ };
285
+ }
286
+ }
287
+
288
+ static async handleInsertAfter(command, workingDir) {
289
+ const targetDir = workingDir || currentWorkingDirectory;
290
+
291
+ try {
292
+ // Parse: INSERT_AFTER:filepath:line_number:new_content
293
+ const parts = command.substring(13).split(':');
294
+
295
+ if (parts.length < 3) {
296
+ throw new Error('Invalid INSERT_AFTER format. Expected: INSERT_AFTER:filepath:line_number:new_content');
297
+ }
298
+
299
+ const fileName = parts[0].trim();
300
+ const lineNumber = parseInt(parts[1].trim());
301
+ const newContent = parts.slice(2).join(':');
302
+
303
+ if (isNaN(lineNumber) || lineNumber < 1) {
304
+ throw new Error('Invalid line number');
305
+ }
306
+
307
+ const filePath = path.resolve(targetDir, fileName);
308
+
309
+ // console.log(` āž• INSERT_AFTER: ${fileName}`); // REMOVED
310
+ // console.log(` šŸ“ Line: ${lineNumber}`); // REMOVED
311
+ // console.log(` ✨ Content: ${newContent.substring(0, 60)}...`); // REMOVED
312
+
313
+ // Read file
314
+ const content = await fs.readFile(filePath, 'utf8');
315
+ const lines = content.split('\n');
316
+
317
+ // Insert after the line (line numbers are 1-indexed)
318
+ lines.splice(lineNumber, 0, newContent);
319
+
320
+ // Write back
321
+ await fs.writeFile(filePath, lines.join('\n'), 'utf8');
322
+
323
+ return {
324
+ output: `āœ… Inserted line after line ${lineNumber} in ${fileName}\nInserted: "${newContent.trim()}"`,
325
+ success: true,
326
+ cwd: targetDir
327
+ };
328
+
329
+ } catch (error) {
330
+ return {
331
+ output: `āŒ INSERT_AFTER failed: ${error.message}`,
332
+ success: false,
333
+ cwd: targetDir
334
+ };
335
+ }
336
+ }
337
+
338
+ static async handleRemoveImport(command, workingDir) {
339
+ const targetDir = workingDir || currentWorkingDirectory;
340
+
341
+ try {
342
+ // Parse: REMOVE_IMPORT:filepath:import_package
343
+ const parts = command.substring(14).split(':');
344
+
345
+ if (parts.length < 2) {
346
+ throw new Error('Invalid REMOVE_IMPORT format. Expected: REMOVE_IMPORT:filepath:import_package');
347
+ }
348
+
349
+ const fileName = parts[0].trim();
350
+ const importPackage = parts.slice(1).join(':').trim();
351
+
352
+ const filePath = path.resolve(targetDir, fileName);
353
+
354
+ // console.log(` šŸ—‘ļø REMOVE_IMPORT: ${fileName}`); // REMOVED
355
+ // console.log(` šŸ“¦ Package: ${importPackage}`); // REMOVED
356
+
357
+ // Read file
358
+ const content = await fs.readFile(filePath, 'utf8');
359
+ const lines = content.split('\n');
360
+
361
+ // Find and remove the import line
362
+ let removed = false;
363
+ let removedLine = '';
364
+ const newLines = lines.filter(line => {
365
+ const trimmed = line.trim();
366
+ // Match: import 'package:...' or import "package:..."
367
+ if (trimmed.startsWith('import ') && trimmed.includes(importPackage)) {
368
+ removed = true;
369
+ removedLine = line;
370
+ return false;
371
+ }
372
+ return true;
373
+ });
374
+
375
+ if (!removed) {
376
+ throw new Error(`Import not found: ${importPackage}`);
377
+ }
378
+
379
+ // Write back
380
+ await fs.writeFile(filePath, newLines.join('\n'), 'utf8');
381
+
382
+ return {
383
+ output: `āœ… Removed import from ${fileName}\nRemoved: "${removedLine.trim()}"`,
384
+ success: true,
385
+ cwd: targetDir
386
+ };
387
+
388
+ } catch (error) {
389
+ return {
390
+ output: `āŒ REMOVE_IMPORT failed: ${error.message}`,
391
+ success: false,
392
+ cwd: targetDir
393
+ };
394
+ }
395
+ }
396
+
397
+ static async handleAddImport(command, workingDir) {
398
+ const targetDir = workingDir || currentWorkingDirectory;
399
+
400
+ try {
401
+ // Parse: ADD_IMPORT:filepath:import_statement
402
+ const parts = command.substring(11).split(':');
403
+
404
+ if (parts.length < 2) {
405
+ throw new Error('Invalid ADD_IMPORT format. Expected: ADD_IMPORT:filepath:import_statement');
406
+ }
407
+
408
+ const fileName = parts[0].trim();
409
+ const importStatement = parts.slice(1).join(':').trim();
410
+
411
+ const filePath = path.resolve(targetDir, fileName);
412
+
413
+ // console.log(` āž• ADD_IMPORT: ${fileName}`); // REMOVED
414
+ // console.log(` šŸ“¦ Import: ${importStatement}`); // REMOVED
415
+
416
+ // Read file
417
+ const content = await fs.readFile(filePath, 'utf8');
418
+ const lines = content.split('\n');
419
+
420
+ // Format the import statement
421
+ let formattedImport = importStatement;
422
+ if (!formattedImport.startsWith('import ')) {
423
+ formattedImport = `import 'package:${importStatement}';`;
424
+ }
425
+ if (!formattedImport.endsWith(';')) {
426
+ formattedImport += ';';
427
+ }
428
+
429
+ // Check if import already exists
430
+ const importExists = lines.some(line =>
431
+ line.trim().includes(importStatement) && line.trim().startsWith('import ')
432
+ );
433
+
434
+ if (importExists) {
435
+ return {
436
+ output: `ā„¹ļø Import already exists in ${fileName}`,
437
+ success: true,
438
+ cwd: targetDir
439
+ };
440
+ }
441
+
442
+ // Find the last import statement
443
+ let lastImportIndex = -1;
444
+ for (let i = 0; i < lines.length; i++) {
445
+ if (lines[i].trim().startsWith('import ')) {
446
+ lastImportIndex = i;
447
+ }
448
+ }
449
+
450
+ // Insert after the last import, or at the beginning if no imports
451
+ if (lastImportIndex >= 0) {
452
+ lines.splice(lastImportIndex + 1, 0, formattedImport);
453
+ } else {
454
+ // Insert at the beginning, after any leading comments
455
+ let insertIndex = 0;
456
+ for (let i = 0; i < lines.length; i++) {
457
+ const trimmed = lines[i].trim();
458
+ if (trimmed === '' || trimmed.startsWith('//') || trimmed.startsWith('/*')) {
459
+ insertIndex = i + 1;
460
+ } else {
461
+ break;
462
+ }
463
+ }
464
+ lines.splice(insertIndex, 0, formattedImport);
465
+ }
466
+
467
+ // Write back
468
+ await fs.writeFile(filePath, lines.join('\n'), 'utf8');
469
+
470
+ return {
471
+ output: `āœ… Added import to ${fileName}\nAdded: "${formattedImport}"`,
472
+ success: true,
473
+ cwd: targetDir
474
+ };
475
+
476
+ } catch (error) {
477
+ return {
478
+ output: `āŒ ADD_IMPORT failed: ${error.message}`,
479
+ success: false,
480
+ cwd: targetDir
481
+ };
482
+ }
483
+ }
484
+
485
+ // ========================================================================
486
+ // EXISTING COMMANDS (UNCHANGED)
487
+ // ========================================================================
488
+
489
+ static async handleDirectFileWrite(command, workingDir) {
490
+ const targetDir = workingDir || currentWorkingDirectory;
491
+
492
+ try {
493
+ // Parse command: WRITE_FILE:filepath|||CONTENT|||actual content here
494
+ const parts = command.substring(11).split('|||CONTENT|||');
495
+
496
+ if (parts.length !== 2) {
497
+ throw new Error('Invalid WRITE_FILE command format');
498
+ }
499
+
500
+ const fileName = parts[0].trim();
501
+ const fileContent = parts[1]; // Don't trim - preserve formatting
502
+ const filePath = path.resolve(targetDir, fileName);
503
+
504
+ // console.log(` āœļø Writing: ${fileName}`); // REMOVED
505
+ // console.log(` šŸ“ Size: ${fileContent.length} bytes`); // REMOVED
506
+
507
+ // Create directories if needed
508
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
509
+
510
+ // Write file with UTF-8 encoding
511
+ await fs.writeFile(filePath, fileContent, 'utf8');
512
+
513
+ // Verify file was written
514
+ const stats = await fs.stat(filePath);
515
+
516
+ return {
517
+ output: `āœ… Created file: ${fileName} (${stats.size} bytes)`,
518
+ success: true,
519
+ cwd: targetDir,
520
+ bytes_written: stats.size
521
+ };
522
+
523
+ } catch (error) {
524
+ return {
525
+ output: `āŒ Failed to write file: ${error.message}`,
526
+ success: false,
527
+ cwd: targetDir
528
+ };
529
+ }
530
+ }
531
+
532
+ static async handleDirectFileRead(command, workingDir) {
533
+ const targetDir = workingDir || currentWorkingDirectory;
534
+
535
+ try {
536
+ // Parse command: READ_FILE:filepath
537
+ const fileName = command.substring(10).trim(); // Remove 'READ_FILE:'
538
+
539
+ // Handle both absolute and relative paths
540
+ let filePath;
541
+ if (path.isAbsolute(fileName)) {
542
+ filePath = fileName;
543
+ } else {
544
+ filePath = path.resolve(targetDir, fileName);
545
+ }
546
+
547
+ // console.log(` šŸ“– Reading: ${fileName}`); // REMOVED
548
+ // console.log(` šŸ“ Full path: ${filePath}`); // REMOVED
549
+
550
+ // Check if file exists
551
+ try {
552
+ await fs.access(filePath);
553
+ } catch (error) {
554
+ throw new Error(`File not found: ${filePath}`);
555
+ }
556
+
557
+ // Read file with UTF-8 encoding
558
+ const content = await fs.readFile(filePath, 'utf8');
559
+
560
+ // console.log(` āœ“ Read ${content.length} bytes`); // REMOVED
561
+
562
+ return {
563
+ output: content,
564
+ success: true,
565
+ cwd: targetDir,
566
+ bytes_read: content.length,
567
+ file_path: filePath
568
+ };
569
+
570
+ } catch (error) {
571
+ // console.log(` āŒ Read failed: ${error.message}`); // REMOVED
572
+ return {
573
+ output: `Failed to read file: ${error.message}`,
574
+ success: false,
575
+ cwd: targetDir,
576
+ error: error.message
577
+ };
578
+ }
579
+ }
580
+
581
+ static async executeEmulatorTool(command, workingDir) {
582
+ // console.log(`šŸ“± Emulator Tool: ${command}`); // REMOVED
583
+ return await this.executeSystemCommand(command, workingDir);
584
+ }
585
+
586
+ static async executeSystemTool(command, workingDir) {
587
+ // console.log(`āš™ļø System Tool: ${command}`); // REMOVED
588
+ return await this.executeSystemCommand(command, workingDir);
589
+ }
590
+
591
+ static async handleFileOperations(command, workingDir) {
592
+ const targetDir = workingDir || currentWorkingDirectory;
593
+ const platform = os.platform();
594
+
595
+ try {
596
+ // Handle Windows-specific commands
597
+ if (platform === 'win32') {
598
+ // Handle Windows directory creation: md
599
+ if (command.startsWith('md ')) {
600
+ const dirName = command.replace('md ', '').trim();
601
+ const dirPath = path.resolve(targetDir, dirName);
602
+
603
+ await fs.mkdir(dirPath, { recursive: true });
604
+
605
+ return {
606
+ output: `Created directory: ${dirName} in ${targetDir}`,
607
+ success: true,
608
+ cwd: targetDir
609
+ };
610
+ }
611
+
612
+ // Handle Windows file creation: echo. > filename
613
+ if (command.includes('echo. >')) {
614
+ const fileName = command.split('echo. >')[1].trim();
615
+ const filePath = path.resolve(targetDir, fileName);
616
+
617
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
618
+ await fs.writeFile(filePath, '', 'utf8');
619
+
620
+ return {
621
+ output: `Created file: ${fileName} in ${targetDir}`,
622
+ success: true,
623
+ cwd: targetDir
624
+ };
625
+ }
626
+ }
627
+
628
+ // Handle Unix/Linux commands
629
+ if (command.startsWith('touch ')) {
630
+ const fileName = command.replace('touch ', '').trim();
631
+ const filePath = path.resolve(targetDir, fileName);
632
+
633
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
634
+ await fs.writeFile(filePath, '', 'utf8');
635
+
636
+ return {
637
+ output: `Created file: ${fileName} in ${targetDir}`,
638
+ success: true,
639
+ cwd: targetDir
640
+ };
641
+ }
642
+
643
+ // Handle mkdir commands with -p flag
644
+ if (command.startsWith('mkdir ')) {
645
+ const dirName = command.replace('mkdir -p ', '').replace('mkdir ', '').trim();
646
+ const dirPath = path.resolve(targetDir, dirName);
647
+
648
+ await fs.mkdir(dirPath, { recursive: true });
649
+
650
+ return {
651
+ output: `Created directory: ${dirName} in ${targetDir}`,
652
+ success: true,
653
+ cwd: targetDir
654
+ };
655
+ }
656
+
657
+ // If no special handling matched, execute as system command
658
+ return await this.executeSystemCommand(command, workingDir);
659
+
660
+ } catch (error) {
661
+ return {
662
+ output: `File operation error: ${error.message}`,
663
+ success: false,
664
+ cwd: targetDir
665
+ };
666
+ }
667
+ }
668
+
669
+ static async executeSystemCommand(command, workingDir) {
670
+ const execDir = workingDir || currentWorkingDirectory;
671
+
672
+ try {
673
+ const { stdout, stderr } = await execAsync(command, {
674
+ cwd: execDir,
675
+ timeout: 600000, // 10 minutes
676
+ maxBuffer: 50 * 1024 * 1024, // 50MB buffer
677
+ shell: true
678
+ });
679
+
680
+ // Return full output (stdout + stderr combined)
681
+ const output = (stdout || '') + (stderr || '');
682
+
683
+ // Success if no stderr or stderr is just warnings
684
+ const success = !stderr || stderr.trim().length === 0;
685
+
686
+ return {
687
+ output: output || 'Command completed successfully',
688
+ success: success,
689
+ cwd: execDir,
690
+ command: command
691
+ };
692
+
693
+ } catch (error) {
694
+ // Return full error output (combine stdout and stderr)
695
+ const output = (error.stdout || '') + (error.stderr || '') || error.message;
696
+
697
+ return {
698
+ output: `Command failed: ${output}`,
699
+ success: false,
700
+ cwd: execDir,
701
+ command: command,
702
+ exit_code: error.code
703
+ };
704
+ }
705
+ }
706
+ }
707
+
708
+ async function executeCommand(taskData) {
709
+ const { task_id, command, tool_type, working_directory } = taskData;
710
+
711
+ // console.log(`\nšŸ“‹ Task: ${task_id}`); // REMOVED
712
+ // console.log(`šŸ› ļø Tool: ${tool_type || 'system'}`); // REMOVED
713
+ // console.log(`šŸ’» Command: ${command.substring(0, 80)}...`); // REMOVED
714
+ // console.log(`šŸ“‚ Directory: ${working_directory || currentWorkingDirectory}`); // REMOVED
715
+
716
+ try {
717
+ let result;
718
+
719
+ switch (tool_type) {
720
+ case 'filesystem_tool':
721
+ result = await ToolExecutor.executeFileSystemTool(command, working_directory);
722
+ break;
723
+
724
+ case 'emulator_tool':
725
+ result = await ToolExecutor.executeEmulatorTool(command, working_directory);
726
+ break;
727
+
728
+ case 'system_tool':
729
+ result = await ToolExecutor.executeSystemTool(command, working_directory);
730
+ break;
731
+
732
+ default:
733
+ result = await ToolExecutor.executeSystemCommand(command, working_directory);
734
+ break;
735
+ }
736
+
737
+ result.task_id = task_id;
738
+ result.tool_type = tool_type;
739
+ result.execution_time = new Date().toISOString();
740
+
741
+ return result;
742
+
743
+ } catch (error) {
744
+ return {
745
+ task_id: task_id,
746
+ output: `Execution error: ${error.message}`,
747
+ success: false,
748
+ cwd: working_directory || currentWorkingDirectory,
749
+ tool_type: tool_type,
750
+ command: command,
751
+ error: error.message
752
+ };
753
+ }
754
+ }
755
+
756
+ function connectToBrain() {
757
+ // MODIFIED: Welcome message with a styled, detailed security box
758
+ const whiteColor = '\x1b[37m'; // White for the main heading
759
+ const grayColor = '\x1b[90m'; // Light gray for the security warning text
760
+ const resetColor = '\x1b[0m';
761
+
762
+ // Console log a large block of text with proper spacing and color
763
+ const welcomeMessage = `${whiteColor}
764
+ ā— Welcome to Spirit AI
765
+ ${resetColor}
766
+ ${grayColor}
767
+ Spirit AI provides full access to your working environment, including the
768
+ ability to read, create, modify, edit, and delete files, and execute shell
769
+ commands with your consent.
770
+ This agent has extensive permissions - Use at your own risk
771
+ ${resetColor}`;
772
+
773
+ console.log(welcomeMessage);
774
+ // console.log('šŸš€ Flutter Development Agent Starting...'); // REMOVED
775
+ // console.log(`🌐 Connecting to: ${serverUrl}`); // REMOVED
776
+ // console.log(`šŸ”— Connection code: ${connectionCode}`); // REMOVED
777
+ // console.log('šŸ”Ŗ Surgical Precision Commands: ENABLED āœ…'); // REMOVED
778
+
779
+ socket = io(serverUrl, {
780
+ reconnection: true,
781
+ reconnectionDelay: 2000,
782
+ reconnectionAttempts: 5,
783
+ timeout: 20000
784
+ });
785
+
786
+ socket.on("connect", async () => {
787
+ // Removed: console.log('āœ… Connected to brain! Connection secured.');
788
+
789
+ const systemInfo = await getSystemInfo();
790
+ // System Info Logs REMOVED
791
+ // console.log('šŸ“Š System Info:');
792
+ // console.log(` Platform: ${systemInfo.platform} (${systemInfo.arch})`);
793
+ // console.log(` Node: ${systemInfo.node_version}`);
794
+ // console.log(` Flutter: ${systemInfo.flutter_available ? 'āœ…' : 'āŒ'}`);
795
+ // console.log(` Dart: ${systemInfo.dart_available ? 'āœ…' : 'āŒ'}`);
796
+ // console.log(` Git: ${systemInfo.git_available ? 'āœ…' : 'āŒ'}`);
797
+
798
+ socket.emit('cli_connect', {
799
+ connection_code: connectionCode,
800
+ system_info: systemInfo
801
+ });
802
+ });
803
+
804
+ socket.on('cli_connected', (data) => {
805
+ // CHANGED: ANSI escape codes for WHITE color (\x1b[37m) and reset (\x1b[0m)
806
+ const whiteColor = '\x1b[37m';
807
+ const resetColor = '\x1b[0m';
808
+
809
+ const banner = `${whiteColor}
810
+ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—
811
+ ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā•šā•ā•ā–ˆā–ˆā•”ā•ā•ā• ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘
812
+ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘ā–ˆā–ˆā•‘
813
+ ā•šā•ā•ā•ā•ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā•ā• ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘
814
+ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘
815
+ ā•šā•ā•ā•ā•ā•ā•ā•ā•šā•ā• ā•šā•ā•ā•šā•ā• ā•šā•ā•ā•šā•ā• ā•šā•ā• ā•šā•ā• ā•šā•ā•ā•šā•ā•
816
+
817
+ by Niyantri Labs
818
+
819
+ ${resetColor}`;
820
+
821
+
822
+ console.log(banner);
823
+ // console.log('šŸŽÆ Agent successfully connected and ready!'); // REMOVED
824
+ // console.log('šŸ’¬ You can now send commands through the web interface'); // REMOVED
825
+ // console.log('šŸ“ Type "status" to check connection, "exit" to quit\n'); // REMOVED
826
+ rl.prompt();
827
+ });
828
+
829
+ socket.on('execute_command', async (data) => {
830
+ // console.log('\n⚔ Received command from brain...'); // REMOVED
831
+
832
+ const result = await executeCommand(data);
833
+
834
+ socket.emit('command_result', {
835
+ task_id: data.task_id,
836
+ output: result.output,
837
+ success: result.success,
838
+ cwd: result.cwd,
839
+ tool_type: result.tool_type,
840
+ timestamp: new Date().toISOString(),
841
+ working_directory: result.cwd
842
+ });
843
+
844
+ // const status = result.success ? 'āœ… Success' : 'āŒ Failed'; // Keep internal logic
845
+ // console.log(`${status}: ${result.command || data.command}`); // REMOVED
846
+
847
+ // if (!result.success) {
848
+ // console.log(`Error details: ${result.output}`); // REMOVED
849
+ // } else if (result.output && result.output.length < 200) {
850
+ // console.log(`Output: ${result.output}`); // REMOVED
851
+ // }
852
+
853
+ // console.log(''); // REMOVED
854
+ rl.prompt();
855
+ });
856
+
857
+ socket.on("disconnect", (reason) => {
858
+ // console.log(`\nšŸ”Œ Disconnected: ${reason}`); // REMOVED
859
+ if (reason === 'io server disconnect') {
860
+ // console.log('Server initiated disconnect. Exiting...'); // REMOVED
861
+ process.exit(0);
862
+ } else {
863
+ // console.log('Attempting to reconnect...'); // REMOVED
864
+ }
865
+ });
866
+
867
+ socket.on('connect_error', (error) => {
868
+ // console.log(`āŒ Connection failed: ${error.message}`); // REMOVED
869
+ });
870
+
871
+ socket.on('reconnect', (attemptNumber) => {
872
+ // console.log(`šŸ”„ Reconnected after ${attemptNumber} attempts`); // REMOVED
873
+ rl.prompt();
874
+ });
875
+ }
876
+
877
+ // CLI interface (Local logs kept for usability)
878
+ rl.on('line', (input) => {
879
+ const cmd = input.trim().toLowerCase();
880
+
881
+ if (cmd === 'exit' || cmd === 'quit') {
882
+ console.log('šŸ‘‹ Goodbye!');
883
+ process.exit(0);
884
+ }
885
+
886
+ if (cmd === 'status') {
887
+ console.log('\nšŸ“Š Agent Status:');
888
+ console.log(` Connected: ${socket?.connected ? 'āœ… Yes' : 'āŒ No'}`);
889
+ console.log(` Directory: ${currentWorkingDirectory}`);
890
+ console.log(` Server: ${serverUrl}`);
891
+ console.log(` Code: ${connectionCode}`);
892
+ console.log(` Surgical Mode: āœ… ENABLED`);
893
+ console.log('');
894
+ }
895
+
896
+ if (cmd === 'help') {
897
+ console.log('\nšŸ†˜ Available Commands:');
898
+ console.log(' status - Show connection status');
899
+ console.log(' help - Show this help');
900
+ console.log(' exit - Exit the agent');
901
+ console.log(' clear - Clear the screen');
902
+ console.log('\nšŸ’¬ Send commands through the web interface for mobile development!');
903
+ console.log('\nšŸ”Ŗ Surgical Precision Commands Supported:');
904
+ console.log(' DELETE_LINE - Remove specific line');
905
+ console.log(' REPLACE_LINE - Replace specific line');
906
+ console.log(' INSERT_BEFORE - Insert line before position');
907
+ console.log(' INSERT_AFTER - Insert line after position');
908
+ console.log(' REMOVE_IMPORT - Remove unused import');
909
+ console.log(' ADD_IMPORT - Add missing import');
910
+ console.log('');
911
+ }
912
+
913
+ if (cmd === 'clear') {
914
+ console.clear();
915
+ console.log('šŸš€ Spirit AI'); // CHANGED: 'Flutter Development Agent' to 'v6 Agent'
916
+ console.log(`šŸ”— Connected to: ${serverUrl}`);
917
+ console.log('šŸ”Ŗ Surgical Precision: ENABLED');
918
+ console.log('');
919
+ }
920
+
921
+ if (input.trim() && !['exit', 'quit', 'status', 'help', 'clear'].includes(cmd)) {
922
+ console.log('šŸ’¬ For Flutter development, use the web interface.');
923
+ console.log(' Direct commands are not supported in this mode.');
924
+ }
925
+
926
+ rl.prompt();
927
+ });
928
+
929
+ // Graceful shutdown
930
+ process.on('SIGINT', () => {
931
+ console.log('\n\nšŸ‘‹ Shutting down Spirit AI...'); // CHANGED: 'Flutter Agent' to 'v6 Agent'
932
+ if (socket && socket.connected) {
933
+ socket.disconnect();
934
+ }
935
+ process.exit(0);
936
+ });
937
+
938
+ process.on('SIGTERM', () => {
939
+ console.log('\n\nšŸ›‘ Received termination signal. Shutting down...');
940
+ if (socket && socket.connected) {
941
+ socket.disconnect();
942
+ }
943
+ process.exit(0);
944
+ });
945
+
946
+ process.on('uncaughtException', (error) => {
947
+ console.error('šŸ’„ Uncaught Exception:', error);
948
+ console.error('Stack:', error.stack);
949
+ process.exit(1);
950
+ });
951
+
952
+ process.on('unhandledRejection', (reason, promise) => {
953
+ console.error('šŸ’„ Unhandled Rejection at:', promise, 'reason:', reason);
954
+ process.exit(1);
955
+ });
956
+
957
+ // Start the connection
958
+ // console.log('šŸ”§ Initializing Flutter Development Agent...'); // REMOVED
959
+ // console.log('šŸ”Ŗ Surgical Precision Mode: ENABLED'); // REMOVED
960
+ connectToBrain();
Binary file
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@niyantrilabs/spiritai",
3
+ "version": "1.0.0",
4
+ "description": "Spirit AI - Mobile Autonomous Developer for multi-platform app development",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "spiritai": "./index.js",
8
+ "spirit": "./index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node index.js",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [
15
+ "spiritai",
16
+ "spirit-ai",
17
+ "niyantri-labs",
18
+ "autonomous-developer",
19
+ "ai-developer",
20
+ "mobile-development",
21
+ "flutter",
22
+ "android",
23
+ "ios",
24
+ "react-native",
25
+ "kotlin",
26
+ "swift",
27
+ "dart",
28
+ "cross-platform",
29
+ "multi-platform",
30
+ "cli",
31
+ "ai-assistant",
32
+ "code-generation"
33
+ ],
34
+ "author": "Niyantri Labs",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "readline": "^1.3.0",
38
+ "socket.io-client": "^4.7.2",
39
+ "yargs": "^17.7.2"
40
+ },
41
+ "engines": {
42
+ "node": ">=16.0.0"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/niyantrilabs/spiritai.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/niyantrilabs/spiritai/issues"
50
+ },
51
+ "homepage": "https://github.com/niyantrilabs/spiritai#readme",
52
+ "preferGlobal": true,
53
+ "os": [
54
+ "darwin",
55
+ "linux",
56
+ "win32"
57
+ ],
58
+ "cpu": [
59
+ "x64",
60
+ "arm64"
61
+ ]
62
+ }