shmakk 1.2.1 → 1.2.3

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
@@ -170,6 +170,46 @@ The coordinator system enables complex, multi-step task execution with plan-firs
170
170
  - Secrets (`.env`, keys, tokens) are never sent to the AI
171
171
  - Workspace root is enforced — tools can't access files outside it
172
172
 
173
+ ## Remote SSH
174
+
175
+ The agent can run commands and transfer files on remote hosts via SSH. Configure hosts in `.shmakk/hosts.json` (per-project) or `~/.config/shmakk/hosts.json` (global):
176
+
177
+ ```json
178
+ {
179
+ "hosts": {
180
+ "devbox": {
181
+ "host": "marcus@192.168.1.100",
182
+ "port": 22,
183
+ "auto_approve": false,
184
+ "timeout_sec": 30
185
+ },
186
+ "staging": {
187
+ "host": "deploy@10.0.0.5",
188
+ "port": 2247
189
+ }
190
+ },
191
+ "allow_ssh_config": false,
192
+ "default_timeout_sec": 30
193
+ }
194
+ ```
195
+
196
+ | Tool | Description |
197
+ |------|-------------|
198
+ | `ssh_run` | Run a shell command on a remote host |
199
+ | `ssh_push` | Copy a local workspace file to a remote host |
200
+ | `ssh_pull` | Copy a remote file into the local workspace |
201
+
202
+ SSH key auth via `~/.ssh` is assumed. For persistent connections (avoid re-auth on every call), add to `~/.ssh/config`:
203
+
204
+ ```
205
+ Host *
206
+ ControlMaster auto
207
+ ControlPath ~/.ssh/controlmasters/%r@%h:%p
208
+ ControlPersist 600
209
+ ```
210
+
211
+ Then `mkdir -p ~/.ssh/controlmasters` once.
212
+
173
213
  ## How it works
174
214
 
175
215
  shmakk wraps your shell in a PTY (pseudo-terminal). Every command that fails is checked against a deterministic correction engine (no LLM, no API call). If a correction matches and the fixed command succeeds, shmakk feeds the agent your **original input** (not the fixed command) so the agent can address your full intent — not just the typo. You can also give task instructions in natural language — shmakk uses tools to read files, write code, list directories, and run commands, all constrained to your workspace.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shmakk",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "AI-supervised terminal wrapper — command correction, tool-driven tasks, safety controls",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/src/agent.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
- const { makeClient, modelFor, isConfigured, getDeepSeekOptions } = require('./llm');
10
+ const { makeClient, modelFor, isConfigured, getDeepSeekOptions, supportsVision } = require('./llm');
11
11
  const {
12
12
  sanitizeAssistantContent,
13
13
  isLeakedToolMarkup,
@@ -87,13 +87,16 @@ function clearTaskJournal(root) {
87
87
  const { renderBlock } = require('./markdown');
88
88
 
89
89
  // Tiny spinner so the user sees "the agent is thinking" while we wait on
90
- // the model. Erased when stop() is called.
90
+ // the model. Erased when stop() is called. Also updates the terminal tab
91
+ // title so you can see agent activity even when looking at another tab.
91
92
  function startSpinner(write, label = 'thinking') {
92
93
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
93
94
  let i = 0; let line = '';
94
95
  const draw = () => {
95
96
  line = `\x1b[2m${frames[i % frames.length]} ${label}…\x1b[0m`;
96
97
  write('\r' + line);
98
+ // Update terminal tab/window title so activity is visible from other tabs
99
+ write(`\x1b]0;${frames[i % frames.length]} ${label} — shmakk\x07`);
97
100
  i++;
98
101
  };
99
102
  draw();
@@ -101,6 +104,8 @@ function startSpinner(write, label = 'thinking') {
101
104
  return () => {
102
105
  clearInterval(tm);
103
106
  write('\r' + ' '.repeat(line.replace(/\x1b\[[0-9;]*m/g, '').length + 2) + '\r\r');
107
+ // Clear the terminal title — shell will restore normal title on next prompt
108
+ write('\x1b]0;\x07');
104
109
  };
105
110
  }
106
111
 
@@ -251,6 +256,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
251
256
  mcpToolHint,
252
257
  userRulesText,
253
258
  userMemoryText,
259
+ supportsVision: supportsVision(),
254
260
  });
255
261
 
256
262
  const boundedHistory = trimForContext(history, runtimeProfile.historyEntries);
@@ -292,14 +298,22 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
292
298
  // Tool loop. Streams content as it arrives; prints each tool call.
293
299
  let producedAnything = false;
294
300
  const runState = { _dsmlLeakRetries: 0 };
301
+ let taskSpinnerStop = null;
302
+ const ensureSpinner = () => {
303
+ if (!taskSpinnerStop) taskSpinnerStop = startSpinner(write, 'thinking');
304
+ };
305
+ const stopTaskSpinner = () => {
306
+ if (taskSpinnerStop) { taskSpinnerStop(); taskSpinnerStop = null; }
307
+ };
295
308
  for (let i = 0; i < dynamicToolBudget; i++) {
296
- if (signal && signal.aborted) return messages.slice(1);
309
+ if (signal && signal.aborted) { stopTaskSpinner(); return messages.slice(1); }
297
310
 
298
311
  // Stream the response so the user sees text as it generates.
299
312
  const cacheKey = promptCacheEnabled ? promptCache.makeKey({ model: modelFor('agent'), messages, toolChoice: 'auto' }) : null;
300
313
  if (promptCacheEnabled && cacheKey) {
301
314
  const hit = promptCache.get(roots[0], cacheKey);
302
315
  if (hit && hit.content) {
316
+ stopTaskSpinner();
303
317
  write(dim('[shmakk] prompt cache hit', colors) + '\n');
304
318
  write(highlightCodeBlocks(hit.content, colors));
305
319
  if (!hit.content.endsWith('\n')) write('\n');
@@ -309,7 +323,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
309
323
  }
310
324
  }
311
325
 
312
- const stop = startSpinner(write, i === 0 ? 'thinking' : 'continuing');
326
+ ensureSpinner();
313
327
  const allTools = mcpManager ? [...TOOLS, ...mcpManager.getToolDefinitions()] : TOOLS;
314
328
  let stream;
315
329
  try {
@@ -324,14 +338,13 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
324
338
  ...getDeepSeekOptions('tool_loop'),
325
339
  }, { signal });
326
340
  } catch (e) {
327
- stop();
341
+ stopTaskSpinner();
328
342
  throw e;
329
343
  }
330
344
 
331
345
  let content = '';
332
346
  let reasoningContent = '';
333
347
  const toolCalls = []; // [{id, type:'function', function:{name, arguments}}]
334
- let spinnerStopped = false;
335
348
  let streamingContentOk = true; // flipped to false on leak
336
349
  try {
337
350
  for await (const chunk of stream) {
@@ -370,7 +383,6 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
370
383
  reasoningContent += delta.reasoning_content;
371
384
  }
372
385
  if (delta.tool_calls) {
373
- if (!spinnerStopped) { stop(); spinnerStopped = true; }
374
386
  for (const tc of delta.tool_calls) {
375
387
  const idx = tc.index ?? 0;
376
388
  if (!toolCalls[idx]) toolCalls[idx] = { id: tc.id || '', type: 'function', function: { name: '', arguments: '' } };
@@ -382,7 +394,8 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
382
394
  }
383
395
  }
384
396
  } finally {
385
- if (!spinnerStopped) stop();
397
+ // Spinner runs continuously from loop start — stopped below when
398
+ // we're about to show output or dispatch tools that may need input.
386
399
  }
387
400
 
388
401
  // ── DSML leak detection (after stream completes) ────────────────────
@@ -427,6 +440,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
427
440
  // Max retries exceeded — strip and show what we can.
428
441
  content = sanitized.visibleText;
429
442
  if (!content) {
443
+ stopTaskSpinner();
430
444
  write(dim('[shmakk] response contained only leaked tool markup — blocked.', colors) + '\n');
431
445
  clearTaskJournal(roots[0]);
432
446
  return messages.slice(1);
@@ -477,6 +491,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
477
491
 
478
492
  // No tools → done.
479
493
  if (!normalizedToolCalls.length) {
494
+ stopTaskSpinner();
480
495
  if (content) {
481
496
  write(renderBlock(content, { enabled: markdown, colors }));
482
497
  if (!content.endsWith('\n')) write('\n');
@@ -497,6 +512,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
497
512
  // Dispatch tool calls.
498
513
  // Reads/lists are noisy — collect them silently and show a single dim summary.
499
514
  // Writes, edits, runs, and errors always print clearly.
515
+ stopTaskSpinner();
500
516
  const SILENT_TOOLS = new Set(['read_file', 'list_dir', 'web_search', 'fetch_url']);
501
517
  let iterProgress = false;
502
518
  let silentReads = [];
@@ -534,7 +550,34 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
534
550
  write(dim(`→ ${c.function.name}(${shortArgs(args)})${summary ? ' — ' + summary : ''}`, colors) + '\n');
535
551
  }
536
552
 
537
- messages.push({ role: 'tool', tool_call_id: c.id, content: JSON.stringify(result).slice(0, 8000) });
553
+ // Build tool result message. If the result includes images with real
554
+ // base64 data AND the current endpoint has `vision: true`, send them as
555
+ // vision content blocks in a follow-up user message. Otherwise just
556
+ // include image metadata in the text. Keep the tool message text-only.
557
+ const toolImages = Array.isArray(result.images) ? result.images.filter((img) => img.data) : [];
558
+ const toolText = result.content || '';
559
+ const toolMeta = Object.fromEntries(
560
+ Object.entries(result || {}).filter(([k]) => !['content', 'images', 'error', 'isRetryable'].includes(k)),
561
+ );
562
+ let toolContent = (toolText + (Object.keys(toolMeta).length ? ' ' + JSON.stringify(toolMeta) : '')).trim();
563
+ if (toolImages.length > 0 && !supportsVision()) {
564
+ // Endpoint doesn't support vision — include image metadata as text
565
+ const imgDesc = toolImages.map((img, i) => `[Image #${i + 1}: ${img.mimeType}, base64=${img.dataLength} chars${img.truncated ? ', truncated' : ''}]`).join(', ');
566
+ toolContent = toolContent ? `${toolContent} ${imgDesc}` : imgDesc;
567
+ }
568
+ messages.push({ role: 'tool', tool_call_id: c.id, content: toolContent.slice(0, 8000) });
569
+ if (toolImages.length > 0 && supportsVision()) {
570
+ messages.push({
571
+ role: 'user',
572
+ content: [
573
+ { type: 'text', text: `[Images returned by ${c.function.name}${toolImages.some((img) => img.truncated) ? ' (some truncated to ~2MB base64)' : ''}]` },
574
+ ...toolImages.map((img) => ({
575
+ type: 'image_url',
576
+ image_url: { url: `data:${img.mimeType};base64,${img.data}`, detail: 'auto' },
577
+ })),
578
+ ],
579
+ });
580
+ }
538
581
  producedAnything = true;
539
582
  persistJournal('running');
540
583
  if (signal && signal.aborted) return messages.slice(1);
@@ -553,6 +596,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
553
596
  }
554
597
 
555
598
  if (repeatedSignatureCount >= runtimeProfile.stallRepeat && noProgressRepeats >= 2) {
599
+ stopTaskSpinner();
556
600
  break;
557
601
  }
558
602
  }
@@ -589,6 +633,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
589
633
  }
590
634
  } catch {}
591
635
 
636
+ stopTaskSpinner();
592
637
  write(dim('[shmakk] paused after several tool rounds. Resume later continues from task journal; try `shmakk --reset` to clear.', colors) + '\n');
593
638
  persistJournal('paused');
594
639
  return messages.slice(1);
package/src/cli.js CHANGED
@@ -41,8 +41,10 @@ function parseArgs(argv) {
41
41
  voiceSilenceStartSec: null,
42
42
  voicePadStartSec: null,
43
43
  ttsVoice: null,
44
- notify: false,
44
+ notify: true,
45
45
  completion: null,
46
+ helpCategory: null,
47
+ shell: null,
46
48
  unknown: [],
47
49
  };
48
50
 
@@ -53,7 +55,13 @@ function parseArgs(argv) {
53
55
  case '--yes-files': opts.yesFiles = true; break;
54
56
  case '--update-command-glossary': opts.updateGlossary = true; break;
55
57
  case '-h':
56
- case '--help': opts.help = true; break;
58
+ case '--help':
59
+ opts.help = true;
60
+ // Capture optional category: shmakk --help voice
61
+ if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
62
+ opts.helpCategory = argv[++i];
63
+ }
64
+ break;
57
65
  case '--debug': opts.debug = true; break;
58
66
  case '--no-ai': opts.noAi = true; break;
59
67
  case '--no-correction': opts.noCorrection = true; break;
@@ -102,34 +110,67 @@ function parseArgs(argv) {
102
110
  case '--markdown': opts.markdown = argv[++i] || null; break;
103
111
  case '--endpoint': opts.endpoint = argv[++i] || null; break;
104
112
  case '--model-recommendation': opts.modelRecommendation = true; break;
113
+ case '--shell':
114
+ {
115
+ const v = argv[++i];
116
+ if (!v || !['fish', 'bash', 'zsh'].includes(v)) {
117
+ process.stderr.write('[shmakk] invalid --shell. Use: fish|bash|zsh\n');
118
+ process.exit(2);
119
+ }
120
+ opts.shell = v;
121
+ }
122
+ break;
105
123
  default: opts.unknown.push(a);
106
124
  }
107
125
  }
108
126
  return opts;
109
127
  }
110
128
 
111
- const HELP = `shmakk - AI-supervised terminal wrapper
129
+ // ── Help: category-based navigation ──────────────────────────────────────────
130
+
131
+ const HELP_SUMMARY = `shmakk - AI-supervised terminal wrapper
112
132
 
113
133
  Launch shmakk, then type commands as usual. shmakk watches the shell, catches
114
134
  failures, and calls an LLM to fix them, plan tasks, and edit files.
115
135
 
116
- You can also type natural-language self-commands directly into the session
117
- (e.g. "list skills", "agent overview", "compact"). See SELF-COMMANDS below.
118
-
119
136
  Type "help" inside a session to see this same text.
120
137
 
138
+ Usage: shmakk [--flag ...]
139
+ shmakk --help [category]
140
+
141
+ Categories (shmakk --help <name> for details):
142
+
143
+ launch Startup modes, profiles, tuning flags
144
+ session Status, stats, restart, exit, control signals
145
+ skills Skill discovery, loading, listing, management
146
+ models Provider configuration, endpoint presets
147
+ voice Speech-to-Text / Text-to-Speech options
148
+ env Environment variable reference
149
+ mcp MCP servers and browser automation
150
+ ssh Remote host execution
151
+ self Natural-language self-commands (inside a session)
152
+
153
+ `;
154
+
155
+ const HELP_SECTIONS = {};
156
+
157
+ HELP_SECTIONS.launch = `═══════════════════════════════════════════════════════════════════════════
158
+ LAUNCH OPTIONS (shmakk --flag from outside a session)
121
159
  ═══════════════════════════════════════════════════════════════════════════
122
- LAUNCH OPTIONS
123
- ═══════════════════════════════════════════════════════════════════════════
160
+
161
+ These flags only apply when starting a new shmakk session. They are
162
+ ignored if you are already inside shmakk (SHMAKK=1).
124
163
 
125
164
  shmakk Launch in auto mode (AI acts on failures)
126
165
  shmakk --review Launch in review mode (confirm every AI action)
127
166
  shmakk --yes-files Auto-accept file writes, edits, mkdir
128
167
 
129
- shmakk --help Show this help
168
+ shmakk --help Show overview (this text)
169
+ shmakk --help <category> Show detailed help for a category
130
170
  shmakk --build-history [files] Parse shell history for better corrections
131
171
  shmakk --update-command-glossary Scan PATH and build local command glossary
132
172
 
173
+ --shell <fish|bash|zsh> Use a specific shell (default: current $SHELL)
133
174
  --no-ai Disable AI entirely (pure passthrough)
134
175
  --no-correction Disable command correction
135
176
  --debug Verbose logging to stderr
@@ -140,8 +181,9 @@ const HELP = `shmakk - AI-supervised terminal wrapper
140
181
  --colors <true|false> Enable or disable ANSI colors
141
182
  --markdown <true|false> Enable or disable markdown rendering
142
183
  --notify Desktop notifications for Y/n prompts
184
+ `;
143
185
 
144
- ═══════════════════════════════════════════════════════════════════════════
186
+ HELP_SECTIONS.models = `═══════════════════════════════════════════════════════════════════════════
145
187
  MODEL PROVIDERS
146
188
  ═══════════════════════════════════════════════════════════════════════════
147
189
 
@@ -159,8 +201,9 @@ const HELP = `shmakk - AI-supervised terminal wrapper
159
201
  "model":"qwen/qwen3.5-9b" }
160
202
  }
161
203
  }
204
+ `;
162
205
 
163
- ═══════════════════════════════════════════════════════════════════════════
206
+ HELP_SECTIONS.session = `═══════════════════════════════════════════════════════════════════════════
164
207
  SESSION CONTROL (shmakk --flag from another terminal)
165
208
  ═══════════════════════════════════════════════════════════════════════════
166
209
 
@@ -176,6 +219,11 @@ const HELP = `shmakk - AI-supervised terminal wrapper
176
219
  shmakk --exit Cleanly exit the parent shmakk
177
220
 
178
221
  shmakk --profile-set <name> Switch profile and restart
222
+ `;
223
+
224
+ HELP_SECTIONS.skills = `═══════════════════════════════════════════════════════════════════════════
225
+ SKILLS
226
+ ═══════════════════════════════════════════════════════════════════════════
179
227
 
180
228
  shmakk --load-skill <name> Load a skill into workspace state
181
229
  shmakk --install-skill <url> Download skill markdown from URL, validate, load
@@ -183,71 +231,9 @@ const HELP = `shmakk - AI-supervised terminal wrapper
183
231
  shmakk --list-skills List all registered skills (workspace + global)
184
232
  shmakk --skill-status Active skill and registry status
185
233
  shmakk --unload-skill <name> Remove skill from whichever registry has it
234
+ `;
186
235
 
187
- ═══════════════════════════════════════════════════════════════════════════
188
- SELF-COMMANDS (type inside an shmakk session)
189
- ═══════════════════════════════════════════════════════════════════════════
190
-
191
- ── Skills ──
192
- list skills List all registered skills
193
- list skills <category> List skills in a specific category
194
- list skill categories Show available skill categories
195
- find skills <query> Search skills by name/description
196
- load skill <name> Load a skill into the active workspace
197
- unload skill <name> Remove a skill from its registry
198
- skill status Show active skill and registry state
199
-
200
- ── Agents & Team ──
201
- agent overview Show all agents and their specialisms
202
- agent skills List all agent skills
203
- agent <name> Show detail for a specific agent
204
- list agents Alias for agent overview
205
-
206
- ── Context & Session ──
207
- status Show session status
208
- stats Show session/task statistics
209
- resume status Show task journal for resume continuity
210
- show plan Display current plan and progress
211
- compact Clear conversation + task journal
212
- reset Clear AI conversation history
213
-
214
- ── Memory & Search ──
215
- recall <query> Search past sessions by content
216
- find session <query> Find a session by topic
217
- last sessions Show recent sessions
218
- search db status Display session search DB info
219
- show memory List stored memories
220
- forget <query> Remove matching memories
221
-
222
- ── Configuration ──
223
- show config Print resolved configuration
224
- mcp status Show MCP servers and tools
225
- show rules Display active workspace rules
226
- list endpoints List configured model endpoints
227
- use endpoint <name> Switch to a named model endpoint
228
- set model to <name> Change the active model
229
- set url to <url> Change the base URL
230
- set api key to <key> Change the API key
231
-
232
- ── Toggles ──
233
- enable review | disable review
234
- enable correction | disable correction
235
- enable yes-files | disable yes-files
236
- enable colors | disable colors
237
- enable debug | disable debug
238
-
239
- ── Workflows ──
240
- list workflows Show available automation workflows
241
- run workflow <name> Execute a named workflow
242
-
243
- ── Edits ──
244
- review edits Step through pending file changes
245
-
246
- ── Meta ──
247
- sidebar <query> Out-of-band agent query (not added to history)
248
- help Show this help
249
-
250
- ═══════════════════════════════════════════════════════════════════════════
236
+ HELP_SECTIONS.voice = `═══════════════════════════════════════════════════════════════════════════
251
237
  VOICE (Speech-to-Text / Text-to-Speech)
252
238
  ═══════════════════════════════════════════════════════════════════════════
253
239
 
@@ -266,8 +252,9 @@ const HELP = `shmakk - AI-supervised terminal wrapper
266
252
  STT: Whisper-base ONNX in-process. No Python, no server, no API key.
267
253
  TTS: kokoro-js (Kokoro-82M ONNX, ~334MB fp16). Auto-download on first use.
268
254
  Requires aplay, paplay, or afplay for audio. 28 voices, rotated daily.
255
+ `;
269
256
 
270
- ═══════════════════════════════════════════════════════════════════════════
257
+ HELP_SECTIONS.env = `═══════════════════════════════════════════════════════════════════════════
271
258
  ENVIRONMENT
272
259
  ═══════════════════════════════════════════════════════════════════════════
273
260
 
@@ -287,8 +274,9 @@ const HELP = `shmakk - AI-supervised terminal wrapper
287
274
  SHMAKK_VOICE_SILENCE_SEC VAD silence threshold seconds
288
275
  SHMAKK_VOICE_SILENCE_THRESHOLD VAD amplitude threshold
289
276
  SHMAKK_VOICE_PAD_START_SEC Start-of-recording padding
277
+ `;
290
278
 
291
- ═══════════════════════════════════════════════════════════════════════════
279
+ HELP_SECTIONS.mcp = `═══════════════════════════════════════════════════════════════════════════
292
280
  MCP & BROWSER
293
281
  ═══════════════════════════════════════════════════════════════════════════
294
282
 
@@ -299,7 +287,134 @@ const HELP = `shmakk - AI-supervised terminal wrapper
299
287
  npm install playwright && npx playwright install chromium
300
288
  Tools: navigate, click, type, read_page, screenshot, evaluate, select,
301
289
  wait, scroll, close.
290
+ `;
291
+
292
+ HELP_SECTIONS.ssh = `═══════════════════════════════════════════════════════════════════════════
293
+ REMOTE HOSTS (SSH)
294
+ ═══════════════════════════════════════════════════════════════════════════
295
+
296
+ The agent can run commands on remote hosts and transfer files via SSH.
297
+ Configure hosts in .shmakk/hosts.json or ~/.config/shmakk/hosts.json:
298
+
299
+ {
300
+ "hosts": {
301
+ "devbox": {
302
+ "host": "user@192.168.1.100",
303
+ "port": 22,
304
+ "auto_approve": false,
305
+ "timeout_sec": 30
306
+ }
307
+ },
308
+ "allow_ssh_config": false,
309
+ "default_timeout_sec": 30
310
+ }
311
+
312
+ Agent tools: ssh_run (run command), ssh_push (upload), ssh_pull (download).
313
+ For persistent connections, use ControlMaster in ~/.ssh/config:
314
+ Host *
315
+ ControlMaster auto
316
+ ControlPath ~/.ssh/controlmasters/%r@%h:%p
317
+ ControlPersist 600
318
+ `;
319
+
320
+ HELP_SECTIONS.self = `═══════════════════════════════════════════════════════════════════════════
321
+ SELF-COMMANDS (type inside an shmakk session)
322
+ ═══════════════════════════════════════════════════════════════════════════
323
+
324
+ Self-commands work with a prefix — /cmd or shmakk cmd.
325
+ Bare words like "status" or "stats" go to the shell, not shmakk.
326
+
327
+ -- Session --
328
+ /status | shmakk status Show session status
329
+ /stats | shmakk stats Show session/task statistics
330
+ /sessions | shmakk sessions Show recent sessions
331
+ show sessions | last sessions (same as /sessions)
332
+ resume status Show task journal for resume continuity
333
+ show plan Display current plan and progress
334
+ /compact | shmakk compact Clear conversation + task journal
335
+ /reset | shmakk reset Clear AI conversation history
336
+
337
+ -- Skills --
338
+ list skills List all registered skills
339
+ list skills <category> List skills in a specific category
340
+ list skill categories Show available skill categories
341
+ find skills <query> Search skills by name/description
342
+ load skill <name> Load a skill into the active workspace
343
+ unload skill <name> Remove a skill from its registry
344
+ skill status Show active skill and registry state
345
+
346
+ -- Agents & Team --
347
+ agent overview Show all agents and their specialisms
348
+ agent skills List all agent skills
349
+ agent <name> Show detail for a specific agent
350
+ list agents Alias for agent overview
351
+
352
+ -- Memory & Search --
353
+ recall <query> Search past sessions by content
354
+ find session <query> Find a session by topic
355
+ search db status Display session search DB info
356
+ show memory List stored memories
357
+ forget <query> Remove matching memories
358
+
359
+ -- Configuration --
360
+ show config Print resolved configuration
361
+ mcp status Show MCP servers and tools
362
+ show rules Display active workspace rules
363
+ list endpoints List configured model endpoints
364
+ use endpoint <name> Switch to a named model endpoint
365
+ set model to <name> Change the active model
366
+ set url to <url> Change the base URL
367
+ set api key to <key> Change the API key
368
+
369
+ -- Toggles --
370
+ enable review | disable review
371
+ enable correction | disable correction
372
+ enable yes-files | disable yes-files
373
+ enable colors | disable colors
374
+ enable debug | disable debug
375
+
376
+ -- Workflows --
377
+ list workflows Show available automation workflows
378
+ run workflow <name> Execute a named workflow
379
+
380
+ -- Edits --
381
+ review edits Step through pending file changes
382
+
383
+ -- Meta --
384
+ sidebar <query> Out-of-band agent query (not added to history)
385
+ help Show this help
386
+ `;
387
+
388
+ // Resolve: returns the full old HELP string for backward compat, or the category
389
+ // text, or the summary.
390
+ function resolveHelp(category) {
391
+ if (category) {
392
+ const key = category.toLowerCase();
393
+ if (HELP_SECTIONS[key]) return HELP_SECTIONS[key];
394
+ // Unknown category: show summary + available categories
395
+ const available = Object.keys(HELP_SECTIONS).join(', ');
396
+ return HELP_SUMMARY + `Unknown category "${category}". Available: ${available}\n`;
397
+ }
398
+ return HELP_SUMMARY;
399
+ }
400
+
401
+ // Help text shown INSIDE a session (no launch flags — you're already running)
402
+ const HELP_SESSION_SUMMARY = `shmakk — inside a session
403
+
404
+ Type commands as usual. shmakk watches the shell, catches failures, and
405
+ calls an LLM to fix them, plan tasks, and edit files.
406
+
407
+ Self-commands use a prefix: /cmd or shmakk cmd
408
+ Examples: /status /sessions shmakk status shmakk show sessions
409
+
410
+ Multi-word natural language also works: "show help" "list skills"
411
+
412
+ For the full reference: shmakk --help self (from outside the session)
413
+ For launch flags: shmakk --help launch
302
414
 
303
415
  `;
304
416
 
305
- module.exports = { parseArgs, HELP };
417
+ // Full legacy HELP for backward compat
418
+ const HELP = HELP_SUMMARY + Object.values(HELP_SECTIONS).join('\n');
419
+
420
+ module.exports = { parseArgs, HELP, resolveHelp, HELP_SUMMARY, HELP_SESSION_SUMMARY };
package/src/correction.js CHANGED
@@ -115,6 +115,12 @@ function preserveCase(original, corrected) {
115
115
  }
116
116
 
117
117
  async function correct({ input, glossary, signal: _unused }) {
118
+ // /-prefixed and "shmakk ..." commands are shmakk self-commands.
119
+ // They should never reach the correction engine — bail out immediately.
120
+ if (/^\//.test(input) || /^shmakk\s/i.test(input)) {
121
+ return { category: 'not_a_correction', proposed: null, safety: 'uncertain', reason: 'shmakk self-command prefix — not a shell command' };
122
+ }
123
+
118
124
  // Pre-filter natural language
119
125
  if (looksLikeNaturalLanguage(input)) {
120
126
  return { category: 'not_a_correction', proposed: null, safety: 'uncertain', reason: 'looks like natural language' };
package/src/endpoints.js CHANGED
@@ -73,6 +73,7 @@ function normalizeModelConfig(name, cfg) {
73
73
  headers: cfg.headers || cfg.headears || null,
74
74
  registry: cfg.registry || null,
75
75
  main: !!cfg.main,
76
+ vision: !!cfg.vision,
76
77
  };
77
78
  }
78
79
 
@@ -143,6 +144,10 @@ function getCurrentEndpointName() {
143
144
  return currentEndpointName;
144
145
  }
145
146
 
147
+ function supportsVision() {
148
+ return !!(currentEndpointConfig && currentEndpointConfig.vision);
149
+ }
150
+
146
151
  function listEndpoints(cwd) {
147
152
  return Object.keys(loadModelRegistry(cwd).models);
148
153
  }
@@ -160,5 +165,6 @@ module.exports = {
160
165
  listEndpoints,
161
166
  getCurrentEndpoint,
162
167
  getCurrentEndpointName,
168
+ supportsVision,
163
169
  getModelRegistry,
164
170
  };