sapper-iq 1.1.36 โ†’ 1.1.38

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/README.md CHANGED
@@ -13,6 +13,8 @@ Sapper is a command-line interface that connects to Ollama models to help you bu
13
13
  - ๐ŸŽฏ **Context-aware** - Automatically detects directory contents
14
14
  - โšก **Live streaming** - See AI responses in real-time
15
15
  - ๐Ÿ”’ **Security prompts** - Review commands before execution
16
+ - โœ๏ธ **Inline approval feedback** - Type feedback, or use `f` and `e` shortcuts at shell or file approval prompts, to make Sapper revise the command or change
17
+ - ๐Ÿงต **Background shell sessions** - Long-running commands can hand off to tracked background sessions with chunked output inspection
16
18
 
17
19
  ## Installation
18
20
 
@@ -36,13 +38,17 @@ sapper
36
38
 
37
39
  - `/reset` or `/clear-session` - Start a new session
38
40
  - `/session-info` - Show current session details
41
+ - `/summary` - View or change auto-summary settings
42
+ - `/shell` - Inspect shell config and tracked background sessions
43
+ - `/shell read <id>` - Read output from a tracked shell session
44
+ - `/shell stop <id>` - Stop a tracked shell session
39
45
  - `/step` - Toggle step-by-step mode
40
46
  - `/help` - Show command help
41
47
  - `exit` - Exit Sapper
42
48
 
43
49
  ### Example Interactions
44
50
 
45
- ```
51
+ ```text
46
52
  > set up a React project in ./my-app
47
53
  > run the development server
48
54
  > create a login component with TypeScript
@@ -57,6 +63,59 @@ sapper
57
63
  4. **Review & approve** - Security prompts for shell commands
58
64
  5. **Context awareness** - Sapper understands your project structure
59
65
 
66
+ ## Config
67
+
68
+ Sapper creates `.sapper/config.json` on first run. You can tune context behavior there.
69
+
70
+ ```json
71
+ {
72
+ "autoAttach": true,
73
+ "contextLimit": null,
74
+ "toolRoundLimit": 40,
75
+ "summaryPhases": true,
76
+ "summarizeTriggerPercent": 65,
77
+ "shell": {
78
+ "streamToModel": true,
79
+ "backgroundMode": "auto",
80
+ "backgroundAfterSeconds": 8,
81
+ "outputChunkChars": 4000
82
+ },
83
+ "thinking": {
84
+ "mode": "auto"
85
+ },
86
+ "streaming": {
87
+ "showPhaseStatus": true,
88
+ "showHeartbeat": true,
89
+ "idleNoticeSeconds": 4
90
+ },
91
+ "prompt": {
92
+ "prepend": "",
93
+ "append": "Prefer concise answers.",
94
+ "coreOverride": ""
95
+ }
96
+ }
97
+ ```
98
+
99
+ - `toolRoundLimit`: maximum tool-call rounds Sapper will allow in one prompt loop before it forces a final answer. Default: `40`.
100
+ - `summaryPhases`: show or hide the step-by-step auto-summary progress list.
101
+ - `summarizeTriggerPercent`: start summarizing older context near this percentage of the active context window. Lower values summarize earlier and reduce large-context pauses.
102
+ - `shell.streamToModel`: include shell output chunks in tool results when a command is handed off to a background session.
103
+ - `shell.backgroundMode`: three modes: `off`, `auto`, or `on`. `off` keeps commands fully attached so you keep seeing live shell output in the terminal. `auto` backgrounds likely long-running commands like dev servers; `on` applies the timeout to every shell command.
104
+ - `shell.backgroundAfterSeconds`: how long Sapper waits before handing an eligible running command off to a background shell session.
105
+ - `shell.outputChunkChars`: maximum shell output chunk size returned to the model for background handoffs and session reads.
106
+ - `thinking.mode`: three modes: `auto`, `on`, or `off`. `auto` skips long reasoning blocks for simple prompts, `on` always enables reasoning for every prompt, and `off` disables it for every prompt. This controls model reasoning visibility, not shell backgrounding.
107
+ - `streaming.showPhaseStatus`: show short status lines when Sapper is finalizing output, executing tools, or looping for the next model turn.
108
+ - `streaming.showHeartbeat`: keep updating the live progress line during quiet streamed phases instead of looking frozen.
109
+ - `streaming.idleNoticeSeconds`: print an idle notice after this many seconds without visible streamed output.
110
+ - `prompt.prepend`: insert custom instructions before the default Sapper prompt.
111
+ - `prompt.append`: add custom instructions near the end of the system prompt.
112
+ - `prompt.coreOverride`: replace the default Sapper core prompt block while keeping the tool, context, agent, and skill sections.
113
+
114
+ You can also change these inside Sapper with `/summary`, for example `/summary phases off` or `/summary trigger 60`.
115
+ Prompt config is read from `.sapper/config.json` and Sapper refreshes it on the next turn if you edit the file while it is running.
116
+ Background shell sessions are controlled through `run_shell` with `__shell_list__`, `__shell_read__ <session_id>`, and `__shell_stop__ <session_id>`.
117
+ You can also inspect them directly in Sapper with `/shell`, `/shell read <session_id>`, and `/shell stop <session_id>`.
118
+
60
119
  ## Supported Tools
61
120
 
62
121
  - `SHELL` - Execute terminal commands
@@ -69,12 +128,14 @@ sapper
69
128
  ## Examples
70
129
 
71
130
  **Create a Next.js project:**
72
- ```
131
+
132
+ ```text
73
133
  > create a Next.js app with TypeScript and Tailwind in ./my-nextjs-app
74
134
  ```
75
135
 
76
136
  **Add features to existing project:**
77
- ```
137
+
138
+ ```text
78
139
  > analyze the codebase in ./my-project
79
140
  > add a user authentication system
80
141
  > create API endpoints for user management
@@ -96,4 +157,4 @@ MIT
96
157
 
97
158
  ## Author
98
159
 
99
- Ibrahim Ihsan
160
+ Ibrahim Ihsan
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.1.36",
3
+ "version": "1.1.38",
4
4
  "description": "AI-powered development assistant that executes commands and builds projects",
5
5
  "main": "sapper.mjs",
6
6
  "bin": {
7
7
  "sapper": "sapper.mjs",
8
8
  "sapper-ui": "sapper-ui.mjs"
9
9
  },
10
+ "files": [
11
+ "sapper.mjs",
12
+ "sapper-ui.mjs",
13
+ "README.md"
14
+ ],
10
15
  "type": "module",
11
16
  "scripts": {
12
17
  "start": "node sapper.mjs"
package/sapper-ui.mjs CHANGED
@@ -88,6 +88,56 @@ function loadSkills() {
88
88
 
89
89
  const IGNORE_DIRS = new Set(['node_modules', '.git', '.sapper', '__pycache__', '.next', 'dist', 'build', '.cache']);
90
90
 
91
+ // โ”€โ”€โ”€ .sapperignore Support โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
92
+ const SAPPERIGNORE_FILE = '.sapperignore';
93
+
94
+ function loadSapperIgnorePatterns() {
95
+ const patterns = [];
96
+ try {
97
+ const ignorePath = join(workingDir, SAPPERIGNORE_FILE);
98
+ if (fs.existsSync(ignorePath)) {
99
+ const lines = fs.readFileSync(ignorePath, 'utf8').split('\n');
100
+ for (const rawLine of lines) {
101
+ const line = rawLine.trim();
102
+ if (!line || line.startsWith('#')) continue;
103
+ const negate = line.startsWith('!');
104
+ const pattern = negate ? line.slice(1) : line;
105
+ patterns.push({ pattern, negate });
106
+ }
107
+ }
108
+ } catch (e) {}
109
+ return patterns;
110
+ }
111
+
112
+ let _sapperIgnorePatterns = null;
113
+ function getSapperIgnorePatterns() {
114
+ if (_sapperIgnorePatterns === null) _sapperIgnorePatterns = loadSapperIgnorePatterns();
115
+ return _sapperIgnorePatterns;
116
+ }
117
+
118
+ function ignorePatternToRegex(pattern) {
119
+ let p = pattern.replace(/\/+$/, '');
120
+ p = p.replace(/([.+^${}()|[\]\\])/g, '\\$1');
121
+ p = p.replace(/\*\*/g, '<<<GLOBSTAR>>>');
122
+ p = p.replace(/\*/g, '[^/]*');
123
+ p = p.replace(/<<<GLOBSTAR>>>/g, '.*');
124
+ p = p.replace(/\?/g, '[^/]');
125
+ return new RegExp(`(^|/)${p}($|/)`, 'i');
126
+ }
127
+
128
+ function shouldIgnore(nameOrPath) {
129
+ const baseName = nameOrPath.includes('/') ? nameOrPath.split('/').pop() : nameOrPath;
130
+ if (IGNORE_DIRS.has(baseName)) return true;
131
+ const patterns = getSapperIgnorePatterns();
132
+ if (patterns.length === 0) return false;
133
+ let ignored = false;
134
+ for (const { pattern, negate } of patterns) {
135
+ const regex = ignorePatternToRegex(pattern);
136
+ if (regex.test(nameOrPath) || regex.test(baseName)) ignored = !negate;
137
+ }
138
+ return ignored;
139
+ }
140
+
91
141
  function safePath(p) {
92
142
  const resolved = resolve(workingDir, p || '.');
93
143
  if (!resolved.startsWith(workingDir)) return null;
@@ -150,7 +200,7 @@ const tools = {
150
200
  let dir = resolve(workingDir, path.trim() || '.');
151
201
  if (dir === '/') dir = workingDir;
152
202
  const entries = fs.readdirSync(dir);
153
- return entries.filter(e => !IGNORE_DIRS.has(e) && !e.startsWith('.')).join('\n') || '(empty)';
203
+ return entries.filter(e => !shouldIgnore(e) && !e.startsWith('.')).join('\n') || '(empty)';
154
204
  } catch (e) { return `Error: ${e.message}`; }
155
205
  },
156
206
  read: (path) => {
@@ -208,7 +258,11 @@ const tools = {
208
258
  },
209
259
  search: (pattern) => {
210
260
  return new Promise((res) => {
211
- const excludes = [...IGNORE_DIRS].join(',');
261
+ const allIgnoreDirs = new Set(IGNORE_DIRS);
262
+ for (const { pattern: p, negate } of getSapperIgnorePatterns()) {
263
+ if (!negate && p.endsWith('/')) allIgnoreDirs.add(p.replace(/\/+$/, ''));
264
+ }
265
+ const excludes = [...allIgnoreDirs].join(',');
212
266
  const cmd = `grep -rEin "${pattern.replace(/"/g, '\\"')}" . --exclude-dir={${excludes}} --include="*.{js,ts,jsx,tsx,py,java,go,rs,rb,php,c,cpp,h,css,scss,html,json,md,txt,yml,yaml,toml,sh}" 2>/dev/null | head -50`;
213
267
  const proc = spawn('sh', ['-c', cmd], { cwd: workingDir });
214
268
  let out = '';
@@ -403,7 +457,7 @@ function getTreeEntries(dirPath) {
403
457
  try {
404
458
  const entries = fs.readdirSync(safe);
405
459
  return entries
406
- .filter(e => !IGNORE_DIRS.has(e) && !e.startsWith('.'))
460
+ .filter(e => !shouldIgnore(e) && !e.startsWith('.'))
407
461
  .map(e => {
408
462
  try {
409
463
  const stat = fs.statSync(join(safe, e));