shmakk 1.2.0 → 1.2.1
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 +28 -2
- package/package.json +2 -2
- package/scripts/demo/record.py +196 -0
- package/scripts/demo/scenes.html +913 -0
- package/skills/media-video-compose.md +320 -0
- package/skills/media-video-script.md +204 -0
- package/skills/media-video-voice.md +184 -0
- package/src/agent-overview.js +320 -0
- package/src/agent-roster.js +53 -0
- package/src/agent.js +178 -18
- package/src/cli.js +193 -86
- package/src/completions.js +3 -1
- package/src/correction.js +11 -4
- package/src/endpoints.js +94 -31
- package/src/guard.js +101 -0
- package/src/index.js +19 -5
- package/src/llm.js +462 -52
- package/src/markdown.js +217 -0
- package/src/notify.js +34 -0
- package/src/pty.js +1 -1
- package/src/review.js +8 -1
- package/src/self-commands.js +108 -2
- package/src/session.js +58 -2
- package/src/subagent.js +12 -1
- package/src/taskClassifier.js +2 -2
- package/src/team.js +22 -0
- package/src/tools.js +408 -1
- package/src/workflows.js +32 -0
package/src/agent.js
CHANGED
|
@@ -7,13 +7,23 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const { makeClient, modelFor, isConfigured } = require('./llm');
|
|
10
|
+
const { makeClient, modelFor, isConfigured, getDeepSeekOptions } = require('./llm');
|
|
11
|
+
const {
|
|
12
|
+
sanitizeAssistantContent,
|
|
13
|
+
isLeakedToolMarkup,
|
|
14
|
+
mightBecomeInternalMarkup,
|
|
15
|
+
MUTATION_TOOLS,
|
|
16
|
+
isMutationTool,
|
|
17
|
+
hashArgs,
|
|
18
|
+
DSML_RETRY_USER_MESSAGE,
|
|
19
|
+
} = require('./guard');
|
|
11
20
|
const { buildOrRefreshIndex, relevantSubgraph } = require('./workspace-index');
|
|
12
21
|
const { renderActiveSkillForPrompt } = require('./skills');
|
|
13
22
|
const { renderRulesForPrompt } = require('./rules');
|
|
14
23
|
const { renderMemoryForPrompt } = require('./memory');
|
|
15
24
|
const sessionSearch = require('./session-search');
|
|
16
25
|
const promptCache = require('./prompt-cache');
|
|
26
|
+
const audit = require('./audit');
|
|
17
27
|
const { buildSystemPrompt } = require('./system-prompt');
|
|
18
28
|
const {
|
|
19
29
|
TOOLS,
|
|
@@ -74,6 +84,8 @@ function clearTaskJournal(root) {
|
|
|
74
84
|
try { fs.rmSync(journalPath(root), { force: true }); } catch {}
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
const { renderBlock } = require('./markdown');
|
|
88
|
+
|
|
77
89
|
// Tiny spinner so the user sees "the agent is thinking" while we wait on
|
|
78
90
|
// the model. Erased when stop() is called.
|
|
79
91
|
function startSpinner(write, label = 'thinking') {
|
|
@@ -94,19 +106,9 @@ function startSpinner(write, label = 'thinking') {
|
|
|
94
106
|
|
|
95
107
|
function dim(s, enabled = true) { return enabled ? `\x1b[2m${s}\x1b[0m` : s; }
|
|
96
108
|
|
|
97
|
-
function highlightCodeBlocks(text, enabled = true) {
|
|
98
|
-
const src = String(text || '');
|
|
99
|
-
if (!enabled) return src;
|
|
100
|
-
return src.replace(/```([a-zA-Z0-9_-]*)\n([\s\S]*?)```/g, (_m, lang, code) => {
|
|
101
|
-
const head = `\x1b[36m\x1b[1m${lang || 'code'}\x1b[0m`;
|
|
102
|
-
const body = `\x1b[90m${code.replace(/\n$/, '')}\x1b[0m`;
|
|
103
|
-
return `${head}\n${body}`;
|
|
104
|
-
}).replace(/\u0000\u0000\u0000/g, '```');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
109
|
// ── Main agent entry point ──────────────────────────────────────────────────
|
|
108
110
|
|
|
109
|
-
async function runAgent({ input, roots, glossary, confirmTool, write, signal, history = [], profile = 'balanced', colors = true, voiceMode = false, specialistHint = null, mcpManager = null, requireToolUse = false }) {
|
|
111
|
+
async function runAgent({ input, roots, glossary, confirmTool, write, signal, history = [], profile = 'balanced', colors = true, markdown = true, voiceMode = false, specialistHint = null, mcpManager = null, requireToolUse = false }) {
|
|
110
112
|
// roots: array of allowed workspace roots (first is the primary cwd).
|
|
111
113
|
// history: prior conversation turns (assistant/user/tool). System prompt
|
|
112
114
|
// is rebuilt fresh each call so the current cwd is always accurate.
|
|
@@ -140,6 +142,40 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
140
142
|
|
|
141
143
|
persistJournal('running');
|
|
142
144
|
const promptCacheEnabled = String(process.env.SHMAKK_PROMPT_CACHE || '1') !== '0';
|
|
145
|
+
|
|
146
|
+
// ── Per-turn mutation-approval state ───────────────────────────────────
|
|
147
|
+
// Every mutation tool call must be individually approved. If the user
|
|
148
|
+
// denies ANY mutation in a turn, ALL pending mutations from that turn are
|
|
149
|
+
// invalidated. This prevents the agent from executing an edit after the
|
|
150
|
+
// user said no.
|
|
151
|
+
const turnApprovals = new Map(); // toolCallId → { approved, argsHash, expiresAt }
|
|
152
|
+
let turnDenied = false;
|
|
153
|
+
|
|
154
|
+
function clearTurnApprovals() {
|
|
155
|
+
turnApprovals.clear();
|
|
156
|
+
turnDenied = false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function wrapConfirmTool(baseConfirm) {
|
|
160
|
+
if (!baseConfirm) return null;
|
|
161
|
+
return async ({ name, args, safety, description }) => {
|
|
162
|
+
// If this turn has already been denied, reject all mutation tools.
|
|
163
|
+
if (turnDenied && isMutationTool(name)) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const ok = await baseConfirm({ name, args, safety, description });
|
|
167
|
+
if (!ok && isMutationTool(name)) {
|
|
168
|
+
turnDenied = true;
|
|
169
|
+
// Invalidate any pre-approved mutation calls from this turn.
|
|
170
|
+
for (const [id, a] of turnApprovals) {
|
|
171
|
+
if (isMutationTool(a.toolName)) a.approved = false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return ok;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const guardedConfirm = wrapConfirmTool(confirmTool);
|
|
143
179
|
const maxDiscoveryCallsPerRound = Math.max(
|
|
144
180
|
1,
|
|
145
181
|
Number(process.env.SHMAKK_MAX_DISCOVERY_CALLS_PER_ROUND)
|
|
@@ -152,8 +188,44 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
152
188
|
const graph = relevantSubgraph(idx, input, 12, 1);
|
|
153
189
|
if (graph.length) {
|
|
154
190
|
indexHint = `\n\nCompact relevant subgraph for this task:\n${graph.map((n) => `- ${n.path} [role=${n.role}] symbols=${n.symbols.slice(0, 4).join(', ') || '-'} edges=${n.edges.slice(0, 4).join(', ') || '-'} snippet=${(n.snippet || []).slice(0, 3).join(' | ') || '-'}`).join('\n')}\nStart with these files and their immediate dependencies before broad exploration. Prefer these snippet cues before reading full files.`;
|
|
191
|
+
} else {
|
|
192
|
+
// Fallback: no query hits — give the agent a top-level map so it can
|
|
193
|
+
// start exploring without waiting for the user to say "read the dir".
|
|
194
|
+
const files = idx.files || {};
|
|
195
|
+
const configHints = [];
|
|
196
|
+
const topDirs = new Set();
|
|
197
|
+
const topFiles = new Set();
|
|
198
|
+
const allKeys = Object.keys(files);
|
|
199
|
+
// Determine which top-level names are directories vs files by checking
|
|
200
|
+
// whether they appear as a prefix for deeper entries.
|
|
201
|
+
const hasSlash = new Map(); // topName -> true if dir, false if top-level file
|
|
202
|
+
for (const rel of allKeys) {
|
|
203
|
+
const top = rel.split('/')[0];
|
|
204
|
+
if (!top || top.startsWith('.') || top === 'node_modules') continue;
|
|
205
|
+
if (rel === top) {
|
|
206
|
+
// A top-level file (no / in path) — only mark if not already known as dir
|
|
207
|
+
if (!hasSlash.has(top)) hasSlash.set(top, false);
|
|
208
|
+
} else {
|
|
209
|
+
// e.g. "src/agent.js" — top="src", this is a directory
|
|
210
|
+
hasSlash.set(top, true);
|
|
211
|
+
}
|
|
212
|
+
const base = rel.split('/').pop();
|
|
213
|
+
if (base === 'package.json' || base === 'README.md' || base === 'tsconfig.json') {
|
|
214
|
+
const f = files[rel];
|
|
215
|
+
configHints.push(`- ${rel} [role=${f.role}] snippet=${(f.snippet || []).slice(0, 1).join(' | ') || '-'}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (const [name, isDir] of hasSlash) {
|
|
219
|
+
if (isDir) topDirs.add(name); else topFiles.add(name);
|
|
220
|
+
}
|
|
221
|
+
const topLines = [];
|
|
222
|
+
if (topDirs.size) topLines.push(`Top-level dirs: ${[...topDirs].sort().join(', ')}`);
|
|
223
|
+
if (topFiles.size) topLines.push(`Top-level files: ${[...topFiles].sort().join(', ')}`);
|
|
224
|
+
indexHint = `\n\nWorkspace structure (no query hits — start by exploring relevant directories):\n${topLines.join('\n')}${configHints.length ? '\n' + configHints.join('\n') : ''}\nUse list_dir to explore further.`;
|
|
155
225
|
}
|
|
156
|
-
} catch {
|
|
226
|
+
} catch (e) {
|
|
227
|
+
indexHint = `\n\nWorkspace index unavailable (${e.message || 'unknown error'}). Start with list_dir of the root directory to discover the project structure.`;
|
|
228
|
+
}
|
|
157
229
|
|
|
158
230
|
// Build MCP tool hint for system prompt if MCP tools are available
|
|
159
231
|
let mcpToolHint = null;
|
|
@@ -219,6 +291,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
219
291
|
|
|
220
292
|
// Tool loop. Streams content as it arrives; prints each tool call.
|
|
221
293
|
let producedAnything = false;
|
|
294
|
+
const runState = { _dsmlLeakRetries: 0 };
|
|
222
295
|
for (let i = 0; i < dynamicToolBudget; i++) {
|
|
223
296
|
if (signal && signal.aborted) return messages.slice(1);
|
|
224
297
|
|
|
@@ -248,6 +321,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
248
321
|
model: modelFor('agent'),
|
|
249
322
|
messages, tools: allTools, tool_choice: toolChoiceForThisIter,
|
|
250
323
|
temperature: 0, stream: true,
|
|
324
|
+
...getDeepSeekOptions('tool_loop'),
|
|
251
325
|
}, { signal });
|
|
252
326
|
} catch (e) {
|
|
253
327
|
stop();
|
|
@@ -258,12 +332,39 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
258
332
|
let reasoningContent = '';
|
|
259
333
|
const toolCalls = []; // [{id, type:'function', function:{name, arguments}}]
|
|
260
334
|
let spinnerStopped = false;
|
|
335
|
+
let streamingContentOk = true; // flipped to false on leak
|
|
261
336
|
try {
|
|
262
337
|
for await (const chunk of stream) {
|
|
338
|
+
// Check for abort between chunks.
|
|
339
|
+
if (signal && signal.aborted) {
|
|
340
|
+
streamingContentOk = false;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
263
343
|
const delta = chunk.choices?.[0]?.delta;
|
|
264
344
|
if (!delta) continue;
|
|
265
345
|
if (delta.content) {
|
|
266
|
-
|
|
346
|
+
// ── Streaming guard: buffer tokens before flushing ──────────
|
|
347
|
+
// We never append tokens directly to visible chat state.
|
|
348
|
+
// Small lookbehind buffer so partial strings like "<||DSML"
|
|
349
|
+
// are not flushed before we can detect them.
|
|
350
|
+
const token = delta.content;
|
|
351
|
+
content += token;
|
|
352
|
+
|
|
353
|
+
// Check the trailing portion for partial DSML prefixes.
|
|
354
|
+
// Only check the last ~80 chars — enough to catch any prefix.
|
|
355
|
+
const tail = content.slice(-80);
|
|
356
|
+
if (mightBecomeInternalMarkup(tail)) {
|
|
357
|
+
// Hold back — don't flush yet, the next token may complete
|
|
358
|
+
// a benign string or reveal leaked markup.
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// If we have accumulated content and there's no dangerous
|
|
363
|
+
// prefix in the tail, flush it now.
|
|
364
|
+
if (content.length > 0 && !mightBecomeInternalMarkup(content.slice(-80))) {
|
|
365
|
+
// Nothing to flush separately here — the spinner handles
|
|
366
|
+
// the "thinking" display. We just keep accumulating.
|
|
367
|
+
}
|
|
267
368
|
}
|
|
268
369
|
if (typeof delta.reasoning_content === 'string' && delta.reasoning_content.length) {
|
|
269
370
|
reasoningContent += delta.reasoning_content;
|
|
@@ -284,6 +385,54 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
284
385
|
if (!spinnerStopped) stop();
|
|
285
386
|
}
|
|
286
387
|
|
|
388
|
+
// ── DSML leak detection (after stream completes) ────────────────────
|
|
389
|
+
if (content && isLeakedToolMarkup(content)) {
|
|
390
|
+
const sanitized = sanitizeAssistantContent(content);
|
|
391
|
+
// Log the leak but do NOT persist raw content.
|
|
392
|
+
audit.append({
|
|
393
|
+
kind: 'dsml-leak',
|
|
394
|
+
model: modelFor('agent'),
|
|
395
|
+
leakedMarkupDetected: true,
|
|
396
|
+
retried: false,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// If this is the first leak in this turn, retry once with safer settings.
|
|
400
|
+
// We inject a user message telling the model not to emit DSML and re-run
|
|
401
|
+
// the current iteration.
|
|
402
|
+
const RETRY_MAX = 1;
|
|
403
|
+
if (!runState._dsmlLeakRetries) runState._dsmlLeakRetries = 0;
|
|
404
|
+
|
|
405
|
+
if (runState._dsmlLeakRetries < RETRY_MAX) {
|
|
406
|
+
runState._dsmlLeakRetries += 1;
|
|
407
|
+
|
|
408
|
+
// Remove the last user message (the one that triggered this turn)
|
|
409
|
+
// and replace it with the retry instruction so we don't grow history.
|
|
410
|
+
// Find the last user message index.
|
|
411
|
+
let lastUserIdx = -1;
|
|
412
|
+
for (let mi = messages.length - 1; mi >= 0; mi--) {
|
|
413
|
+
if (messages[mi].role === 'user') { lastUserIdx = mi; break; }
|
|
414
|
+
}
|
|
415
|
+
if (lastUserIdx >= 0) {
|
|
416
|
+
messages.splice(lastUserIdx, 1);
|
|
417
|
+
}
|
|
418
|
+
messages.push(DSML_RETRY_USER_MESSAGE);
|
|
419
|
+
|
|
420
|
+
// Disable thinking for this retry.
|
|
421
|
+
process.env._SHMAKK_FORCE_NO_THINKING = '1';
|
|
422
|
+
i--; // re-spend this iteration
|
|
423
|
+
audit.append({ kind: 'dsml-leak-retry', model: modelFor('agent') });
|
|
424
|
+
continue; // re-enter the tool loop
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Max retries exceeded — strip and show what we can.
|
|
428
|
+
content = sanitized.visibleText;
|
|
429
|
+
if (!content) {
|
|
430
|
+
write(dim('[shmakk] response contained only leaked tool markup — blocked.', colors) + '\n');
|
|
431
|
+
clearTaskJournal(roots[0]);
|
|
432
|
+
return messages.slice(1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
287
436
|
const fallbackActions = toolCalls.length ? [] : [
|
|
288
437
|
...parseFallbackActions(content),
|
|
289
438
|
...parseXmlFallbackActions(content),
|
|
@@ -329,7 +478,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
329
478
|
// No tools → done.
|
|
330
479
|
if (!normalizedToolCalls.length) {
|
|
331
480
|
if (content) {
|
|
332
|
-
write(
|
|
481
|
+
write(renderBlock(content, { enabled: markdown, colors }));
|
|
333
482
|
if (!content.endsWith('\n')) write('\n');
|
|
334
483
|
producedAnything = true;
|
|
335
484
|
if (promptCacheEnabled && cacheKey) {
|
|
@@ -368,7 +517,7 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
368
517
|
if (canUseCache && toolResultCache.has(cacheKey)) {
|
|
369
518
|
result = toolResultCache.get(cacheKey);
|
|
370
519
|
} else {
|
|
371
|
-
result = await dispatchTool(c.function.name, args, roots,
|
|
520
|
+
result = await dispatchTool(c.function.name, args, roots, guardedConfirm, signal, mcpManager);
|
|
372
521
|
if (canUseCache && !result?.error) toolResultCache.set(cacheKey, result);
|
|
373
522
|
iterProgress = true;
|
|
374
523
|
}
|
|
@@ -419,11 +568,22 @@ async function runAgent({ input, roots, glossary, confirmTool, write, signal, hi
|
|
|
419
568
|
temperature: 0,
|
|
420
569
|
tool_choice: 'none',
|
|
421
570
|
stream: false,
|
|
571
|
+
...getDeepSeekOptions('tool_loop'),
|
|
422
572
|
}, { signal });
|
|
423
573
|
const finalText = final.choices?.[0]?.message?.content || '';
|
|
424
574
|
if (finalText) {
|
|
425
|
-
|
|
426
|
-
|
|
575
|
+
// ── DSML leak guard (same as the main loop) ──────────────────────
|
|
576
|
+
const finalSanitized = sanitizeAssistantContent(finalText);
|
|
577
|
+
const displayText = finalSanitized.hadInternalLeak
|
|
578
|
+
? finalSanitized.visibleText
|
|
579
|
+
: finalText;
|
|
580
|
+
if (displayText) {
|
|
581
|
+
write(renderBlock(displayText, { enabled: markdown, colors }));
|
|
582
|
+
if (!displayText.endsWith('\n')) write('\n');
|
|
583
|
+
}
|
|
584
|
+
if (finalSanitized.hadInternalLeak) {
|
|
585
|
+
audit.append({ kind: 'dsml-leak-finalize', model: modelFor('agent'), leakedMarkupDetected: true });
|
|
586
|
+
}
|
|
427
587
|
clearTaskJournal(roots[0]);
|
|
428
588
|
return messages.slice(1);
|
|
429
589
|
}
|
package/src/cli.js
CHANGED
|
@@ -27,7 +27,9 @@ function parseArgs(argv) {
|
|
|
27
27
|
profile: null,
|
|
28
28
|
profileSet: null,
|
|
29
29
|
colors: null,
|
|
30
|
+
markdown: null,
|
|
30
31
|
endpoint: null,
|
|
32
|
+
modelRecommendation: false,
|
|
31
33
|
voice: false,
|
|
32
34
|
stt: false,
|
|
33
35
|
tts: false,
|
|
@@ -39,6 +41,7 @@ function parseArgs(argv) {
|
|
|
39
41
|
voiceSilenceStartSec: null,
|
|
40
42
|
voicePadStartSec: null,
|
|
41
43
|
ttsVoice: null,
|
|
44
|
+
notify: false,
|
|
42
45
|
completion: null,
|
|
43
46
|
unknown: [],
|
|
44
47
|
};
|
|
@@ -93,9 +96,12 @@ function parseArgs(argv) {
|
|
|
93
96
|
case '--voice-silence-start-sec': opts.voiceSilenceStartSec = argv[++i] || null; break;
|
|
94
97
|
case '--voice-pad-start-sec': opts.voicePadStartSec = argv[++i] || null; break;
|
|
95
98
|
case '--tts-voice': opts.ttsVoice = argv[++i] || null; break;
|
|
99
|
+
case '--notify': opts.notify = true; break;
|
|
96
100
|
case '--completion': opts.completion = argv[++i] || null; break;
|
|
97
101
|
case '--colors': opts.colors = argv[++i] || null; break;
|
|
102
|
+
case '--markdown': opts.markdown = argv[++i] || null; break;
|
|
98
103
|
case '--endpoint': opts.endpoint = argv[++i] || null; break;
|
|
104
|
+
case '--model-recommendation': opts.modelRecommendation = true; break;
|
|
99
105
|
default: opts.unknown.push(a);
|
|
100
106
|
}
|
|
101
107
|
}
|
|
@@ -104,95 +110,196 @@ function parseArgs(argv) {
|
|
|
104
110
|
|
|
105
111
|
const HELP = `shmakk - AI-supervised terminal wrapper
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
shmakk --
|
|
122
|
-
|
|
123
|
-
shmakk --
|
|
124
|
-
shmakk --
|
|
125
|
-
shmakk --
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
--
|
|
143
|
-
--
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
--
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
113
|
+
Launch shmakk, then type commands as usual. shmakk watches the shell, catches
|
|
114
|
+
failures, and calls an LLM to fix them, plan tasks, and edit files.
|
|
115
|
+
|
|
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
|
+
Type "help" inside a session to see this same text.
|
|
120
|
+
|
|
121
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
122
|
+
LAUNCH OPTIONS
|
|
123
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
124
|
+
|
|
125
|
+
shmakk Launch in auto mode (AI acts on failures)
|
|
126
|
+
shmakk --review Launch in review mode (confirm every AI action)
|
|
127
|
+
shmakk --yes-files Auto-accept file writes, edits, mkdir
|
|
128
|
+
|
|
129
|
+
shmakk --help Show this help
|
|
130
|
+
shmakk --build-history [files] Parse shell history for better corrections
|
|
131
|
+
shmakk --update-command-glossary Scan PATH and build local command glossary
|
|
132
|
+
|
|
133
|
+
--no-ai Disable AI entirely (pure passthrough)
|
|
134
|
+
--no-correction Disable command correction
|
|
135
|
+
--debug Verbose logging to stderr
|
|
136
|
+
--print-config Print resolved configuration and exit
|
|
137
|
+
|
|
138
|
+
--workspace <path> Override workspace root
|
|
139
|
+
--profile <name> Startup profile: tiny|balanced|deep|builder|large-app
|
|
140
|
+
--colors <true|false> Enable or disable ANSI colors
|
|
141
|
+
--markdown <true|false> Enable or disable markdown rendering
|
|
142
|
+
--notify Desktop notifications for Y/n prompts
|
|
143
|
+
|
|
144
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
145
|
+
MODEL PROVIDERS
|
|
146
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
147
|
+
|
|
148
|
+
--endpoint <name> Use model preset from ~/.config/shmakk/endpoints.json
|
|
149
|
+
--model-recommendation Main model chooses best model per call
|
|
150
|
+
|
|
151
|
+
Providers: openai-compatible | codex | anthropic | google
|
|
152
|
+
Configure in ~/.config/shmakk/endpoints.json:
|
|
153
|
+
{
|
|
154
|
+
"main": "claude",
|
|
155
|
+
"models": {
|
|
156
|
+
"claude": { "provider":"anthropic", "model":"claude-sonnet-4-5-...", "api_key":"..." },
|
|
157
|
+
"gpt5": { "provider":"codex", "model":"gpt-5-codex", "api_key":"..." },
|
|
158
|
+
"local-qwen": { "provider":"openai-compatible", "base_url":"http://127.0.0.1:1234/v1",
|
|
159
|
+
"model":"qwen/qwen3.5-9b" }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
164
|
+
SESSION CONTROL (shmakk --flag from another terminal)
|
|
165
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
166
|
+
|
|
167
|
+
shmakk --status Is this terminal inside shmakk?
|
|
168
|
+
shmakk --stats Session/task stats (journal, audit, skill)
|
|
169
|
+
shmakk --show-plan Current plan: tasks and progress
|
|
170
|
+
shmakk --resume-status Task journal summary for continuity
|
|
171
|
+
shmakk --mcp-status MCP servers and their tools
|
|
172
|
+
|
|
173
|
+
shmakk --compact Clear conversation + task journal
|
|
174
|
+
shmakk --reset Clear AI conversation history (keep session)
|
|
175
|
+
shmakk --restart Restart the inner shell (keeps window)
|
|
176
|
+
shmakk --exit Cleanly exit the parent shmakk
|
|
177
|
+
|
|
178
|
+
shmakk --profile-set <name> Switch profile and restart
|
|
179
|
+
|
|
180
|
+
shmakk --load-skill <name> Load a skill into workspace state
|
|
181
|
+
shmakk --install-skill <url> Download skill markdown from URL, validate, load
|
|
182
|
+
shmakk -G, --global Use global (~/.config/shmakk) with --load-skill / --install-skill
|
|
183
|
+
shmakk --list-skills List all registered skills (workspace + global)
|
|
184
|
+
shmakk --skill-status Active skill and registry status
|
|
185
|
+
shmakk --unload-skill <name> Remove skill from whichever registry has it
|
|
186
|
+
|
|
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
|
|
167
199
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
251
|
+
VOICE (Speech-to-Text / Text-to-Speech)
|
|
252
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
253
|
+
|
|
254
|
+
--sts Speech-to-Speech: always-on mic + TTS
|
|
255
|
+
--stt Speech-to-Text: mic input, text output
|
|
256
|
+
--tts Text-to-Speech: text input, spoken output
|
|
257
|
+
|
|
258
|
+
--voice-language <code> Language hint (e.g. en, es, fr)
|
|
259
|
+
--voice-max-sec <sec> Max recording seconds (default: 30)
|
|
260
|
+
--voice-silence-sec <sec> Silence before stopping (default: 1.0)
|
|
261
|
+
--voice-silence-threshold <%> VAD amplitude threshold (default: 1%)
|
|
262
|
+
--voice-silence-start-sec <sec> Sound required before start (default: 0.5)
|
|
263
|
+
--voice-pad-start-sec <sec> Padding at start of recording (default: 0.3)
|
|
264
|
+
--tts-voice <name> Override daily voice rotation
|
|
265
|
+
|
|
266
|
+
STT: Whisper-base ONNX in-process. No Python, no server, no API key.
|
|
267
|
+
TTS: kokoro-js (Kokoro-82M ONNX, ~334MB fp16). Auto-download on first use.
|
|
268
|
+
Requires aplay, paplay, or afplay for audio. 28 voices, rotated daily.
|
|
269
|
+
|
|
270
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
271
|
+
ENVIRONMENT
|
|
272
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
273
|
+
|
|
274
|
+
SHMAKK_BASE_URL OpenAI-compatible base URL
|
|
275
|
+
SHMAKK_API_KEY API key
|
|
276
|
+
SHMAKK_MODEL Default model
|
|
277
|
+
SHMAKK_PROVIDER Provider: openai-compatible|codex|anthropic|google
|
|
278
|
+
SHMAKK_HEADERS Extra headers: k=v,k=v
|
|
279
|
+
SHMAKK_REGISTRY Model registry filter (comma-separated)
|
|
280
|
+
SHMAKK_MODEL_RECOMMENDATION Set to 1 to let main model choose per call
|
|
281
|
+
|
|
282
|
+
SHMAKK_HF_CACHE HuggingFace cache directory (voice models)
|
|
283
|
+
SHMAKK_TTS_VOICE Pin a specific TTS voice
|
|
284
|
+
SHMAKK_TTS_DTYPE Kokoro dtype: fp32|fp16|q8|q4|q4f16 (default: fp16)
|
|
285
|
+
SHMAKK_VOICE_LANGUAGE Language hint for STT
|
|
286
|
+
SHMAKK_VOICE_MAX_SEC Max recording seconds
|
|
287
|
+
SHMAKK_VOICE_SILENCE_SEC VAD silence threshold seconds
|
|
288
|
+
SHMAKK_VOICE_SILENCE_THRESHOLD VAD amplitude threshold
|
|
289
|
+
SHMAKK_VOICE_PAD_START_SEC Start-of-recording padding
|
|
290
|
+
|
|
291
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
292
|
+
MCP & BROWSER
|
|
293
|
+
═══════════════════════════════════════════════════════════════════════════
|
|
294
|
+
|
|
295
|
+
MCP servers: configure in ~/.config/shmakk/mcp.json or .shmakk/mcp.json
|
|
171
296
|
{ "mcpServers": { "name": { "command": "...", "args": [...] } } }
|
|
172
297
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
All 28 Kokoro voices rotate automatically on a daily schedule.
|
|
179
|
-
|
|
180
|
-
Voice environment:
|
|
181
|
-
SHMAKK_HF_CACHE HuggingFace cache directory override
|
|
182
|
-
SHMAKK_TTS_VOICE Pin a specific TTS voice (default: auto-rotated)
|
|
183
|
-
SHMAKK_TTS_DTYPE Kokoro dtype: fp32, fp16, q8, q4, q4f16 (default: fp16)
|
|
184
|
-
SHMAKK_VOICE_LANGUAGE Language hint for STT (e.g., en, es, fr)
|
|
185
|
-
SHMAKK_VOICE_MAX_SEC Max recording seconds (default: 30)
|
|
186
|
-
SHMAKK_VOICE_SILENCE_SEC VAD silence threshold seconds (default: 1.0)
|
|
187
|
-
SHMAKK_VOICE_SILENCE_THRESHOLD VAD amplitude threshold (default: 1%)
|
|
188
|
-
SHMAKK_VOICE_PAD_START_SEC Padding added to start of recording (default: 0.3)
|
|
189
|
-
|
|
190
|
-
Environment:
|
|
191
|
-
SHMAKK_BASE_URL OpenAI-compatible base URL
|
|
192
|
-
SHMAKK_API_KEY API key
|
|
193
|
-
SHMAKK_MODEL Default model
|
|
194
|
-
SHMAKK_HEADERS Comma-separated extra headers (k=v,k=v)
|
|
195
|
-
SHMAKK_REGISTRY Comma-separated model registry filter (for makkorch)
|
|
298
|
+
Browser automation: requires playwright
|
|
299
|
+
npm install playwright && npx playwright install chromium
|
|
300
|
+
Tools: navigate, click, type, read_page, screenshot, evaluate, select,
|
|
301
|
+
wait, scroll, close.
|
|
302
|
+
|
|
196
303
|
`;
|
|
197
304
|
|
|
198
305
|
module.exports = { parseArgs, HELP };
|
package/src/completions.js
CHANGED
|
@@ -33,12 +33,13 @@ const FLAGS = [
|
|
|
33
33
|
{ flag: '--tts', arg: false, desc: 'Text-to-Speech: spoken responses' },
|
|
34
34
|
{ flag: '--sts', arg: false, desc: 'Speech-to-Speech: always-on mic + TTS' },
|
|
35
35
|
{ flag: '--voice', arg: false, desc: 'Enable voice input (stt shortcut)' },
|
|
36
|
+
{ flag: '--model-recommendation', arg: false, desc: 'Route each model call via main model recommendation' },
|
|
36
37
|
|
|
37
38
|
// flags with arguments
|
|
38
39
|
{ flag: '--workspace', arg: '<path>', desc: 'Override workspace root' },
|
|
39
40
|
{ flag: '--profile', arg: '<name>', desc: 'Startup profile (tiny|balanced|deep|builder|large-app)' },
|
|
40
41
|
{ flag: '--profile-set', arg: '<name>', desc: 'Switch profile and restart' },
|
|
41
|
-
{ flag: '--endpoint', arg: '<name>', desc: 'Use
|
|
42
|
+
{ flag: '--endpoint', arg: '<name>', desc: 'Use model preset from ~/.config/shmakk/endpoints.json' },
|
|
42
43
|
{ flag: '--colors', arg: '<true|false>', desc: 'Toggle ANSI colors' },
|
|
43
44
|
{ flag: '--load-skill', arg: '<name>', desc: 'Load a skill into workspace state' },
|
|
44
45
|
{ flag: '--unload-skill', arg: '<name>', desc: 'Remove skill from registry' },
|
|
@@ -53,6 +54,7 @@ const FLAGS = [
|
|
|
53
54
|
{ flag: '--voice-silence-start-sec', arg: '<sec>', desc: 'Sound before recording starts' },
|
|
54
55
|
{ flag: '--voice-pad-start-sec', arg: '<sec>', desc: 'Padding before recording' },
|
|
55
56
|
{ flag: '--tts-voice', arg: '<name>', desc: 'Override Kokoro voice' },
|
|
57
|
+
{ flag: '--notify', arg: false, desc: 'Send desktop notifications when shmakk needs your attention' },
|
|
56
58
|
];
|
|
57
59
|
|
|
58
60
|
function bash() {
|