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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/sapper.mjs +53 -700
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "AI-powered development assistant that executes commands and builds projects",
5
5
  "main": "sapper.mjs",
6
6
  "bin": {
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
- const packageJson = JSON.parse(fs.readFileSync(join(__dirname, 'package.json'), 'utf8'));
14
- const CURRENT_VERSION = packageJson.version;
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
- return fs.readFileSync(path.trim(), 'utf8');
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
- // Use shell for complex commands with pipes, redirects, cd, &&, ||, etc
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
- let proc;
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
- console.log(chalk.red(`\n[āœ—] Command error: ${err.message}\n`));
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
- return fs.readdirSync(path || '.').join('\n');
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} | Multi-Tool Execution Mode`));
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 selectedModel = await selectModel();
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 software engineer AI assistant. Execute tools to COMPLETE tasks fully.
469
-
470
- **INTELLIGENT TASK COMPLETION:**
471
-
472
- When user says "analyze files" or "read files":
473
- 1. [TOOL:LIST]./[/TOOL] - see what exists
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
- if (input.toLowerCase() === '/reset' || input.toLowerCase() === '/clear-session') {
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
- let iterations = 0;
686
- while (active && iterations < 30) {
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
- try {
722
- for await (const chunk of response) {
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 summaryMatch = msg.match(/\[SUMMARY:(.*?)\]/s);
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 [_, name, path, content] = match;
753
- const toolName = name.toLowerCase();
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
- try {
758
- if (toolName === 'shell') result = await tools.shell(path);
759
- else if (toolName === 'write') result = tools.write(path, content);
760
- else if (toolName === 'mkdir') result = tools.mkdir(path);
761
- else if (toolName === 'read') result = tools.read(path);
762
- else if (toolName === 'list') result = tools.list(path);
763
- else if (toolName === 'search') result = tools.search(path);
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
- const planMatch = msg.match(/\[PLAN:([\s\S]*?)\]/) || msg.match(/\[PLAN\]([\s\S]*?)\[\/PLAN\]/);
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();