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/README.md
CHANGED
|
@@ -33,10 +33,34 @@ export SHMAKK_API_KEY="your-api-key"
|
|
|
33
33
|
export SHMAKK_MODEL="gpt-4o-mini"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Or
|
|
36
|
+
Or configure multiple native model providers in `~/.config/shmakk/endpoints.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"main": "gpt5-codex",
|
|
41
|
+
"models": {
|
|
42
|
+
"gpt5-codex": {
|
|
43
|
+
"provider": "codex",
|
|
44
|
+
"model": "gpt-5-codex",
|
|
45
|
+
"api_key": "OPENAI_API_KEY"
|
|
46
|
+
},
|
|
47
|
+
"local": {
|
|
48
|
+
"provider": "openai-compatible",
|
|
49
|
+
"base_url": "http://127.0.0.1:1234/v1",
|
|
50
|
+
"model": "qwen/qwen3.5-9b"
|
|
51
|
+
},
|
|
52
|
+
"claude": {
|
|
53
|
+
"provider": "anthropic",
|
|
54
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
55
|
+
"api_key": "ANTHROPIC_API_KEY"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
37
60
|
|
|
38
61
|
```bash
|
|
39
|
-
|
|
62
|
+
shmakk --endpoint claude
|
|
63
|
+
shmakk --model-recommendation
|
|
40
64
|
```
|
|
41
65
|
|
|
42
66
|
### 2. Launch
|
|
@@ -114,7 +138,9 @@ The coordinator system enables complex, multi-step task execution with plan-firs
|
|
|
114
138
|
| `SHMAKK_BASE_URL` | OpenAI-compatible base URL |
|
|
115
139
|
| `SHMAKK_API_KEY` | API key |
|
|
116
140
|
| `SHMAKK_MODEL` | Default model |
|
|
141
|
+
| `SHMAKK_PROVIDER` | `openai-compatible`, `codex`, or `anthropic` |
|
|
117
142
|
| `SHMAKK_HEADERS` | Extra headers (k=v,k=v) |
|
|
143
|
+
| `SHMAKK_MODEL_RECOMMENDATION` | Set to `1` to let the configured `main` model route each call |
|
|
118
144
|
|
|
119
145
|
## Useful commands
|
|
120
146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shmakk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "AI-supervised terminal wrapper — command correction, tool-driven tasks, safety controls",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=18"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"node-pty": "^1.0.
|
|
51
|
+
"@lydell/node-pty": "^1.2.0-beta.12",
|
|
52
52
|
"openai": "^4.77.0",
|
|
53
53
|
"wavefile": "^11.0.0"
|
|
54
54
|
},
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
shmakk demo recorder — uses Playwright to drive the demo HTML page
|
|
4
|
+
while ffmpeg captures the Xvfb display.
|
|
5
|
+
|
|
6
|
+
Usage: python3 scripts/demo/record.py [--out onlymakk.mp4]
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import subprocess, sys, os, time, argparse, shutil, shlex
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
13
|
+
SCENES_HTML = Path(__file__).resolve().parent / "scenes.html"
|
|
14
|
+
|
|
15
|
+
FPS = 30
|
|
16
|
+
WIDTH = 1920
|
|
17
|
+
HEIGHT = 1080
|
|
18
|
+
DISPLAY = ":99"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run(cmd, timeout=120, check=True):
|
|
22
|
+
if isinstance(cmd, str):
|
|
23
|
+
print(f" $ {cmd[:120]}")
|
|
24
|
+
else:
|
|
25
|
+
print(f" $ {' '.join(shlex.quote(str(x)) for x in cmd)[:120]}")
|
|
26
|
+
return subprocess.run(cmd, shell=isinstance(cmd, str),
|
|
27
|
+
capture_output=True, text=True,
|
|
28
|
+
timeout=timeout, check=check)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_xvfb_running():
|
|
32
|
+
r = subprocess.run(["pgrep", "-f", f"Xvfb {DISPLAY}"], capture_output=True)
|
|
33
|
+
return r.returncode == 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def start_xvfb():
|
|
37
|
+
if is_xvfb_running():
|
|
38
|
+
print(f"Xvfb already running on {DISPLAY}")
|
|
39
|
+
return
|
|
40
|
+
print(f"Starting Xvfb on {DISPLAY}...")
|
|
41
|
+
subprocess.Popen(["Xvfb", DISPLAY, "-screen", "0", f"{WIDTH}x{HEIGHT}x24",
|
|
42
|
+
"-ac", "+extension", "RANDR"],
|
|
43
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
44
|
+
time.sleep(1.5)
|
|
45
|
+
if not is_xvfb_running():
|
|
46
|
+
print("ERROR: Xvfb failed to start")
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
print("Xvfb started.")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def record(out_path: Path):
|
|
52
|
+
tmp_dir = ROOT / "tmp" / "demo"
|
|
53
|
+
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
raw_mp4 = tmp_dir / "raw.mp4"
|
|
55
|
+
|
|
56
|
+
# Clean up previous raw files
|
|
57
|
+
for f in list(tmp_dir.glob("*.webm")) + list(tmp_dir.glob("*.mp4")):
|
|
58
|
+
if f.name != "onlymakk.mp4":
|
|
59
|
+
f.unlink(missing_ok=True)
|
|
60
|
+
|
|
61
|
+
start_xvfb()
|
|
62
|
+
|
|
63
|
+
env = os.environ.copy()
|
|
64
|
+
env["DISPLAY"] = DISPLAY
|
|
65
|
+
|
|
66
|
+
scenes_url = SCENES_HTML.as_uri()
|
|
67
|
+
|
|
68
|
+
print(f"Source: {scenes_url}")
|
|
69
|
+
print(f"Output: {out_path}")
|
|
70
|
+
|
|
71
|
+
# Start ffmpeg recording in the background
|
|
72
|
+
ffmpeg_cmd = [
|
|
73
|
+
"ffmpeg", "-y",
|
|
74
|
+
"-f", "x11grab",
|
|
75
|
+
"-framerate", str(FPS),
|
|
76
|
+
"-video_size", f"{WIDTH}x{HEIGHT}",
|
|
77
|
+
"-i", f"{DISPLAY}+0,0",
|
|
78
|
+
"-c:v", "libx264",
|
|
79
|
+
"-preset", "ultrafast",
|
|
80
|
+
"-crf", "18",
|
|
81
|
+
"-pix_fmt", "yuv420p",
|
|
82
|
+
str(raw_mp4)
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
print("Starting ffmpeg capture...")
|
|
86
|
+
ffmpeg_proc = subprocess.Popen(ffmpeg_cmd, env=env,
|
|
87
|
+
stdout=subprocess.DEVNULL,
|
|
88
|
+
stderr=subprocess.DEVNULL)
|
|
89
|
+
time.sleep(1) # let ffmpeg initialize
|
|
90
|
+
|
|
91
|
+
if ffmpeg_proc.poll() is not None:
|
|
92
|
+
print("ERROR: ffmpeg exited immediately")
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
# Now run Playwright to drive the demo
|
|
96
|
+
driver_js = f"""
|
|
97
|
+
const {{ chromium }} = require('playwright');
|
|
98
|
+
|
|
99
|
+
(async () => {{
|
|
100
|
+
const browser = await chromium.launch({{
|
|
101
|
+
headless: false,
|
|
102
|
+
args: ['--no-sandbox', '--disable-gpu', '--disable-setuid-sandbox',
|
|
103
|
+
'--window-size={WIDTH},{HEIGHT}', '--window-position=0,0']
|
|
104
|
+
}});
|
|
105
|
+
|
|
106
|
+
// Make the browser window fill the screen
|
|
107
|
+
const context = await browser.newContext({{
|
|
108
|
+
viewport: {{ width: {WIDTH}, height: {HEIGHT} }},
|
|
109
|
+
deviceScaleFactor: 1,
|
|
110
|
+
}});
|
|
111
|
+
const page = await context.newPage();
|
|
112
|
+
|
|
113
|
+
await page.goto('{scenes_url}', {{ waitUntil: 'networkidle' }});
|
|
114
|
+
|
|
115
|
+
// Wait for DONE signal or timeout
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
const maxDuration = 120_000;
|
|
118
|
+
while (true) {{
|
|
119
|
+
const title = await page.title();
|
|
120
|
+
if (title === 'DONE' || (Date.now() - startTime) > maxDuration) break;
|
|
121
|
+
await new Promise(r => setTimeout(r, 500));
|
|
122
|
+
}}
|
|
123
|
+
|
|
124
|
+
console.log('Demo finished. Title:', await page.title());
|
|
125
|
+
console.log('Elapsed:', (Date.now() - startTime) / 1000, 's');
|
|
126
|
+
|
|
127
|
+
// Hold the last frame briefly
|
|
128
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
129
|
+
|
|
130
|
+
await browser.close();
|
|
131
|
+
console.log('Browser closed.');
|
|
132
|
+
}})();
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
driver_script = tmp_dir / "driver.js"
|
|
136
|
+
driver_script.write_text(driver_js)
|
|
137
|
+
|
|
138
|
+
print("Launching demo driver...")
|
|
139
|
+
result = subprocess.run(
|
|
140
|
+
["node", str(driver_script)],
|
|
141
|
+
env=env,
|
|
142
|
+
capture_output=True, text=True,
|
|
143
|
+
timeout=150,
|
|
144
|
+
cwd=str(ROOT)
|
|
145
|
+
)
|
|
146
|
+
print(result.stdout)
|
|
147
|
+
if result.returncode != 0:
|
|
148
|
+
print("STDERR:", result.stderr)
|
|
149
|
+
ffmpeg_proc.terminate()
|
|
150
|
+
ffmpeg_proc.wait()
|
|
151
|
+
sys.exit(1)
|
|
152
|
+
|
|
153
|
+
# Give ffmpeg time to flush
|
|
154
|
+
time.sleep(2)
|
|
155
|
+
ffmpeg_proc.terminate()
|
|
156
|
+
try:
|
|
157
|
+
ffmpeg_proc.wait(timeout=10)
|
|
158
|
+
except subprocess.TimeoutExpired:
|
|
159
|
+
ffmpeg_proc.kill()
|
|
160
|
+
ffmpeg_proc.wait()
|
|
161
|
+
|
|
162
|
+
print(f"Raw capture: {raw_mp4.stat().st_size / 1024 / 1024:.1f} MB")
|
|
163
|
+
|
|
164
|
+
# Re-encode for optimal size and faststart
|
|
165
|
+
final_tmp = tmp_dir / "final.mp4"
|
|
166
|
+
run([
|
|
167
|
+
"ffmpeg", "-y",
|
|
168
|
+
"-i", str(raw_mp4),
|
|
169
|
+
"-c:v", "libx264",
|
|
170
|
+
"-preset", "medium",
|
|
171
|
+
"-crf", "20",
|
|
172
|
+
"-pix_fmt", "yuv420p",
|
|
173
|
+
"-movflags", "+faststart",
|
|
174
|
+
str(final_tmp)
|
|
175
|
+
], timeout=300)
|
|
176
|
+
|
|
177
|
+
# Move to final destination
|
|
178
|
+
shutil.move(str(final_tmp), str(out_path))
|
|
179
|
+
|
|
180
|
+
# Cleanup
|
|
181
|
+
driver_script.unlink(missing_ok=True)
|
|
182
|
+
raw_mp4.unlink(missing_ok=True)
|
|
183
|
+
|
|
184
|
+
print(f"\nDone! -> {out_path} ({out_path.stat().st_size / 1024 / 1024:.1f} MB)")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def main():
|
|
188
|
+
parser = argparse.ArgumentParser(description="Record shmakk product demo")
|
|
189
|
+
parser.add_argument("--out", default=str(ROOT / "onlymakk.mp4"),
|
|
190
|
+
help="Output MP4 path")
|
|
191
|
+
args = parser.parse_args()
|
|
192
|
+
record(Path(args.out))
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if __name__ == "__main__":
|
|
196
|
+
main()
|