sapper-iq 1.0.8 → 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 -644
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.0.8",
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,659 +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
- **COMPLETE TASK WORKFLOW:**
471
- When user says "analyze files" → DO ALL THESE STEPS IN ONE RESPONSE:
472
- 1. [TOOL:LIST]./[/TOOL] - see what files exist
473
- 2. [TOOL:READ]./file1.md[/TOOL] - read each relevant file
474
- 3. [TOOL:READ]./file2.md[/TOOL] - read more files as needed
475
- 4. Provide detailed analysis based on what you read
476
- 5. [SUMMARY:Analyzed X files, found Y patterns, created Z documentation]
477
-
478
- āŒ WRONG (incomplete):
479
- [TOOL:LIST]./[/TOOL]
480
- (stops here - no reading, no analysis)
481
-
482
- āœ… CORRECT (complete):
483
- [TOOL:LIST]./[/TOOL]
484
- [TOOL:READ]./README.md[/TOOL]
485
- [TOOL:READ]./docs.md[/TOOL]
486
-
487
- Based on the files:
488
- - README.md contains project setup instructions
489
- - docs.md has API documentation for 5 endpoints
490
- - The project uses Express.js with PostgreSQL
491
-
492
- [SUMMARY:Analyzed 2 documentation files covering setup and API endpoints]
493
-
494
- **TOOL FORMAT:**
495
- [TOOL:TYPE]path]content[/TOOL]
496
-
497
- **Available Tools:**
498
- - SHELL: Execute commands
499
- - READ: Read file contents (use this OFTEN!)
500
- - WRITE: Create/update files
501
- - MKDIR: Create directories
502
- - LIST: List directory contents
503
- - SEARCH: Search for patterns
504
-
505
- **Format Examples:**
506
- [TOOL:SHELL]npm install[/TOOL]
507
- [TOOL:READ]./package.json[/TOOL]
508
- [TOOL:WRITE]./app.js]console.log('hello')[/TOOL]
509
- [TOOL:LIST]./[/TOOL]
510
- [TOOL:SEARCH]function auth[/TOOL]
511
-
512
- **Multi-line files:**
513
- [TOOL:WRITE]./README.md]
514
- # Title
515
- - [ ] checkbox
516
- Arrays: [1, 2, 3]
517
- [/TOOL]
518
-
519
- **Multiple Tools Per Response:**
520
- You MUST execute ALL necessary tools in ONE response. Example:
521
- [TOOL:MKDIR]./src[/TOOL]
522
- [TOOL:WRITE]./src/server.js]const express = require('express')[/TOOL]
523
- [TOOL:WRITE]./package.json]{"name": "app"}[/TOOL]
524
-
525
- Created project structure with server and package.json.
526
- [SUMMARY:Created Node.js project with Express server]
527
-
528
- **Shell Commands:**
529
- [TOOL:SHELL]cd /path && npm install && npm start[/TOOL]
530
-
531
- **Critical Rules:**
532
- 1. Execute ALL needed tools in your response
533
- 2. Read files after listing them
534
- 3. Provide analysis/explanation after reading
535
- 4. End with [SUMMARY:what you completed]
536
- 5. NEVER just execute one tool and stop
537
-
538
- 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.`
539
114
  }];
540
115
  }
541
116
 
542
- // Display working directory awareness
543
- console.log(chalk.yellow(`Working Directory: ${process.cwd()}\n`));
544
-
545
117
  const ask = () => {
546
118
  safeQuestion(chalk.blue.bold('\nIbrahim āž” ')).then(async (input) => {
547
119
  if (input.toLowerCase() === 'exit') process.exit();
548
- if (input.toLowerCase() === '/reset' || input.toLowerCase() === '/clear-session') {
549
- if (fs.existsSync(CONTEXT_FILE)) {
550
- const fileSize = fs.statSync(CONTEXT_FILE).size;
551
- console.log(chalk.yellow(`\nšŸ—‘ļø Clearing session (${(fileSize / 1024).toFixed(2)}KB)...`));
552
- fs.unlinkSync(CONTEXT_FILE);
553
- console.log(chalk.green('āœ… Session cleared! Starting fresh...\n'));
554
- } else {
555
- console.log(chalk.yellow('\nā„¹ļø No session to clear.\n'));
556
- }
557
- return runSapper();
558
- }
559
- if (input.toLowerCase() === '/session-info') {
560
- if (fs.existsSync(CONTEXT_FILE)) {
561
- const data = JSON.parse(fs.readFileSync(CONTEXT_FILE, 'utf8'));
562
- const fileSize = fs.statSync(CONTEXT_FILE).size;
563
- console.log(chalk.cyan(`\nšŸ“Š Session Info:`));
564
- console.log(chalk.gray(` Messages: ${data.length}`));
565
- console.log(chalk.gray(` File Size: ${(fileSize / 1024).toFixed(2)}KB`));
566
- console.log(chalk.gray(` Last Message: ${data[data.length - 1]?.role || 'N/A'}`));
567
- } else {
568
- console.log(chalk.yellow('\nā„¹ļø No active session.\n'));
569
- }
570
- return ask();
571
- }
572
- if (input.toLowerCase() === '/version') {
573
- console.log(chalk.cyan(`\nšŸ“¦ Sapper Version: v${CURRENT_VERSION}`));
574
- console.log(chalk.gray(` Node.js: ${process.version}`));
575
- console.log(chalk.gray(` Platform: ${process.platform}\n`));
576
- // Check for updates
577
- await checkForUpdates();
578
- return ask();
579
- }
580
- if (input.toLowerCase() === '/update') {
581
- await updateSapper();
582
- return ask();
583
- }
584
- if (input.toLowerCase() === '/step') {
585
- stepMode = !stepMode;
586
- console.log(chalk.yellow(`Step Mode is ${stepMode ? 'ON' : 'OFF'}`));
587
- return ask();
588
- }
589
- if (input.toLowerCase() === '/help') {
590
- console.log(chalk.cyan(`\nšŸ“š Sapper Commands:`));
591
- console.log(chalk.gray(` /reset or /clear-session - Start a new session`));
592
- console.log(chalk.gray(` /session-info - Show current session details`));
593
- console.log(chalk.gray(` /version - Show version and check for updates`));
594
- console.log(chalk.gray(` /update - Update Sapper to latest version`));
595
- console.log(chalk.gray(` /step - Toggle step-by-step mode`));
596
- console.log(chalk.gray(` /help - Show this help menu`));
597
- console.log(chalk.gray(` exit - Exit Sapper\n`));
598
- return ask();
599
- }
600
-
601
- // Auto-detect task profile and inject specialized instructions
602
- const profile = detectTaskProfile(input);
603
- let contextMsg = input;
604
-
605
- // Inject profile if detected
606
- if (profile) {
607
- console.log(chalk.magenta(`\nšŸŽÆ ${profile.toUpperCase()} MODE DETECTED\n`));
608
- contextMsg = `${input}\n\n${PROMPT_PROFILES[profile]}`;
609
- }
610
-
611
- // Check if user mentioned a directory and provide context
612
- const dirMatch = input.match(/\/Users\/[^\s]+|\/[a-zA-Z0-9_\/-]+/g);
613
-
614
- if (dirMatch && dirMatch[0]) {
615
- const mentionedDir = dirMatch[0];
616
- try {
617
- if (fs.existsSync(mentionedDir) && fs.statSync(mentionedDir).isDirectory()) {
618
- const files = fs.readdirSync(mentionedDir).slice(0, 10).join(', ');
619
- contextMsg += `\n\n[CONTEXT: Directory "${mentionedDir}" contains: ${files}${fs.readdirSync(mentionedDir).length > 10 ? '...' : ''}]`;
620
- }
621
- } catch (e) {
622
- // Silently ignore if directory doesn't exist
623
- }
624
- }
625
-
626
- messages.push({ role: 'user', content: contextMsg });
120
+ messages.push({ role: 'user', content: input });
627
121
 
628
122
  let active = true;
629
- let iterations = 0;
630
- while (active && iterations < 30) {
631
- iterations++;
632
-
633
- if (stepMode) {
634
- const proceed = await safeQuestion(chalk.gray('\n[STEP-MODE] Press Enter to continue (or type "/stop"): '));
635
- if (proceed.toLowerCase() === '/stop') break;
636
- }
637
-
638
- spinner.stop();
639
- console.log(chalk.blue(`\n${selectedModel} is thinking...`));
640
-
641
- let response;
642
- try {
643
- response = await ollama.chat({
644
- model: selectedModel,
645
- messages,
646
- stream: true,
647
- options: { num_ctx: 16384 }
648
- });
649
- } catch (error) {
650
- console.log(chalk.red('\nāŒ Failed to communicate with Ollama!'));
651
- console.log(chalk.yellow('Possible issues:'));
652
- console.log(chalk.gray(' - Ollama service stopped'));
653
- console.log(chalk.gray(' - Model was removed'));
654
- console.log(chalk.gray(' - Network connection issue'));
655
- console.log(chalk.red(`Error: ${error.message}`));
656
- console.log(chalk.cyan('\nšŸ’” Try restarting Sapper or check Ollama status'));
657
- active = false;
658
- ask();
659
- return;
660
- }
123
+ while (active) {
124
+ if (stepMode) await safeQuestion(chalk.gray('[STEP] Press Enter to let AI think...'));
661
125
 
126
+ spinner.start('Thinking...');
127
+ const response = await ollama.chat({ model: selectedModel, messages, stream: true });
128
+ spinner.stop();
129
+
662
130
  let msg = '';
663
131
  process.stdout.write(chalk.white('Sapper: '));
664
-
665
- try {
666
- for await (const chunk of response) {
667
- if (chunk.message && chunk.message.content) {
668
- process.stdout.write(chunk.message.content);
669
- msg += chunk.message.content;
670
- }
671
- }
672
- } catch (error) {
673
- console.log(chalk.red('\n\nāŒ Connection interrupted while streaming response!'));
674
- console.log(chalk.yellow(`Error: ${error.message}`));
675
- console.log(chalk.cyan('šŸ’” The conversation will continue, but you may want to restart Sapper'));
676
- 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;
677
135
  }
678
136
  console.log();
679
-
680
137
  messages.push({ role: 'assistant', content: msg });
681
138
 
682
- const summaryMatch = msg.match(/\[SUMMARY:(.*?)\]/s);
139
+ const toolMatches = [...msg.matchAll(/\[TOOL:(\w+)\]([^\]\n]+)(?:\]([\s\S]*?))?\[\/TOOL\]/g)];
683
140
 
684
- // Primary format: [TOOL:TYPE:path]content[/TOOL]
685
- const toolMatches = [...msg.matchAll(/\[TOOL:(\w+)\]([^\[]+?)\]([\s\S]*?)\[\/TOOL\]/g)];
686
-
687
- if (summaryMatch) {
688
- console.log(chalk.green.bold("\nāœ… MISSION COMPLETE:"));
689
- console.log(chalk.white(summaryMatch[1].trim()));
690
- active = false;
691
- continue;
692
- }
693
-
694
141
  if (toolMatches.length > 0) {
695
142
  for (const match of toolMatches) {
696
- const [_, name, path, content] = match;
697
- const toolName = name.toLowerCase();
698
- 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}`));
699
145
 
700
146
  let result;
701
- try {
702
- if (toolName === 'shell') result = await tools.shell(path);
703
- else if (toolName === 'write') result = tools.write(path, content);
704
- else if (toolName === 'mkdir') result = tools.mkdir(path);
705
- else if (toolName === 'read') result = tools.read(path);
706
- else if (toolName === 'list') result = tools.list(path);
707
- else if (toolName === 'search') result = tools.search(path);
708
- else result = `Unknown tool: ${name}`;
709
- } catch (e) {
710
- result = `Error: ${e.message}`;
711
- }
712
-
713
- console.log(chalk.gray(`> Result: ${result.substring(0, 60)}...`));
714
- 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}` });
715
154
  }
716
155
  fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
717
-
718
- // Add interrupt check after tool execution
719
- console.log(chalk.gray('\n[Press Enter to continue or type "/stop" to halt execution]'));
720
- const userChoice = await safeQuestion('');
721
- if (userChoice.toLowerCase() === '/stop') {
722
- console.log(chalk.yellow('\nā¹ļø Execution halted by user'));
723
- active = false;
724
- break;
725
- }
726
156
  } else {
727
- const planMatch = msg.match(/\[PLAN:([\s\S]*?)\]/) || msg.match(/\[PLAN\]([\s\S]*?)\[\/PLAN\]/);
728
- if (planMatch) {
729
- const feedback = await safeQuestion(chalk.yellow('\nModify plan or type "go": '));
730
- if (feedback.toLowerCase() === '/stop') { active = false; break; }
731
- messages.push({ role: 'user', content: feedback.toLowerCase() === 'go' ? "Plan approved. Proceed with all steps." : feedback });
732
- } else {
733
- active = false;
734
- }
735
- }
736
-
737
- // Safety check: if model is repeating itself, break the loop
738
- if (iterations > 5) {
739
- const recentMessages = messages.slice(-4);
740
- const isRepeating = recentMessages.every(m =>
741
- m.role === 'assistant' &&
742
- recentMessages[0].content &&
743
- m.content === recentMessages[0].content
744
- );
745
- if (isRepeating) {
746
- console.log(chalk.yellow('\nāš ļø Detected repetitive behavior, stopping execution'));
747
- active = false;
748
- }
157
+ active = false;
749
158
  }
750
159
  }
751
160
  ask();
@@ -754,4 +163,4 @@ Working directory: ${process.cwd()}`
754
163
  ask();
755
164
  }
756
165
 
757
- runSapper();
166
+ runSapper();