@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 +21 -0
- package/README.md +76 -0
- package/index.js +960 -0
- package/niyantrilabs-spiritai-1.0.0.tgz +0 -0
- package/package.json +62 -0
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
|
+
}
|