sapper-iq 1.0.9 ā 1.0.10
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/package.json +1 -1
- package/sapper.mjs +53 -700
package/package.json
CHANGED
package/sapper.mjs
CHANGED
|
@@ -10,8 +10,13 @@ import { dirname, join } from 'path';
|
|
|
10
10
|
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
// Initialize versioning
|
|
15
|
+
let CURRENT_VERSION = "1.1.0";
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(join(__dirname, 'package.json'), 'utf8'));
|
|
18
|
+
CURRENT_VERSION = pkg.version;
|
|
19
|
+
} catch (e) {}
|
|
15
20
|
|
|
16
21
|
const spinner = ora();
|
|
17
22
|
const CONTEXT_FILE = '.sapper_context.json';
|
|
@@ -24,65 +29,13 @@ let rl = readline.createInterface({
|
|
|
24
29
|
historySize: 100
|
|
25
30
|
});
|
|
26
31
|
|
|
27
|
-
// Helper function to safely prompt for input
|
|
28
32
|
async function safeQuestion(query) {
|
|
29
33
|
return new Promise((resolve) => {
|
|
30
34
|
process.stdout.write(query);
|
|
31
|
-
rl.once('line', (answer) => {
|
|
32
|
-
resolve(answer.trim());
|
|
33
|
-
});
|
|
35
|
+
rl.once('line', (answer) => { resolve(answer.trim()); });
|
|
34
36
|
});
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
// Helper function to check for updates
|
|
38
|
-
async function checkForUpdates() {
|
|
39
|
-
try {
|
|
40
|
-
const response = await fetch('https://registry.npmjs.org/sapper-iq/latest');
|
|
41
|
-
const data = await response.json();
|
|
42
|
-
const latestVersion = data.version;
|
|
43
|
-
|
|
44
|
-
if (latestVersion && latestVersion !== CURRENT_VERSION) {
|
|
45
|
-
console.log(chalk.yellow('š UPDATE AVAILABLE!'));
|
|
46
|
-
console.log(chalk.gray(` Current: v${CURRENT_VERSION}`));
|
|
47
|
-
console.log(chalk.green(` Latest: v${latestVersion}`));
|
|
48
|
-
console.log(chalk.cyan(' Run: npm update -g sapper-iq\n'));
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
// Silently fail if update check fails
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Helper function to update sapper
|
|
56
|
-
async function updateSapper() {
|
|
57
|
-
console.log(chalk.cyan('š Updating Sapper...'));
|
|
58
|
-
const confirm = await safeQuestion(chalk.yellow('Continue with update? (y/n): '));
|
|
59
|
-
if (confirm.toLowerCase() === 'y') {
|
|
60
|
-
return new Promise((resolve) => {
|
|
61
|
-
const proc = spawn('npm', ['update', '-g', 'sapper-iq'], {
|
|
62
|
-
stdio: 'inherit'
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
proc.on('close', (code) => {
|
|
66
|
-
recreateReadline();
|
|
67
|
-
if (code === 0) {
|
|
68
|
-
console.log(chalk.green('\nā
Sapper updated successfully!'));
|
|
69
|
-
console.log(chalk.gray('Please restart Sapper to use the new version.\n'));
|
|
70
|
-
} else {
|
|
71
|
-
console.log(chalk.red('\nā Update failed. Try manually: npm update -g sapper-iq\n'));
|
|
72
|
-
}
|
|
73
|
-
resolve();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
proc.on('error', (err) => {
|
|
77
|
-
recreateReadline();
|
|
78
|
-
console.log(chalk.red(`\nā Update error: ${err.message}\n`));
|
|
79
|
-
resolve();
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Helper function to recreate readline after shell commands
|
|
86
39
|
function recreateReadline() {
|
|
87
40
|
rl.close();
|
|
88
41
|
rl = readline.createInterface({
|
|
@@ -93,715 +46,115 @@ function recreateReadline() {
|
|
|
93
46
|
});
|
|
94
47
|
}
|
|
95
48
|
|
|
96
|
-
// Specialized prompt profiles for different tasks
|
|
97
|
-
const PROMPT_PROFILES = {
|
|
98
|
-
documentation: `
|
|
99
|
-
šÆ DOCUMENTATION MODE ACTIVATED
|
|
100
|
-
Write comprehensive technical documentation. Execute tools to analyze codebase first.
|
|
101
|
-
|
|
102
|
-
Documentation Types & Formats:
|
|
103
|
-
|
|
104
|
-
**Use Cases:**
|
|
105
|
-
Title: [Action User Wants to Perform]
|
|
106
|
-
- Actor: [Who performs the action]
|
|
107
|
-
- Preconditions: [What must be true before]
|
|
108
|
-
- Main Flow:
|
|
109
|
-
1. User does X
|
|
110
|
-
2. System responds with Y
|
|
111
|
-
3. User confirms Z
|
|
112
|
-
- Postconditions: [Result after completion]
|
|
113
|
-
- Alternative Flows: [Error cases, edge cases]
|
|
114
|
-
|
|
115
|
-
**User Stories:**
|
|
116
|
-
"As a [role], I want [feature] so that [benefit]"
|
|
117
|
-
- Acceptance Criteria:
|
|
118
|
-
- Given [context]
|
|
119
|
-
- When [action]
|
|
120
|
-
- Then [result]
|
|
121
|
-
|
|
122
|
-
**BRD (Business Requirements Document):**
|
|
123
|
-
## Background
|
|
124
|
-
## Problem Statement
|
|
125
|
-
## Objectives
|
|
126
|
-
## Functional Requirements (numbered list)
|
|
127
|
-
## Non-Functional Requirements (performance, security, scalability)
|
|
128
|
-
## Success Metrics
|
|
129
|
-
## Timeline & Milestones
|
|
130
|
-
|
|
131
|
-
**Technical Documentation:**
|
|
132
|
-
- API endpoints with request/response examples
|
|
133
|
-
- Architecture diagrams (use mermaid)
|
|
134
|
-
- Setup instructions
|
|
135
|
-
- Environment variables
|
|
136
|
-
|
|
137
|
-
Execute [TOOL:LIST] and [TOOL:READ] to understand project before documenting.`,
|
|
138
|
-
|
|
139
|
-
backend: `
|
|
140
|
-
šÆ BACKEND MODE ACTIVATED - Node.js Specialist
|
|
141
|
-
Build server-side APIs, business logic, and integrations using Node.js/Express.
|
|
142
|
-
|
|
143
|
-
Tech Stack & Standards:
|
|
144
|
-
- Framework: Express.js or Fastify
|
|
145
|
-
- Runtime: Node.js (ES6+ with async/await)
|
|
146
|
-
- Architecture: MVC, Clean Architecture, or layered approach
|
|
147
|
-
|
|
148
|
-
Implementation Checklist:
|
|
149
|
-
ā RESTful API Design:
|
|
150
|
-
- GET /api/resource (list/read)
|
|
151
|
-
- POST /api/resource (create)
|
|
152
|
-
- PUT /PATCH /api/resource/:id (update)
|
|
153
|
-
- DELETE /api/resource/:id (delete)
|
|
154
|
-
- Return proper status codes: 200, 201, 400, 401, 404, 500
|
|
155
|
-
|
|
156
|
-
ā Request Handling:
|
|
157
|
-
- Use express.json() for body parsing
|
|
158
|
-
- Validate inputs with joi or zod
|
|
159
|
-
- Implement error middleware
|
|
160
|
-
- Add request logging (morgan/winston)
|
|
161
|
-
|
|
162
|
-
ā Security:
|
|
163
|
-
- Use helmet for security headers
|
|
164
|
-
- Implement CORS properly
|
|
165
|
-
- JWT authentication with bcrypt password hashing
|
|
166
|
-
- Rate limiting with express-rate-limit
|
|
167
|
-
- Input sanitization
|
|
168
|
-
|
|
169
|
-
ā Project Structure:
|
|
170
|
-
/src
|
|
171
|
-
/routes - API endpoints
|
|
172
|
-
/controllers - Request handlers
|
|
173
|
-
/services - Business logic
|
|
174
|
-
/models - Data models
|
|
175
|
-
/middleware - Auth, validation, errors
|
|
176
|
-
/config - Environment configs
|
|
177
|
-
/utils - Helper functions
|
|
178
|
-
|
|
179
|
-
ā Best Practices:
|
|
180
|
-
- Use environment variables (.env with dotenv)
|
|
181
|
-
- Implement graceful shutdown
|
|
182
|
-
- Add health check endpoint: GET /health
|
|
183
|
-
- Use async/await with try-catch
|
|
184
|
-
- Create separate router files
|
|
185
|
-
|
|
186
|
-
Execute tools to create Express server structure immediately.`,
|
|
187
|
-
|
|
188
|
-
frontend: `
|
|
189
|
-
šÆ FRONTEND MODE ACTIVATED
|
|
190
|
-
Focus on UI components, styling, and user interactions.
|
|
191
|
-
|
|
192
|
-
Implementation Rules:
|
|
193
|
-
- Component-based architecture (React/Vue/Svelte)
|
|
194
|
-
- Responsive design with mobile-first approach
|
|
195
|
-
- Use Tailwind CSS or styled-components
|
|
196
|
-
- Implement proper state management
|
|
197
|
-
- Add loading states and error boundaries
|
|
198
|
-
- Handle forms with validation
|
|
199
|
-
- Optimize for performance (lazy loading, memoization)
|
|
200
|
-
|
|
201
|
-
Execute tools to create component structure immediately.`,
|
|
202
|
-
|
|
203
|
-
testing: `
|
|
204
|
-
šÆ TESTING MODE ACTIVATED
|
|
205
|
-
Write comprehensive test suites with high coverage.
|
|
206
|
-
|
|
207
|
-
Test Requirements:
|
|
208
|
-
- Unit tests: Test individual functions/components
|
|
209
|
-
- Integration tests: Test feature workflows
|
|
210
|
-
- Use describe/it blocks with clear names
|
|
211
|
-
- Add assertions: expect().toBe(), toEqual(), toHaveBeenCalled()
|
|
212
|
-
- Mock external dependencies
|
|
213
|
-
- Test edge cases and error scenarios
|
|
214
|
-
- Aim for 80%+ coverage
|
|
215
|
-
|
|
216
|
-
Execute tools to create test files immediately.`,
|
|
217
|
-
|
|
218
|
-
database: `
|
|
219
|
-
šÆ DATABASE MODE ACTIVATED - PostgreSQL Specialist
|
|
220
|
-
Design schemas, write migrations, and optimize queries for PostgreSQL.
|
|
221
|
-
|
|
222
|
-
Tech Stack:
|
|
223
|
-
- Database: PostgreSQL 14+
|
|
224
|
-
- ORM Options: Prisma (recommended), Sequelize, or TypeORM
|
|
225
|
-
- Query Builder: Knex.js
|
|
226
|
-
- Migrations: Prisma Migrate or Knex migrations
|
|
227
|
-
|
|
228
|
-
Schema Design Best Practices:
|
|
229
|
-
ā Data Types:
|
|
230
|
-
- Use SERIAL or UUID for primary keys
|
|
231
|
-
- TEXT for variable strings (not VARCHAR)
|
|
232
|
-
- TIMESTAMP WITH TIME ZONE for dates
|
|
233
|
-
- JSONB for flexible data (indexed)
|
|
234
|
-
- ENUM types for fixed choices
|
|
235
|
-
|
|
236
|
-
ā Table Structure:
|
|
237
|
-
CREATE TABLE users (
|
|
238
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
239
|
-
email TEXT UNIQUE NOT NULL,
|
|
240
|
-
password_hash TEXT NOT NULL,
|
|
241
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
242
|
-
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
ā Relationships:
|
|
246
|
-
- Foreign Keys: REFERENCES other_table(id) ON DELETE CASCADE
|
|
247
|
-
- Indexes on FK columns: CREATE INDEX idx_user_id ON posts(user_id)
|
|
248
|
-
- Join tables for many-to-many
|
|
249
|
-
|
|
250
|
-
ā Performance:
|
|
251
|
-
- Add indexes on frequently queried columns
|
|
252
|
-
- Use partial indexes for conditional queries
|
|
253
|
-
- Create composite indexes for multi-column queries
|
|
254
|
-
- Analyze query plans: EXPLAIN ANALYZE
|
|
255
|
-
|
|
256
|
-
ā Migrations Pattern:
|
|
257
|
-
- Up: Create/alter tables and indexes
|
|
258
|
-
- Down: Rollback changes safely
|
|
259
|
-
- Version control all migrations
|
|
260
|
-
- Never edit existing migrations
|
|
261
|
-
|
|
262
|
-
ā Prisma Schema Example:
|
|
263
|
-
model User {
|
|
264
|
-
id String @id @default(uuid())
|
|
265
|
-
email String @unique
|
|
266
|
-
posts Post[]
|
|
267
|
-
createdAt DateTime @default(now())
|
|
268
|
-
updatedAt DateTime @updatedAt
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
ā Connection:
|
|
272
|
-
- Use connection pooling (pg.Pool)
|
|
273
|
-
- Set max connections: 20-50
|
|
274
|
-
- Use prepared statements
|
|
275
|
-
- Handle connection errors gracefully
|
|
276
|
-
|
|
277
|
-
ā Seed Data:
|
|
278
|
-
- Create seed files for development
|
|
279
|
-
- Use transactions for bulk inserts
|
|
280
|
-
- Make seeds idempotent
|
|
281
|
-
|
|
282
|
-
Execute tools to create schema files and migrations immediately.`,
|
|
283
|
-
|
|
284
|
-
devops: `
|
|
285
|
-
šÆ DEVOPS MODE ACTIVATED
|
|
286
|
-
Setup CI/CD, containerization, deployment automation.
|
|
287
|
-
|
|
288
|
-
DevOps Tasks:
|
|
289
|
-
- Write Dockerfile with multi-stage builds
|
|
290
|
-
- Create docker-compose.yml for local dev
|
|
291
|
-
- Setup GitHub Actions or GitLab CI
|
|
292
|
-
- Add environment-specific configs
|
|
293
|
-
- Implement health checks and monitoring
|
|
294
|
-
- Use secrets management
|
|
295
|
-
- Optimize build times
|
|
296
|
-
|
|
297
|
-
Execute tools to create config files immediately.`
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
// Auto-detect task profile from user input
|
|
301
|
-
function detectTaskProfile(input) {
|
|
302
|
-
const text = input.toLowerCase();
|
|
303
|
-
|
|
304
|
-
// Documentation keywords
|
|
305
|
-
if (text.match(/\b(document|documentation|readme|brd|prd|user stor(y|ies)|use case|spec|wiki|guide)\b/))
|
|
306
|
-
return 'documentation';
|
|
307
|
-
|
|
308
|
-
// Backend keywords
|
|
309
|
-
if (text.match(/\b(api|endpoint|backend|server|route|controller|middleware|rest|graphql|service)\b/))
|
|
310
|
-
return 'backend';
|
|
311
|
-
|
|
312
|
-
// Frontend keywords
|
|
313
|
-
if (text.match(/\b(ui|frontend|component|page|design|style|css|tailwind|react|vue|svelte|button|form|navbar)\b/))
|
|
314
|
-
return 'frontend';
|
|
315
|
-
|
|
316
|
-
// Testing keywords
|
|
317
|
-
if (text.match(/\b(test|testing|spec|jest|vitest|cypress|unit test|integration|e2e|coverage)\b/))
|
|
318
|
-
return 'testing';
|
|
319
|
-
|
|
320
|
-
// Database keywords
|
|
321
|
-
if (text.match(/\b(database|schema|migration|model|orm|sql|postgres|mysql|mongo|prisma|sequelize)\b/))
|
|
322
|
-
return 'database';
|
|
323
|
-
|
|
324
|
-
// DevOps keywords
|
|
325
|
-
if (text.match(/\b(docker|container|ci\/cd|deploy|pipeline|kubernetes|k8s|helm|terraform|ansible)\b/))
|
|
326
|
-
return 'devops';
|
|
327
|
-
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// --- Tool Logic ---
|
|
332
49
|
const tools = {
|
|
333
50
|
read: (path) => {
|
|
334
|
-
try {
|
|
335
|
-
|
|
336
|
-
} catch (error) {
|
|
337
|
-
return `Error reading file: ${error.message}`;
|
|
338
|
-
}
|
|
51
|
+
try { return fs.readFileSync(path.trim(), 'utf8'); }
|
|
52
|
+
catch (error) { return `Error reading file: ${error.message}`; }
|
|
339
53
|
},
|
|
340
54
|
write: (path, content) => {
|
|
341
55
|
try {
|
|
342
56
|
fs.writeFileSync(path.trim(), content);
|
|
343
57
|
return `Successfully saved changes to ${path}`;
|
|
344
|
-
} catch (error) {
|
|
345
|
-
return `Error writing file: ${error.message}`;
|
|
346
|
-
}
|
|
58
|
+
} catch (error) { return `Error writing file: ${error.message}`; }
|
|
347
59
|
},
|
|
348
60
|
mkdir: (path) => {
|
|
349
61
|
try {
|
|
350
62
|
fs.mkdirSync(path.trim(), { recursive: true });
|
|
351
63
|
return `Directory created: ${path}`;
|
|
352
|
-
} catch (error) {
|
|
353
|
-
return `Error creating directory: ${error.message}`;
|
|
354
|
-
}
|
|
64
|
+
} catch (error) { return `Error creating directory: ${error.message}`; }
|
|
355
65
|
},
|
|
356
66
|
shell: async (cmd) => {
|
|
357
67
|
console.log(chalk.red.bold(`\n[SECURITY] Sapper wants to execute: `) + chalk.white(cmd));
|
|
358
68
|
const confirm = await safeQuestion(chalk.yellow('Allow? (y/n): '));
|
|
359
69
|
if (confirm.toLowerCase() === 'y') {
|
|
360
70
|
return new Promise((resolve) => {
|
|
361
|
-
|
|
362
|
-
const useShell = cmd.includes('&&') || cmd.includes('||') || cmd.includes('|') || cmd.includes('cd ') || cmd.includes('>');
|
|
363
|
-
|
|
71
|
+
const useShell = cmd.includes('&&') || cmd.includes('|') || cmd.includes('cd ');
|
|
364
72
|
console.log(chalk.cyan(`\n[RUNNING] ${cmd}\n`));
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (useShell) {
|
|
368
|
-
// For complex commands, use shell
|
|
369
|
-
proc = spawn('sh', ['-c', cmd], {
|
|
370
|
-
stdio: 'inherit',
|
|
371
|
-
shell: true
|
|
372
|
-
});
|
|
373
|
-
} else {
|
|
374
|
-
// For simple commands, parse and use direct execution
|
|
375
|
-
const parts = cmd.trim().split(/\s+/);
|
|
376
|
-
const executable = parts[0];
|
|
377
|
-
const args = parts.slice(1);
|
|
378
|
-
proc = spawn(executable, args, {
|
|
379
|
-
stdio: 'inherit',
|
|
380
|
-
shell: false
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
proc.on('close', (code) => {
|
|
385
|
-
// Recreate readline after shell command completes
|
|
386
|
-
recreateReadline();
|
|
387
|
-
console.log(chalk.green(`\n[ā] Command completed with exit code ${code}\n`));
|
|
388
|
-
resolve(`Command completed with exit code ${code}.`);
|
|
73
|
+
const proc = spawn(useShell ? 'sh' : cmd.split(' ')[0], useShell ? ['-c', cmd] : cmd.split(' ').slice(1), {
|
|
74
|
+
stdio: 'inherit', shell: useShell
|
|
389
75
|
});
|
|
390
|
-
|
|
391
|
-
proc.on('error', (err) => {
|
|
76
|
+
proc.on('close', (code) => {
|
|
392
77
|
recreateReadline();
|
|
393
|
-
|
|
394
|
-
resolve(`Execution Error: ${err.message}`);
|
|
78
|
+
resolve(`Command completed with code ${code}`);
|
|
395
79
|
});
|
|
396
80
|
});
|
|
397
81
|
}
|
|
398
82
|
return "Command blocked by user.";
|
|
399
83
|
},
|
|
400
84
|
list: (path) => {
|
|
401
|
-
try {
|
|
402
|
-
|
|
403
|
-
} catch (error) {
|
|
404
|
-
return `Error listing directory: ${error.message}`;
|
|
405
|
-
}
|
|
406
|
-
},
|
|
407
|
-
search: (pattern) => {
|
|
408
|
-
try {
|
|
409
|
-
const { execSync } = require('child_process');
|
|
410
|
-
const cmd = `grep -rnEi "${pattern.trim()}" . --exclude-dir=node_modules --exclude-dir=.git`;
|
|
411
|
-
return execSync(cmd, { encoding: 'utf8' }) || "No matches found.";
|
|
412
|
-
} catch (e) {
|
|
413
|
-
return "No matches found.";
|
|
414
|
-
}
|
|
85
|
+
try { return fs.readdirSync(path.trim() || '.').join('\n'); }
|
|
86
|
+
catch (e) { return `Error: ${e.message}`; }
|
|
415
87
|
}
|
|
416
88
|
};
|
|
417
89
|
|
|
418
|
-
async function selectModel() {
|
|
419
|
-
try {
|
|
420
|
-
const localModels = await ollama.list();
|
|
421
|
-
if (localModels.models.length === 0) {
|
|
422
|
-
console.log(chalk.red('ā No Ollama models found!'));
|
|
423
|
-
console.log(chalk.yellow('Please install at least one model:'));
|
|
424
|
-
console.log(chalk.gray(' ollama pull llama2'));
|
|
425
|
-
console.log(chalk.gray(' ollama pull codellama'));
|
|
426
|
-
process.exit(1);
|
|
427
|
-
}
|
|
428
|
-
console.log(chalk.magenta.bold("\nAvailable Models:"));
|
|
429
|
-
localModels.models.forEach((m, i) => console.log(`${i + 1}. ${chalk.white(m.name)}`));
|
|
430
|
-
const choice = await safeQuestion(chalk.yellow('\nChoose model: '));
|
|
431
|
-
const index = parseInt(choice) - 1;
|
|
432
|
-
return localModels.models[index]?.name || localModels.models[0].name;
|
|
433
|
-
} catch (error) {
|
|
434
|
-
console.log(chalk.red('ā Failed to connect to Ollama!'));
|
|
435
|
-
console.log(chalk.yellow('Please make sure Ollama is running:'));
|
|
436
|
-
console.log(chalk.gray(' 1. Install Ollama: https://ollama.ai'));
|
|
437
|
-
console.log(chalk.gray(' 2. Start Ollama: ollama serve'));
|
|
438
|
-
console.log(chalk.gray(' 3. Install a model: ollama pull llama2'));
|
|
439
|
-
console.log(chalk.red(`\nError details: ${error.message}`));
|
|
440
|
-
process.exit(1);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
90
|
async function runSapper() {
|
|
445
91
|
console.clear();
|
|
446
|
-
console.log(chalk.cyan.bold(` SAPPER v${CURRENT_VERSION} |
|
|
447
|
-
console.log(chalk.gray("Commands: /reset, /session-info, /step, /version, /update, /help, exit\n"));
|
|
448
|
-
|
|
449
|
-
// Check for updates on startup
|
|
450
|
-
await checkForUpdates();
|
|
451
|
-
|
|
452
|
-
// Early Ollama connectivity check
|
|
453
|
-
console.log(chalk.gray('š Checking Ollama connection...'));
|
|
92
|
+
console.log(chalk.cyan.bold(` SAPPER v${CURRENT_VERSION} | Autonomous "OpenCode" Mode`));
|
|
454
93
|
|
|
455
94
|
let messages = [];
|
|
456
95
|
if (fs.existsSync(CONTEXT_FILE)) {
|
|
457
96
|
const resume = await safeQuestion(chalk.green('Resume previous session? (y/n): '));
|
|
458
|
-
if (resume.toLowerCase() === 'y')
|
|
459
|
-
messages = JSON.parse(fs.readFileSync(CONTEXT_FILE, 'utf8'));
|
|
460
|
-
}
|
|
97
|
+
if (resume.toLowerCase() === 'y') messages = JSON.parse(fs.readFileSync(CONTEXT_FILE, 'utf8'));
|
|
461
98
|
}
|
|
462
99
|
|
|
463
|
-
const
|
|
100
|
+
const localModels = await ollama.list();
|
|
101
|
+
localModels.models.forEach((m, i) => console.log(`${i + 1}. ${m.name}`));
|
|
102
|
+
const choice = await safeQuestion(chalk.yellow('\nChoose model: '));
|
|
103
|
+
const selectedModel = localModels.models[parseInt(choice) - 1]?.name || localModels.models[0].name;
|
|
464
104
|
|
|
465
105
|
if (messages.length === 0) {
|
|
466
106
|
messages = [{
|
|
467
107
|
role: 'system',
|
|
468
|
-
content: `You are Sapper, a senior
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
2. Intelligently detect and READ key files based on what you actually find:
|
|
475
|
-
|
|
476
|
-
**Documentation files (read first):**
|
|
477
|
-
- README.md, README.txt, README.rst, README, docs/README.md
|
|
478
|
-
|
|
479
|
-
**Project config (detect language/framework):**
|
|
480
|
-
- package.json (Node.js/JavaScript)
|
|
481
|
-
- requirements.txt, setup.py, pyproject.toml (Python)
|
|
482
|
-
- pom.xml, build.gradle, build.gradle.kts (Java)
|
|
483
|
-
- Cargo.toml (Rust)
|
|
484
|
-
- go.mod (Go)
|
|
485
|
-
- composer.json (PHP)
|
|
486
|
-
|
|
487
|
-
**Entry points (find the main file):**
|
|
488
|
-
- Look for: index.*, main.*, app.*, server.*, start.*
|
|
489
|
-
- Check common locations: ./src/, ./app/, root directory
|
|
490
|
-
|
|
491
|
-
**Then decide strategically:**
|
|
492
|
-
- If < 10 total files: Read ALL files
|
|
493
|
-
- If 10-30 files: Read config + README + 3-5 important files you detect
|
|
494
|
-
- If > 30 files: Read config + README + main entry + 1-2 others, then ask user
|
|
495
|
-
|
|
496
|
-
3. Provide detailed analysis based on files you actually READ
|
|
497
|
-
4. For large projects, list available modules and ask user what to focus on
|
|
498
|
-
5. [SUMMARY:Read and analyzed X files, found Y structure]
|
|
499
|
-
|
|
500
|
-
ā WRONG (reading too many files):
|
|
501
|
-
[TOOL:LIST]./[/TOOL]
|
|
502
|
-
[TOOL:READ]./file1.js[/TOOL]
|
|
503
|
-
[TOOL:READ]./file2.js[/TOOL]
|
|
504
|
-
... (reads 100+ files) ā TOO MUCH!
|
|
505
|
-
|
|
506
|
-
ā
CORRECT (intelligent detection and reading):
|
|
507
|
-
[TOOL:LIST]./[/TOOL]
|
|
508
|
-
|
|
509
|
-
Found 50+ files. Let me identify key files to understand the project:
|
|
510
|
-
[TOOL:READ]./README.md[/TOOL]
|
|
511
|
-
[TOOL:READ]./package.json[/TOOL]
|
|
512
|
-
|
|
513
|
-
Detected: Node.js project with Express framework. Looking for entry point...
|
|
514
|
-
[TOOL:LIST]./src[/TOOL]
|
|
515
|
-
[TOOL:READ]./src/index.js[/TOOL]
|
|
516
|
-
[TOOL:READ]./src/app.js[/TOOL]
|
|
517
|
-
|
|
518
|
-
Analysis based on what I found and read:
|
|
519
|
-
- Project Type: Node.js web application
|
|
520
|
-
- Framework: Express.js v4.18.0
|
|
521
|
-
- Entry Point: src/index.js (starts server on port 3000)
|
|
522
|
-
- Architecture: Organized in src/ with subdirectories
|
|
523
|
-
- Dependencies: express, cors, helmet, dotenv, pg
|
|
524
|
-
|
|
525
|
-
Project structure detected:
|
|
526
|
-
- /src - Source code
|
|
527
|
-
- /src/routes - API endpoints
|
|
528
|
-
- /src/models - Data models
|
|
529
|
-
- /src/controllers - Business logic
|
|
530
|
-
- /src/middleware - Request processors
|
|
531
|
-
- /tests - Test files
|
|
532
|
-
|
|
533
|
-
Which area would you like me to analyze in detail?
|
|
534
|
-
|
|
535
|
-
[SUMMARY:Detected Node.js/Express project, read 5 key files, identified MVC architecture with PostgreSQL database]
|
|
536
|
-
|
|
537
|
-
**TOOL FORMAT:**
|
|
538
|
-
[TOOL:TYPE]path]content[/TOOL]
|
|
539
|
-
|
|
540
|
-
**Available Tools:**
|
|
541
|
-
- SHELL: Execute commands
|
|
542
|
-
- READ: Read file contents (use strategically!)
|
|
543
|
-
- WRITE: Create/update files
|
|
544
|
-
- MKDIR: Create directories
|
|
545
|
-
- LIST: List directory contents
|
|
546
|
-
- SEARCH: Search for patterns
|
|
547
|
-
|
|
548
|
-
**Format Examples:**
|
|
549
|
-
[TOOL:SHELL]npm install[/TOOL]
|
|
550
|
-
[TOOL:READ]./package.json[/TOOL]
|
|
551
|
-
[TOOL:WRITE]./app.js]console.log('hello')[/TOOL]
|
|
552
|
-
[TOOL:LIST]./[/TOOL]
|
|
553
|
-
[TOOL:SEARCH]function auth[/TOOL]
|
|
554
|
-
|
|
555
|
-
**Multi-line files:**
|
|
556
|
-
[TOOL:WRITE]./README.md]
|
|
557
|
-
# Title
|
|
558
|
-
- [ ] checkbox
|
|
559
|
-
Arrays: [1, 2, 3]
|
|
560
|
-
[/TOOL]
|
|
561
|
-
|
|
562
|
-
**Multiple Tools - But Be Smart:**
|
|
563
|
-
Execute multiple tools in one response, but DON'T overdo it:
|
|
564
|
-
- ā
3-10 tool calls in one response: Good
|
|
565
|
-
- ā ļø 20-30 tool calls: Too many, be selective
|
|
566
|
-
- ā 50+ tool calls: Way too much, you'll get cut off
|
|
567
|
-
|
|
568
|
-
**Strategic File Reading:**
|
|
569
|
-
Priority detection order:
|
|
570
|
-
1. Documentation: README.* in any format
|
|
571
|
-
2. Config files: package.json, requirements.txt, pom.xml, Cargo.toml, go.mod, etc.
|
|
572
|
-
3. Entry points: Search for index.*, main.*, app.*, server.* in root or /src
|
|
573
|
-
4. Important directories: /src, /lib, /app for more context
|
|
574
|
-
5. Then ask user what specific area to analyze
|
|
575
|
-
|
|
576
|
-
**Smart Detection Examples:**
|
|
577
|
-
- See package.json ā Node.js project ā look for index.js, server.js, app.js
|
|
578
|
-
- See requirements.txt ā Python project ā look for main.py, app.py, __init__.py
|
|
579
|
-
- See pom.xml ā Java project ā look for src/main/java/**/Main.java, Application.java
|
|
580
|
-
- See Cargo.toml ā Rust project ā look for src/main.rs, src/lib.rs
|
|
581
|
-
- See go.mod ā Go project ā look for main.go, cmd/*/main.go
|
|
582
|
-
|
|
583
|
-
**Shell Commands:**
|
|
584
|
-
[TOOL:SHELL]cd /path && npm install && npm start[/TOOL]
|
|
585
|
-
|
|
586
|
-
**Critical Rules:**
|
|
587
|
-
1. Be selective with large codebases (30+ files)
|
|
588
|
-
2. Read strategically, not exhaustively
|
|
589
|
-
3. Ask user for focus area if project is large
|
|
590
|
-
4. Provide analysis after reading key files
|
|
591
|
-
5. End with [SUMMARY:what you completed]
|
|
592
|
-
6. NEVER try to read 50+ files at once
|
|
593
|
-
|
|
594
|
-
Working directory: ${process.cwd()}`
|
|
108
|
+
content: `You are Sapper, a senior engineer.
|
|
109
|
+
STRATEGY:
|
|
110
|
+
1. When asked to analyze, use [TOOL:LIST]./[/TOOL] first.
|
|
111
|
+
2. Immediately [TOOL:READ] key files (package.json, README.md, entry points) in the SAME turn.
|
|
112
|
+
3. Use the format: [TOOL:TYPE]path]content[/TOOL]. For LIST/READ, content is empty.
|
|
113
|
+
4. DO NOT ask for permission to read or list. Just do it.`
|
|
595
114
|
}];
|
|
596
115
|
}
|
|
597
116
|
|
|
598
|
-
// Display working directory awareness
|
|
599
|
-
console.log(chalk.yellow(`Working Directory: ${process.cwd()}\n`));
|
|
600
|
-
|
|
601
117
|
const ask = () => {
|
|
602
118
|
safeQuestion(chalk.blue.bold('\nIbrahim ā ')).then(async (input) => {
|
|
603
119
|
if (input.toLowerCase() === 'exit') process.exit();
|
|
604
|
-
|
|
605
|
-
if (fs.existsSync(CONTEXT_FILE)) {
|
|
606
|
-
const fileSize = fs.statSync(CONTEXT_FILE).size;
|
|
607
|
-
console.log(chalk.yellow(`\nšļø Clearing session (${(fileSize / 1024).toFixed(2)}KB)...`));
|
|
608
|
-
fs.unlinkSync(CONTEXT_FILE);
|
|
609
|
-
console.log(chalk.green('ā
Session cleared! Starting fresh...\n'));
|
|
610
|
-
} else {
|
|
611
|
-
console.log(chalk.yellow('\nā¹ļø No session to clear.\n'));
|
|
612
|
-
}
|
|
613
|
-
return runSapper();
|
|
614
|
-
}
|
|
615
|
-
if (input.toLowerCase() === '/session-info') {
|
|
616
|
-
if (fs.existsSync(CONTEXT_FILE)) {
|
|
617
|
-
const data = JSON.parse(fs.readFileSync(CONTEXT_FILE, 'utf8'));
|
|
618
|
-
const fileSize = fs.statSync(CONTEXT_FILE).size;
|
|
619
|
-
console.log(chalk.cyan(`\nš Session Info:`));
|
|
620
|
-
console.log(chalk.gray(` Messages: ${data.length}`));
|
|
621
|
-
console.log(chalk.gray(` File Size: ${(fileSize / 1024).toFixed(2)}KB`));
|
|
622
|
-
console.log(chalk.gray(` Last Message: ${data[data.length - 1]?.role || 'N/A'}`));
|
|
623
|
-
} else {
|
|
624
|
-
console.log(chalk.yellow('\nā¹ļø No active session.\n'));
|
|
625
|
-
}
|
|
626
|
-
return ask();
|
|
627
|
-
}
|
|
628
|
-
if (input.toLowerCase() === '/version') {
|
|
629
|
-
console.log(chalk.cyan(`\nš¦ Sapper Version: v${CURRENT_VERSION}`));
|
|
630
|
-
console.log(chalk.gray(` Node.js: ${process.version}`));
|
|
631
|
-
console.log(chalk.gray(` Platform: ${process.platform}\n`));
|
|
632
|
-
// Check for updates
|
|
633
|
-
await checkForUpdates();
|
|
634
|
-
return ask();
|
|
635
|
-
}
|
|
636
|
-
if (input.toLowerCase() === '/update') {
|
|
637
|
-
await updateSapper();
|
|
638
|
-
return ask();
|
|
639
|
-
}
|
|
640
|
-
if (input.toLowerCase() === '/step') {
|
|
641
|
-
stepMode = !stepMode;
|
|
642
|
-
console.log(chalk.yellow(`Step Mode is ${stepMode ? 'ON' : 'OFF'}`));
|
|
643
|
-
return ask();
|
|
644
|
-
}
|
|
645
|
-
if (input.toLowerCase() === '/help') {
|
|
646
|
-
console.log(chalk.cyan(`\nš Sapper Commands:`));
|
|
647
|
-
console.log(chalk.gray(` /reset or /clear-session - Start a new session`));
|
|
648
|
-
console.log(chalk.gray(` /session-info - Show current session details`));
|
|
649
|
-
console.log(chalk.gray(` /version - Show version and check for updates`));
|
|
650
|
-
console.log(chalk.gray(` /update - Update Sapper to latest version`));
|
|
651
|
-
console.log(chalk.gray(` /step - Toggle step-by-step mode`));
|
|
652
|
-
console.log(chalk.gray(` /help - Show this help menu`));
|
|
653
|
-
console.log(chalk.gray(` exit - Exit Sapper\n`));
|
|
654
|
-
return ask();
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Auto-detect task profile and inject specialized instructions
|
|
658
|
-
const profile = detectTaskProfile(input);
|
|
659
|
-
let contextMsg = input;
|
|
660
|
-
|
|
661
|
-
// Inject profile if detected
|
|
662
|
-
if (profile) {
|
|
663
|
-
console.log(chalk.magenta(`\nšÆ ${profile.toUpperCase()} MODE DETECTED\n`));
|
|
664
|
-
contextMsg = `${input}\n\n${PROMPT_PROFILES[profile]}`;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// Check if user mentioned a directory and provide context
|
|
668
|
-
const dirMatch = input.match(/\/Users\/[^\s]+|\/[a-zA-Z0-9_\/-]+/g);
|
|
669
|
-
|
|
670
|
-
if (dirMatch && dirMatch[0]) {
|
|
671
|
-
const mentionedDir = dirMatch[0];
|
|
672
|
-
try {
|
|
673
|
-
if (fs.existsSync(mentionedDir) && fs.statSync(mentionedDir).isDirectory()) {
|
|
674
|
-
const files = fs.readdirSync(mentionedDir).slice(0, 10).join(', ');
|
|
675
|
-
contextMsg += `\n\n[CONTEXT: Directory "${mentionedDir}" contains: ${files}${fs.readdirSync(mentionedDir).length > 10 ? '...' : ''}]`;
|
|
676
|
-
}
|
|
677
|
-
} catch (e) {
|
|
678
|
-
// Silently ignore if directory doesn't exist
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
messages.push({ role: 'user', content: contextMsg });
|
|
120
|
+
messages.push({ role: 'user', content: input });
|
|
683
121
|
|
|
684
122
|
let active = true;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
iterations++;
|
|
688
|
-
|
|
689
|
-
if (stepMode) {
|
|
690
|
-
const proceed = await safeQuestion(chalk.gray('\n[STEP-MODE] Press Enter to continue (or type "/stop"): '));
|
|
691
|
-
if (proceed.toLowerCase() === '/stop') break;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
spinner.stop();
|
|
695
|
-
console.log(chalk.blue(`\n${selectedModel} is thinking...`));
|
|
696
|
-
|
|
697
|
-
let response;
|
|
698
|
-
try {
|
|
699
|
-
response = await ollama.chat({
|
|
700
|
-
model: selectedModel,
|
|
701
|
-
messages,
|
|
702
|
-
stream: true,
|
|
703
|
-
options: { num_ctx: 16384 }
|
|
704
|
-
});
|
|
705
|
-
} catch (error) {
|
|
706
|
-
console.log(chalk.red('\nā Failed to communicate with Ollama!'));
|
|
707
|
-
console.log(chalk.yellow('Possible issues:'));
|
|
708
|
-
console.log(chalk.gray(' - Ollama service stopped'));
|
|
709
|
-
console.log(chalk.gray(' - Model was removed'));
|
|
710
|
-
console.log(chalk.gray(' - Network connection issue'));
|
|
711
|
-
console.log(chalk.red(`Error: ${error.message}`));
|
|
712
|
-
console.log(chalk.cyan('\nš” Try restarting Sapper or check Ollama status'));
|
|
713
|
-
active = false;
|
|
714
|
-
ask();
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
123
|
+
while (active) {
|
|
124
|
+
if (stepMode) await safeQuestion(chalk.gray('[STEP] Press Enter to let AI think...'));
|
|
717
125
|
|
|
126
|
+
spinner.start('Thinking...');
|
|
127
|
+
const response = await ollama.chat({ model: selectedModel, messages, stream: true });
|
|
128
|
+
spinner.stop();
|
|
129
|
+
|
|
718
130
|
let msg = '';
|
|
719
131
|
process.stdout.write(chalk.white('Sapper: '));
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
if (chunk.message && chunk.message.content) {
|
|
724
|
-
process.stdout.write(chunk.message.content);
|
|
725
|
-
msg += chunk.message.content;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
} catch (error) {
|
|
729
|
-
console.log(chalk.red('\n\nā Connection interrupted while streaming response!'));
|
|
730
|
-
console.log(chalk.yellow(`Error: ${error.message}`));
|
|
731
|
-
console.log(chalk.cyan('š” The conversation will continue, but you may want to restart Sapper'));
|
|
732
|
-
msg += `\n[ERROR: Response interrupted - ${error.message}]`;
|
|
132
|
+
for await (const chunk of response) {
|
|
133
|
+
process.stdout.write(chunk.message.content);
|
|
134
|
+
msg += chunk.message.content;
|
|
733
135
|
}
|
|
734
136
|
console.log();
|
|
735
|
-
|
|
736
137
|
messages.push({ role: 'assistant', content: msg });
|
|
737
138
|
|
|
738
|
-
const
|
|
139
|
+
const toolMatches = [...msg.matchAll(/\[TOOL:(\w+)\]([^\]\n]+)(?:\]([\s\S]*?))?\[\/TOOL\]/g)];
|
|
739
140
|
|
|
740
|
-
// Primary format: [TOOL:TYPE:path]content[/TOOL]
|
|
741
|
-
const toolMatches = [...msg.matchAll(/\[TOOL:(\w+)\]([^\[]+?)\]([\s\S]*?)\[\/TOOL\]/g)];
|
|
742
|
-
|
|
743
|
-
if (summaryMatch) {
|
|
744
|
-
console.log(chalk.green.bold("\nā
MISSION COMPLETE:"));
|
|
745
|
-
console.log(chalk.white(summaryMatch[1].trim()));
|
|
746
|
-
active = false;
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
141
|
if (toolMatches.length > 0) {
|
|
751
142
|
for (const match of toolMatches) {
|
|
752
|
-
const [_,
|
|
753
|
-
|
|
754
|
-
console.log(chalk.cyan(`\n[ACTION] Executing ${toolName} on: ${path}`));
|
|
143
|
+
const [_, type, path, content] = match;
|
|
144
|
+
console.log(chalk.cyan(`\n[ACTION] ${type} -> ${path}`));
|
|
755
145
|
|
|
756
146
|
let result;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
else result = `Unknown tool: ${name}`;
|
|
765
|
-
} catch (e) {
|
|
766
|
-
result = `Error: ${e.message}`;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
console.log(chalk.gray(`> Result: ${result.substring(0, 60)}...`));
|
|
770
|
-
messages.push({ role: 'user', content: `TOOL_RESULT for ${path}: ${result}` });
|
|
147
|
+
if (type.toLowerCase() === 'list') result = tools.list(path);
|
|
148
|
+
else if (type.toLowerCase() === 'read') result = tools.read(path);
|
|
149
|
+
else if (type.toLowerCase() === 'mkdir') result = tools.mkdir(path);
|
|
150
|
+
else if (type.toLowerCase() === 'write') result = tools.write(path, content);
|
|
151
|
+
else if (type.toLowerCase() === 'shell') result = await tools.shell(path);
|
|
152
|
+
|
|
153
|
+
messages.push({ role: 'user', content: `RESULT (${path}): ${result}` });
|
|
771
154
|
}
|
|
772
155
|
fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
|
|
773
|
-
|
|
774
|
-
// Add interrupt check after tool execution
|
|
775
|
-
console.log(chalk.gray('\n[Press Enter to continue or type "/stop" to halt execution]'));
|
|
776
|
-
const userChoice = await safeQuestion('');
|
|
777
|
-
if (userChoice.toLowerCase() === '/stop') {
|
|
778
|
-
console.log(chalk.yellow('\nā¹ļø Execution halted by user'));
|
|
779
|
-
active = false;
|
|
780
|
-
break;
|
|
781
|
-
}
|
|
782
156
|
} else {
|
|
783
|
-
|
|
784
|
-
if (planMatch) {
|
|
785
|
-
const feedback = await safeQuestion(chalk.yellow('\nModify plan or type "go": '));
|
|
786
|
-
if (feedback.toLowerCase() === '/stop') { active = false; break; }
|
|
787
|
-
messages.push({ role: 'user', content: feedback.toLowerCase() === 'go' ? "Plan approved. Proceed with all steps." : feedback });
|
|
788
|
-
} else {
|
|
789
|
-
active = false;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Safety check: if model is repeating itself, break the loop
|
|
794
|
-
if (iterations > 5) {
|
|
795
|
-
const recentMessages = messages.slice(-4);
|
|
796
|
-
const isRepeating = recentMessages.every(m =>
|
|
797
|
-
m.role === 'assistant' &&
|
|
798
|
-
recentMessages[0].content &&
|
|
799
|
-
m.content === recentMessages[0].content
|
|
800
|
-
);
|
|
801
|
-
if (isRepeating) {
|
|
802
|
-
console.log(chalk.yellow('\nā ļø Detected repetitive behavior, stopping execution'));
|
|
803
|
-
active = false;
|
|
804
|
-
}
|
|
157
|
+
active = false;
|
|
805
158
|
}
|
|
806
159
|
}
|
|
807
160
|
ask();
|
|
@@ -810,4 +163,4 @@ Working directory: ${process.cwd()}`
|
|
|
810
163
|
ask();
|
|
811
164
|
}
|
|
812
165
|
|
|
813
|
-
runSapper();
|
|
166
|
+
runSapper();
|