shmakk 1.2.3 → 1.2.5

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 (52) hide show
  1. package/.env.example +11 -0
  2. package/README.md +75 -1
  3. package/docs/index.html +154 -16
  4. package/docs/mcp.md +78 -0
  5. package/docs/ssh.md +82 -0
  6. package/docs/vibedit-analysis.md +375 -0
  7. package/docs/vim.md +110 -0
  8. package/docs/voice.md +4 -0
  9. package/package.json +9 -5
  10. package/scripts/test-vibedit.js +45 -0
  11. package/scripts/vibedit-demo.sh +52 -0
  12. package/skills/shmakk-skill-creator.md +269 -0
  13. package/src/_check.js +7 -0
  14. package/src/_check_schema.js +5 -0
  15. package/src/_cleanup.js +18 -0
  16. package/src/_fix.js +9 -0
  17. package/src/_test_import.js +15 -0
  18. package/src/agent.js +11 -4
  19. package/src/browser-daemon.js +209 -0
  20. package/src/browser.js +10 -0
  21. package/src/cli/browserDaemon.js +60 -0
  22. package/src/cli/connectBrowser.js +137 -0
  23. package/src/cli.js +235 -8
  24. package/src/completions.js +8 -0
  25. package/src/control.js +273 -1
  26. package/src/core/browserConnector.js +523 -0
  27. package/src/correction.js +6 -0
  28. package/src/electron.js +305 -0
  29. package/src/endpoints.js +74 -9
  30. package/src/index.js +24 -1
  31. package/src/llm.js +501 -61
  32. package/src/mobile.js +307 -0
  33. package/src/notify.js +51 -3
  34. package/src/orchestrator.js +35 -1
  35. package/src/pty.js +11 -6
  36. package/src/review.js +45 -11
  37. package/src/self-commands.js +153 -0
  38. package/src/session-convert.js +508 -0
  39. package/src/session-search.js +31 -0
  40. package/src/session.js +392 -46
  41. package/src/skills/browserActions.ts +984 -0
  42. package/src/skills.js +451 -24
  43. package/src/system-prompt.js +31 -25
  44. package/src/tools.js +81 -0
  45. package/src/vibedit/control.js +534 -0
  46. package/src/vibedit/electron.js +108 -0
  47. package/src/vibedit/files.js +171 -0
  48. package/src/vibedit/index.js +298 -0
  49. package/src/vibedit/overlay.js +1482 -0
  50. package/src/vibedit/prompts.js +245 -0
  51. package/src/vibedit/state.js +32 -0
  52. package/src/vim.js +410 -0
@@ -0,0 +1,245 @@
1
+ // Prompts for the vibedit overlay chat, save, and flow operations.
2
+ // Kept short and rigid for compatibility with smaller models.
3
+
4
+ function chatSystem() {
5
+ return `You are a frontend editing assistant embedded in a live web page.
6
+ You receive the page DOM (pruned) and a user request. You answer briefly and,
7
+ when the user asks for a visible change, you return DOM operations.
8
+
9
+ If you also receive recorded user interactions (click/scroll/input events with timestamps), use the recorded selectors as the best source of truth for what elements the user interacted with.
10
+
11
+ YOU MUST RESPOND WITH ONLY A RAW JSON OBJECT. No markdown. No backticks. No preamble. No "Here you go". NOTHING except the JSON object.
12
+
13
+ {
14
+ "reply": "one or two short sentences for the user",
15
+ "ops": [
16
+ { "selector": "css selector", "action": "setText", "value": "new text" },
17
+ { "selector": "css selector", "action": "setStyle", "style": { "color": "#ff0000" } },
18
+ { "selector": "css selector", "action": "setHTML", "value": "<b>html</b>" },
19
+ { "selector": "css selector", "action": "setAttr", "name": "src", "value": "..." },
20
+ { "selector": "css selector", "action": "remove" }
21
+ ]
22
+ }
23
+ Rules:
24
+ - Use selectors that exist in the provided DOM. Prefer ids, then stable class names.
25
+ - If recorded interactions are provided, prefer selectors from the recorded events.
26
+ - If no visual change is requested, return "ops": [].
27
+ - Never invent selectors. If unsure, say so in "reply" and return no ops.`;
28
+ }
29
+
30
+ function chatUser(msg) {
31
+ const sel = msg.selected ? `\nCurrently selected element:\n${msg.selected.slice(0, 1500)}\n` : "";
32
+ return `Page URL: ${msg.url}
33
+ Page title: ${msg.title}
34
+ ${sel}
35
+ Pruned DOM:
36
+ ${(msg.dom || "").slice(0, 9000)}
37
+
38
+ User request: ${msg.text}`;
39
+ }
40
+
41
+ // ── Save = translate visual DOM diffs into a task for shmakk PM ─────
42
+ // vibedit acts as a translation layer: it captures WHAT the user changed
43
+ // visually and maps changes to source files. shmakk PM handles HOW to
44
+ // implement (all code edits, backend, architecture, etc.).
45
+ //
46
+ // The prompt is kept minimal so even small/fast models produce useful output.
47
+
48
+ function saveSystem() {
49
+ return `You translate visual DOM changes the user made in a browser into a task
50
+ specification for shmakk PM, which will then implement the changes.
51
+
52
+ You are given:
53
+ 1. BEFORE → AFTER DOM diffs (the user's live visual edits on the page)
54
+ 2. A shortlist of candidate source files from the project
55
+
56
+ Your output must describe WHAT changed, mapped to WHICH files. Do NOT write
57
+ code, do NOT design the implementation, do NOT invent files. That is shmakk
58
+ PM's job. You are a translator, not an engineer.
59
+
60
+ Output ONLY a raw JSON object. No markdown, no backticks, no preamble.
61
+
62
+ {
63
+ "summary": "One sentence describing what the user changed",
64
+ "files": [
65
+ {
66
+ "path": "exact/relative/path/from/candidate/list",
67
+ "whatChanged": "describe what needs to happen in this file to produce the visual changes seen"
68
+ }
69
+ ],
70
+ "backendHints": "if the change likely needs server/database work say so briefly, otherwise empty string"
71
+ }
72
+
73
+ RULES:
74
+ - path MUST be an exact path from the provided candidate file list. Copy it literally.
75
+ - If no candidate file matches a change, do NOT invent a file. Mention it only in backendHints.
76
+ - whatChanged must be specific: mention element text, CSS property names, layout changes, etc.
77
+ - For text edits, include the exact old text and the exact replacement text. Never say only "replace the text" or "update the copy".
78
+ - For inserted elements, include the exact inserted element/content.
79
+ - Do not say "update the styling". Say "change .header background from #fff to #000".
80
+ - Keep every whatChanged under 200 characters.
81
+ - Output empty "files: []" array only if truly no file changes are needed.
82
+ - NEVER suggest removing or deleting code, elements, styles, or files unless the user explicitly asked to remove something. Just because a piece of the page is not visible in the provided DOM snapshot does NOT mean it was deleted. The DOM is pruned; missing elements were simply not captured. If you are unsure whether something should be removed, do NOT suggest removal.`;
83
+ }
84
+
85
+ function saveUser(msg, shortlist) {
86
+ const changes = msg.changes.map((c, i) => {
87
+ const before = (c.before || "").slice(0, 1200);
88
+ const after = c.after === "" ? "(deleted)" : (c.after || "").slice(0, 1200);
89
+ if (c.kind === "css") {
90
+ return `CHANGE ${i + 1} | CSS | ${c.selector}\n Existing rules: ${before}\n User changed to: ${after}`;
91
+ }
92
+ const textLine = c.beforeText || c.afterText
93
+ ? `\n TEXT BEFORE: ${c.beforeText || "(empty)"}\n TEXT AFTER: ${c.afterText || "(empty)"}`
94
+ : "";
95
+ const addLine = c.addedHTML ? `\n INSERTED HTML: ${String(c.addedHTML).slice(0, 1200)}` : "";
96
+ return `CHANGE ${i + 1} | ${c.kind || "dom"} | ${c.selector}${c.added ? " | inserted element" : ""}\n BEFORE: ${before}\n AFTER: ${after}${textLine}${addLine}`;
97
+ }).join("\n\n");
98
+
99
+ const fileList = shortlist.map((f) => ` ${f.path}`).join("\n");
100
+ const fileContents = shortlist.map((f) =>
101
+ `=== FILE: ${f.path}${f.truncated ? " (truncated)" : ""} ===\n${f.content.slice(0, 4000)}`
102
+ ).join("\n\n");
103
+
104
+ return `URL: ${msg.url}
105
+
106
+ VISUAL CHANGES the user made (BEFORE → AFTER):
107
+ ${changes}
108
+
109
+ CANDIDATE FILES (paths you may reference; do not invent paths):
110
+ ${fileList}
111
+
112
+ FILE CONTENTS for context:
113
+ ${fileContents}
114
+
115
+ IMPORTANT: The DOM snapshot is pruned, not complete. Missing elements are NOT deleted. Never suggest removing code, elements, or styles unless the user explicitly asked to remove something.
116
+
117
+ Produce the JSON spec. Use ONLY paths from the candidate list above.`;
118
+ }
119
+
120
+ function flowUser(msg, events, shortlist) {
121
+ const evLines = events
122
+ .filter((e) => e.kind !== "shot")
123
+ .slice(0, 80)
124
+ .map((e) => {
125
+ if (e.kind === "click") return `[${(e.t / 1000).toFixed(1)}s] click ${e.selector} "${(e.text || "").slice(0, 60)}"`;
126
+ if (e.kind === "scroll") return `[${(e.t / 1000).toFixed(1)}s] scroll to y=${e.y}`;
127
+ if (e.kind === "input") return `[${(e.t / 1000).toFixed(1)}s] typed in ${e.selector}`;
128
+ if (e.kind === "nav") return `[${(e.t / 1000).toFixed(1)}s] navigated to ${e.url}`;
129
+ return `[${(e.t / 1000).toFixed(1)}s] ${e.kind}`;
130
+ }).join("\n");
131
+
132
+ const fileList = shortlist.map((f) => ` ${f.path}`).join("\n");
133
+ const fileContents = shortlist.map((f) =>
134
+ `=== FILE: ${f.path}${f.truncated ? " (truncated)" : ""} ===\n${f.content.slice(0, 4000)}`
135
+ ).join("\n\n");
136
+
137
+ return `The user recorded this interaction flow on the page:
138
+
139
+ ${evLines}
140
+
141
+ Pruned DOM at the end of the recording:
142
+ ${(msg.dom || "").slice(0, 6000)}
143
+
144
+ The user wants the flow changed like this:
145
+ "${msg.instruction}"
146
+
147
+ CANDIDATE FILES (paths you may reference; do not invent paths):
148
+ ${fileList}
149
+
150
+ FILE CONTENTS for context:
151
+ ${fileContents}
152
+
153
+ IMPORTANT: The DOM snapshot is pruned, not complete. Missing elements are NOT deleted. Never suggest removing code, elements, or styles unless the user explicitly asked to remove something.
154
+
155
+ Produce the JSON spec. Use ONLY paths from the candidate list above.`;
156
+ }
157
+
158
+ // ── Automation mode: realtime in-page execution ─────────────────────
159
+ // User types natural-language instructions. The LLM produces actions
160
+ // that execute IMMEDIATELY in the user's current browser tab.
161
+ // A reusable Playwright script is only generated when explicitly requested.
162
+
163
+ function automationSystem() {
164
+ return `You are a browser automation executor. You control the user's CURRENT browser tab IN REALTIME. There is NO separate browser. You run actions directly in the page the user is looking at.
165
+
166
+ The user describes what they want automated in natural language. You EXECUTE IT NOW by producing an "actions" array and a reusable Playwright script. Each action runs sequentially in the current tab with visual highlighting so the user can watch.
167
+
168
+ YOU MUST RESPOND WITH ONLY A RAW JSON OBJECT. No markdown. No backticks. No preamble.
169
+
170
+ {
171
+ "summary": "one sentence describing what this automation does",
172
+ "actions": [
173
+ { "action": "click", "selector": ".button", "description": "click the login button" },
174
+ { "action": "type", "selector": "input[name=email]", "value": "user@example.com", "delay": 40, "description": "type email" },
175
+ { "action": "press", "selector": "input", "key": "Enter", "description": "press enter to submit" }
176
+ ],
177
+ "script": "// Playwright script that does the same thing\nconst { chromium } = require('playwright');\n(async () => {\n const browser = await chromium.launch();\n const page = await browser.newPage();\n // ...\n await browser.close();\n})();",
178
+ "notes": "any caveats or things the user should know"
179
+ }
180
+
181
+ Action types (all execute in the current tab):
182
+ - "click": { "action": "click", "selector": "css-selector", "description": "..." }
183
+ - "type": { "action": "type", "selector": "css-selector", "value": "text to type", "delay": 40, "description": "..." } -- delay is ms between keystrokes (optional, default 40)
184
+ - "press": { "action": "press", "selector": "css-selector", "key": "Enter", "description": "..." } -- key can be Enter, Tab, Escape, Backspace, ArrowDown, etc.
185
+ - "hover": { "action": "hover", "selector": "css-selector", "description": "..." }
186
+ - "select": { "action": "select", "selector": "css-selector", "value": "option-value-or-label", "description": "..." }
187
+ - "scroll": { "action": "scroll", "to": "bottom"|"top", "description": "..." }
188
+ - "scroll": { "action": "scroll", "selector": "css-selector", "description": "..." } -- scroll element into view
189
+ - "wait": { "action": "wait", "ms": 1500, "description": "..." }
190
+ - "navigate": { "action": "navigate", "url": "https://...", "description": "..." } -- ONLY if user needs a different URL
191
+ - "waitSelector": { "action": "waitSelector", "selector": "css-selector", "description": "..." } -- wait for element to appear
192
+
193
+ CRITICAL RULES:
194
+ - YOUR ONLY JOB IS TO PRODUCE THE "actions" ARRAY AND A "script". Nothing else matters.
195
+ - Every selector in actions MUST exist verbatim in the pruned DOM provided below. Copy-paste the exact attribute values you see in the DOM. NEVER invent selectors.
196
+ - If you cannot find a suitable selector in the DOM for a requested action, SKIP that action and explain what was missing in "notes". Do NOT guess or fabricate selectors.
197
+ - NEVER use selectors containing "vibedit", "#__vibedit_host", "#vibedit", or any vibedit panel class names. The automation target is the PAGE under the overlay, not the editing toolbar.
198
+ - Prefer: id > data-testid > aria-label > placeholder > title > name attribute > stable class names. Avoid nth-child, avoid deeply nested paths.
199
+ - Include appropriate wait actions between interactions (200-500ms between clicks, 1000-2000ms for page loads or navigations).
200
+ - Actions run in order, one at a time. The user sees each action highlighted on the page as it runs.
201
+ - The "script" field must be a complete, runnable Playwright script (CommonJS, no imports) that reproduces the same actions. Use the page URL provided by the user as the starting URL.`;
202
+ }
203
+
204
+ function automationUser(msg) {
205
+ const instructions = msg.instructions || msg.text || '';
206
+ const instStr = Array.isArray(instructions) ? instructions.map((s,i) => `${i+1}. ${s}`).join('\n') : instructions;
207
+ let parts = [
208
+ `Page URL: ${msg.url}`,
209
+ `Page title: ${msg.title}`,
210
+ '',
211
+ `User's automation instructions (execute these in the page):`,
212
+ instStr,
213
+ '',
214
+ 'IMPORTANT:',
215
+ '- A screenshot of the page is attached. Use it to understand the visual layout, identify elements the user is referring to, and confirm what the page looks like.',
216
+ '- This is a user-facing web page. The vibedit editor toolbar is an overlay and is NOT part of the page being automated. Ignore it.',
217
+ '- Produce an "actions" array that executes IMMEDIATELY in the current browser tab.',
218
+ '- Also produce a "script" field with a complete Playwright script that does the same thing.',
219
+ '- Every selector in actions MUST be copy-pasted from the pruned DOM below. Do NOT invent any selector.',
220
+ '- If you cannot find a matching element in the DOM for a requested action, skip that action and explain what was missing in "notes".',
221
+ '- Include wait actions between interactions so the user can see each step.',
222
+ ];
223
+
224
+ if (msg.flowEvents && msg.flowEvents.length > 0) {
225
+ const evLines = msg.flowEvents
226
+ .filter((e) => e.kind !== 'shot')
227
+ .slice(0, 80)
228
+ .map((e) => {
229
+ if (e.kind === 'click') return `[${(e.t / 1000).toFixed(1)}s] click ${e.selector} "${(e.text || '').slice(0, 60)}"`;
230
+ if (e.kind === 'scroll') return `[${(e.t / 1000).toFixed(1)}s] scroll to y=${e.y}`;
231
+ if (e.kind === 'input') return `[${(e.t / 1000).toFixed(1)}s] typed in ${e.selector}`;
232
+ if (e.kind === 'nav') return `[${(e.t / 1000).toFixed(1)}s] navigated to ${e.url}`;
233
+ return `[${(e.t / 1000).toFixed(1)}s] ${e.kind}`;
234
+ }).join('\n');
235
+ parts.push('', 'Recorded interaction flow (use these exact selectors):', evLines);
236
+ }
237
+
238
+ if (msg.dom) {
239
+ parts.push('', 'PRUNED DOM OF THE TARGET PAGE (these are the ONLY valid selectors you may use):', (msg.dom || '').slice(0, 7000));
240
+ }
241
+
242
+ return parts.join('\n');
243
+ }
244
+
245
+ module.exports = { chatSystem, chatUser, saveSystem, saveUser, flowUser, automationSystem, automationUser };
@@ -0,0 +1,32 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function shmakkStateDir(projectDir) {
5
+ return path.join(projectDir, '.shmakk', 'state');
6
+ }
7
+
8
+ function vibeditState(projectDir) {
9
+ const stateDir = shmakkStateDir(projectDir);
10
+ return {
11
+ stateDir,
12
+ specsDir: path.join(stateDir, 'vibedit-specs'),
13
+ pendingSpecFile: path.join(stateDir, 'vibedit-specs', 'pending'),
14
+ sessionsDir: path.join(stateDir, 'vibedit-sessions'),
15
+ automationsDir: path.join(stateDir, 'browser-automations'),
16
+ pageStateFile: path.join(stateDir, 'vibedit-page-state.json'),
17
+ };
18
+ }
19
+
20
+ function ensureVibeditState(projectDir) {
21
+ const state = vibeditState(projectDir);
22
+ fs.mkdirSync(state.specsDir, { recursive: true });
23
+ fs.mkdirSync(state.sessionsDir, { recursive: true });
24
+ fs.mkdirSync(state.automationsDir, { recursive: true });
25
+ return state;
26
+ }
27
+
28
+ module.exports = {
29
+ shmakkStateDir,
30
+ vibeditState,
31
+ ensureVibeditState,
32
+ };