gm-hermes 2.0.515

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/AGENTS.md ADDED
@@ -0,0 +1,11 @@
1
+ # AGENTS
2
+
3
+ ## Technical Notes
4
+
5
+ Hook response format: `{"decision":"allow|block","reason":"text"}` with exit code 0.
6
+
7
+ Tool names for this platform: `bash` → `terminal`, `write` → `write_file`, `glob` → `glob_files`, `grep` → `search_in_file`, `search` → `web_search`
8
+
9
+ When filtering transcript history by sessionId, use: `if (sessionId && entry.sessionId === sessionId)`
10
+
11
+ Verification file `.gm-stop-verified` is auto-added to .gitignore and tracks session completion state.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # gm-hermes for Hermes Agent
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install -g gm-hermes
7
+ ```
8
+
9
+ Or install directly:
10
+
11
+ ```bash
12
+ npx gm-hermes
13
+ ```
14
+
15
+ Restart Hermes Agent to activate skills.
16
+
17
+ ## What it does
18
+
19
+ Installs the gm state machine skills (PLAN→EXECUTE→EMIT→VERIFY→COMPLETE) into Hermes Agent under `~/.hermes/skills/gm/skills/software-development/`.
20
+
21
+ Use `/gm` or invoke skills by name in Hermes to access the full state machine workflow.
package/cli.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
7
+ const destDir = path.join(homeDir, '.hermes', 'skills', 'gm');
8
+
9
+ const srcDir = __dirname;
10
+ const isUpgrade = fs.existsSync(destDir);
11
+
12
+ console.log(isUpgrade ? 'Upgrading gm-hermes...' : 'Installing gm-hermes...');
13
+
14
+ try {
15
+ fs.mkdirSync(destDir, { recursive: true });
16
+
17
+ const filesToCopy = [["skills","skills"],["README.md","README.md"]];
18
+
19
+ function copyRecursive(src, dst) {
20
+ if (!fs.existsSync(src)) return;
21
+ if (fs.statSync(src).isDirectory()) {
22
+ fs.mkdirSync(dst, { recursive: true });
23
+ fs.readdirSync(src).forEach(f => copyRecursive(path.join(src, f), path.join(dst, f)));
24
+ } else {
25
+ fs.copyFileSync(src, dst);
26
+ }
27
+ }
28
+
29
+ filesToCopy.forEach(([src, dst]) => copyRecursive(path.join(srcDir, src), path.join(destDir, dst)));
30
+
31
+ const destPath = process.platform === 'win32' ? destDir.replace(/\\/g, '/') : destDir;
32
+ console.log(`✓ gm-hermes ${isUpgrade ? 'upgraded' : 'installed'} to ${destPath}`);
33
+ console.log('Restart Hermes to activate skills.');
34
+ } catch (e) {
35
+ console.error('Installation failed:', e.message);
36
+ process.exit(1);
37
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "gm",
3
+ "version": "2.0.515",
4
+ "description": "State machine agent with hooks, skills, and automated git enforcement",
5
+ "author": "AnEntrypoint",
6
+ "homepage": "https://github.com/AnEntrypoint/gm",
7
+ "skills": "./skills",
8
+ "category": "software-development"
9
+ }
package/index.html ADDED
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Hermes Agent - gm plugin</title>
7
+ <link rel="stylesheet" href="https://unpkg.com/rippleui@1.12.1/dist/css/styles.css">
8
+ <script type="importmap">{"imports":{"webjsx":"https://cdn.jsdelivr.net/npm/webjsx@0.0.42/dist/index.js"}}</script>
9
+ <style>
10
+ body{background:#0f172a;color:#e2e8f0;font-family:system-ui,sans-serif;margin:0}
11
+ .gradient-hero{background:linear-gradient(135deg,#0f172a 0%,#1e1b4b 50%,#0f172a 100%)}
12
+ .card-hover{transition:transform .2s,box-shadow .2s}
13
+ .card-hover:hover{transform:translateY(-2px);box-shadow:0 8px 30px rgba(0,0,0,.3)}
14
+ pre{scrollbar-width:thin;background:#020617;border:1px solid #1e293b;border-radius:8px;padding:12px 16px;color:#4ade80;overflow-x:auto;font-size:.875rem}
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <script type="module">
19
+ import { createElement as h, applyDiff, Fragment } from "webjsx";
20
+ const PLATFORM_NAME="Hermes Agent",PLATFORM_TYPE="CLI Tool",PLATFORM_TYPE_COLOR="#3b82f6";
21
+ const DESCRIPTION="State machine agent with hooks, skills, and automated git enforcement",VERSION="2.0.515";
22
+ const GITHUB_URL="https://github.com/AnEntrypoint/gm-hermes",BADGE_LABEL="hermes";
23
+ const FEATURES=[{"title":"State Machine","desc":"Immutable PLAN→EXECUTE→EMIT→VERIFY→COMPLETE phases with full mutable tracking"},{"title":"Semantic Search","desc":"Natural language codebase exploration via codesearch skill — no grep needed"},{"title":"Hooks","desc":"Pre-tool, session-start, prompt-submit, and stop hooks for full lifecycle control"},{"title":"Agents","desc":"gm, codesearch, and websearch agents pre-configured and ready to use"},{"title":"MCP Integration","desc":"Model Context Protocol server support built in"},{"title":"Auto-Recovery","desc":"Supervisor hierarchy ensures the system never crashes"}],INSTALL_STEPS=[{"desc":"Install via npm","cmd":"npm install -g gm-hermes"},{"desc":"Restart Hermes Agent — skills activate automatically"}];
24
+ const CURRENT_PLATFORM="gm-hermes";
25
+ const ALL_PLATFORMS=[
26
+ {id:'gm-cc',label:'Claude Code',type:'cli'},{id:'gm-gc',label:'Gemini CLI',type:'cli'},
27
+ {id:'gm-oc',label:'OpenCode',type:'cli'},{id:'gm-kilo',label:'Kilo Code',type:'cli'},
28
+ {id:'gm-codex',label:'Codex',type:'cli'},{id:'gm-copilot-cli',label:'Copilot CLI',type:'cli'},
29
+ {id:'gm-qwen',label:'Qwen Code',type:'cli'},{id:'gm-hermes',label:'Hermes Agent',type:'cli'},
30
+ {id:'gm-vscode',label:'VS Code',type:'ide'},{id:'gm-cursor',label:'Cursor',type:'ide'},
31
+ {id:'gm-zed',label:'Zed',type:'ide'},{id:'gm-jetbrains',label:'JetBrains',type:'ide'},
32
+ ];
33
+ const GH_ICON='M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z';
34
+ function NavBar(){return h('nav',{class:'border-b border-gray-800 bg-gray-950/80 backdrop-blur sticky top-0 z-10'},h('div',{class:'max-w-5xl mx-auto px-4 py-3 flex items-center justify-between'},h('div',{class:'flex items-center gap-3'},h('a',{href:'https://anentrypoint.github.io/gm',class:'text-white font-bold text-lg hover:text-indigo-400 transition-colors'},'gm'),h('span',{class:'text-gray-500'},'/'),h('span',{class:'text-gray-300 font-medium'},BADGE_LABEL)),h('a',{href:GITHUB_URL,target:'_blank',rel:'noopener',class:'flex items-center gap-2 text-gray-400 hover:text-white transition-colors text-sm'},h('svg',{viewBox:'0 0 16 16',class:'w-5 h-5 fill-current','aria-hidden':'true'},h('path',{d:GH_ICON})),'GitHub')));}
35
+ function Badge({label,color}){return h('span',{class:'inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold text-white',style:`background-color:${color}`},label);}
36
+ function Hero(){return h('section',{class:'gradient-hero py-20 px-4'},h('div',{class:'max-w-5xl mx-auto text-center'},h('div',{class:'flex justify-center gap-2 mb-6'},h(Badge,{label:PLATFORM_TYPE,color:PLATFORM_TYPE_COLOR}),h(Badge,{label:'v'+VERSION,color:'#374151'})),h('h1',{class:'text-4xl md:text-5xl font-bold text-white mb-4'},PLATFORM_NAME),h('p',{class:'text-lg text-gray-300 max-w-2xl mx-auto mb-8'},DESCRIPTION),h('a',{href:GITHUB_URL,target:'_blank',rel:'noopener',class:'inline-flex items-center gap-2 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold px-6 py-3 rounded-lg transition-colors'},'View on GitHub')));}
37
+ function FeatureCard({title,desc}){return h('div',{class:'card-hover bg-gray-900 border border-gray-800 rounded-xl p-5'},h('h3',{class:'font-semibold text-white mb-2'},title),h('p',{class:'text-gray-400 text-sm leading-relaxed'},desc));}
38
+ function FeaturesSection(){return h('section',{class:'py-16 px-4'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-8 text-center'},'Features'),h('div',{class:'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'},...FEATURES.map(f=>h(FeatureCard,{title:f.title,desc:f.desc})))));}
39
+ function InstallStep({step,index}){return h('div',{class:'flex gap-4 items-start'},h('div',{class:'flex-shrink-0 w-7 h-7 rounded-full bg-indigo-600 flex items-center justify-center text-xs font-bold text-white'},String(index+1)),h('div',{class:'flex-1'},h('p',{class:'text-gray-300 text-sm mb-1'},step.desc),step.cmd?h('pre',{class:'bg-gray-950 border border-gray-700 rounded-lg px-4 py-2 text-sm text-green-400 overflow-x-auto mt-1'},step.cmd):null));}
40
+ function InstallSection(){if(!INSTALL_STEPS.length)return null;return h('section',{class:'py-16 px-4 bg-gray-900/50'},h('div',{class:'max-w-2xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-8 text-center'},'Installation'),h('div',{class:'space-y-6'},...INSTALL_STEPS.map((step,i)=>h(InstallStep,{step,index:i})))));}
41
+ function PlatformLink({p}){const isCurrent=p.id===CURRENT_PLATFORM;return h('a',{href:isCurrent?'#':`https://anentrypoint.github.io/${p.id}`,class:`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${isCurrent?'bg-indigo-600 text-white cursor-default':'bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-white'}`},p.label);}
42
+ const SM_PHASES=[{name:'PLAN',desc:'Write .prd with every unknown named before any work begins'},{name:'EXECUTE',desc:'Prove every hypothesis via witnessed execution, import real modules'},{name:'EMIT',desc:'Write files only after all tests pass — pre and post-emit gates'},{name:'VERIFY',desc:'End-to-end execution confirms all changes work in real context'},{name:'COMPLETE',desc:'.prd empty, git clean, all output pushed'}];
43
+ const HOOK_ITEMS=[{title:'Tool interception',desc:'exec:<lang> commands run code directly; forbidden tools redirected to code-search'},{title:'System injection',desc:'gm.md rules prepended to every system prompt'},{title:'Context injection',desc:'session-start injects codebase analysis and pending work reminder'},{title:'Completion gate',desc:'session end blocked until .prd is empty and git is clean'}];
44
+ function PhaseCard({phase,index,total}){return h('div',{class:'card-hover bg-gray-900 border border-gray-800 rounded-xl p-4 flex-1 min-w-0'},h('div',{class:'flex items-center gap-2 mb-2'},h('span',{class:'text-indigo-400 font-bold text-sm font-mono'},phase.name),index<total-1?h('span',{class:'text-gray-600 text-xs'},'→'):null),h('p',{class:'text-gray-400 text-xs leading-relaxed'},phase.desc));}
45
+ function StateMachineSection(){return h('section',{class:'py-16 px-4 bg-gray-900/40'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-2 text-center'},'State Machine'),h('p',{class:'text-gray-400 text-sm text-center mb-8'},'Every task follows the same 5-phase cycle — no skipping, no shortcuts'),h('div',{class:'flex flex-col sm:flex-row gap-3'},...SM_PHASES.map((phase,i)=>h(PhaseCard,{phase,index:i,total:SM_PHASES.length})))));}
46
+ function HookCard({item}){return h('div',{class:'card-hover bg-gray-900 border border-gray-800 rounded-xl p-5'},h('h3',{class:'font-semibold text-white mb-2'},item.title),h('p',{class:'text-gray-400 text-sm leading-relaxed'},item.desc));}
47
+ function HooksSection(){return h('section',{class:'py-16 px-4'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-2 text-center'},'What the Hooks Enforce'),h('p',{class:'text-gray-400 text-sm text-center mb-8'},'Lifecycle hooks wrap every interaction to keep the agent on rails'),h('div',{class:'grid grid-cols-1 sm:grid-cols-2 gap-4'},...HOOK_ITEMS.map(item=>h(HookCard,{item})))));}
48
+ function AlsoAvailableSection(){const cli=ALL_PLATFORMS.filter(p=>p.type==='cli'),ide=ALL_PLATFORMS.filter(p=>p.type==='ide');return h('section',{class:'py-16 px-4 bg-gray-900/30'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-8 text-center'},'Also Available For'),h('div',{class:'space-y-6'},h('div',null,h('p',{class:'text-xs font-semibold text-blue-400 uppercase tracking-wider mb-3'},'CLI Tools'),h('div',{class:'flex flex-wrap gap-2'},...cli.map(p=>h(PlatformLink,{p,key:p.id})))),h('div',null,h('p',{class:'text-xs font-semibold text-purple-400 uppercase tracking-wider mb-3'},'IDE Extensions'),h('div',{class:'flex flex-wrap gap-2'},...ide.map(p=>h(PlatformLink,{p,key:p.id}))))),h('div',{class:'mt-8 text-center'},h('a',{href:'https://anentrypoint.github.io/gm',class:'text-sm text-gray-400 hover:text-indigo-300 transition-colors'},'← Back to gm hub'))));}
49
+ function Footer(){return h('footer',{class:'border-t border-gray-800 py-8 px-4 text-center text-gray-500 text-sm'},h('p',null,'Generated by ',h('a',{href:'https://github.com/AnEntrypoint/gm',class:'text-indigo-400 hover:text-indigo-300'},'gm'),' — convention-driven multi-platform plugin generator'));}
50
+ function App(){return h(Fragment,null,h(NavBar,null),h(Hero,null),h(FeaturesSection,null),h(StateMachineSection,null),h(HooksSection,null),h(InstallSection,null),h(AlsoAvailableSection,null),h(Footer,null));}
51
+ applyDiff(document.body,[h(App,null)]);
52
+ </script>
53
+ </body>
54
+ </html>
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "gm-hermes",
3
+ "version": "2.0.515",
4
+ "description": "State machine agent with hooks, skills, and automated git enforcement",
5
+ "author": "AnEntrypoint",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "hermes-agent",
9
+ "hermes",
10
+ "agent",
11
+ "state-machine",
12
+ "automation",
13
+ "gm"
14
+ ],
15
+ "homepage": "https://github.com/AnEntrypoint/gm-hermes#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/AnEntrypoint/gm-hermes/issues"
18
+ },
19
+ "engines": {
20
+ "node": ">=16.0.0"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "skills/",
27
+ "cli.js",
28
+ "README.md",
29
+ "AGENTS.md",
30
+ "hermes-skill.json",
31
+ "index.html"
32
+ ]
33
+ }
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: browser
3
+ description: Browser automation via playwriter. Use when user needs to interact with websites, navigate pages, fill forms, click buttons, take screenshots, extract data, test web apps, or automate any browser task.
4
+ allowed-tools: Bash(browser:*), Bash(exec:browser*)
5
+ ---
6
+
7
+ # Browser Automation with playwriter
8
+
9
+ **Use gm subagents for all independent work items. Invoke all skills in the chain: planning → gm-execute → gm-emit → gm-complete → update-docs.**
10
+
11
+
12
+ ## Two Pathways
13
+
14
+ **Session commands** (`browser:` prefix) — manage multi-step sessions via playwriter CLI. Each `browser:` block runs its commands sequentially.
15
+
16
+ **JS execution** (`exec:browser`) — run JavaScript directly against `page`. State persists across calls via `state` global.
17
+
18
+ **CRITICAL**: Never mix these two pathways. Each `browser:` block is a separate Bash call. Each `exec:browser` block is a separate Bash call.
19
+
20
+ ## 15-Second Ceiling — How It Works
21
+
22
+ Every `exec:browser` call has a 15s live window. During that window, all stdout/stderr is streamed to you in real time. After 15s the task backgrounds and you receive:
23
+ - All output produced so far (live drain)
24
+ - A task ID with `plugkit sleep/status/close` instructions
25
+
26
+ **The task keeps running.** Every subsequent plugkit interaction automatically drains all running browser tasks — you will see new output without asking.
27
+
28
+ **Never use `await new Promise(r => setTimeout(r, N))` with N > 10000.** Use short poll loops instead (see patterns below).
29
+
30
+ **"Assertion failed: UV_HANDLE_CLOSING" in output** means the call exceeded 15s and was cut off — ignore the assertion noise, look at the output before it. The task was backgrounded normally.
31
+
32
+ ## Idle Timeout & Session Reaper
33
+
34
+ Playwriter kills idle browser sessions after 5-15 minutes of inactivity. The rs-exec tooling now automatically cleans up the spawned browser process when the Claude Code session ends, preventing zombie tabs.
35
+
36
+ **Historical note**: Earlier versions left the browser running after session end, causing repeated tabs on reconnect. This is now fixed — the browser will be killed when your session idles and closes.
37
+
38
+ ## Session Pathway (`browser:`)
39
+
40
+ Create a session first, use `--direct` for CDP mode (requires Chrome with remote debugging):
41
+
42
+ ```
43
+ browser:
44
+ playwriter session new --direct
45
+ ```
46
+
47
+ Returns a numeric session ID (e.g. `1`). Use that ID for all subsequent calls. **Each command must be a separate Bash call:**
48
+
49
+ ```
50
+ browser:
51
+ playwriter -s 1 -e 'await page.goto("http://example.com")'
52
+ ```
53
+
54
+ ```
55
+ browser:
56
+ playwriter -s 1 -e 'await snapshot({ page })'
57
+ ```
58
+
59
+ ```
60
+ browser:
61
+ playwriter -s 1 -e 'await screenshotWithAccessibilityLabels({ page })'
62
+ ```
63
+
64
+ State persists across session calls:
65
+
66
+ ```
67
+ browser:
68
+ playwriter -s 1 -e 'state.x = 1'
69
+ ```
70
+
71
+ ```
72
+ browser:
73
+ playwriter -s 1 -e 'console.log(state.x)'
74
+ ```
75
+
76
+
77
+ **RULE**: The `-e` argument must use single quotes. The JS inside must use double quotes for strings.
78
+
79
+ **RULE**: Never chain multiple `playwriter` commands in one `browser:` block — run one command per block.
80
+
81
+ ## JS Execution Pathway (`exec:browser`)
82
+
83
+ For direct page access, DOM queries, and data extraction. The runtime provides `page`, `snapshot`, `screenshotWithAccessibilityLabels`, and `state` as globals.
84
+
85
+ ```
86
+ exec:browser
87
+ await page.goto('https://example.com')
88
+ await snapshot({ page })
89
+ ```
90
+
91
+ ```
92
+ exec:browser
93
+ const title = await page.title()
94
+ console.log(title)
95
+ ```
96
+
97
+ Never add shell quoting — write plain JavaScript directly.
98
+
99
+ ## Core Workflow
100
+
101
+ 1. **Navigate**: `exec:browser\nawait page.goto('url')` — session auto-created on first call
102
+ 2. **Snapshot**: `exec:browser\nawait snapshot({ page })`
103
+ 3. **Interact**: click, fill, type in subsequent `exec:browser` calls
104
+ 4. **Extract data**: `exec:browser\nconsole.log(await page.evaluate(() => document.title))`
105
+
106
+ ## Long-Running Operations — Poll Pattern
107
+
108
+ For operations that take >10s (model loading, network fetches, animations):
109
+
110
+ **Step 1** — set up listener and kick off the operation:
111
+ ```
112
+ exec:browser
113
+ state.done = false
114
+ state.result = null
115
+ page.on('console', msg => {
116
+ const t = msg.text()
117
+ if (t.includes('loaded') || t.includes('ready')) { state.done = true; state.result = t }
118
+ })
119
+ await page.click('#start-button')
120
+ console.log('started, waiting...')
121
+ ```
122
+
123
+ **Step 2** — poll in short bursts (this will background after 15s and keep draining):
124
+ ```
125
+ exec:browser
126
+ const start = Date.now()
127
+ while (!state.done && Date.now() - start < 12000) {
128
+ await new Promise(r => setTimeout(r, 500))
129
+ }
130
+ console.log('done:', state.done, 'result:', state.result)
131
+ ```
132
+
133
+ If step 2 backgrounds (takes >15s), every subsequent plugkit call will drain its output automatically. When you see the result in the drain log, close the task:
134
+ ```
135
+ exec:close
136
+ task_N
137
+ ```
138
+
139
+ ## Common Patterns
140
+
141
+
142
+
143
+ ### Data Extraction
144
+
145
+ ```
146
+ exec:browser
147
+ const items = await page.$$eval('.product-title', els => els.map(e => e.textContent))
148
+ console.log(JSON.stringify(items))
149
+ ```
150
+
151
+
152
+ ### Console Monitoring — set up listener first, then poll
153
+
154
+ ```
155
+ exec:browser
156
+ state.logs = []
157
+ state.errors = []
158
+ page.on('console', msg => state.logs.push({ type: msg.type(), text: msg.text() }))
159
+ page.on('pageerror', e => state.errors.push(e.message))
160
+ console.log('listeners attached')
161
+ ```
162
+
163
+ ```
164
+ exec:browser
165
+ console.log('logs so far:', JSON.stringify(state.logs.slice(-20)))
166
+ console.log('errors:', JSON.stringify(state.errors))
167
+ ```
168
+
169
+ ```
170
+ exec:browser
171
+ if (page.workers().length > 0) {
172
+ const r = await page.workers()[0].evaluate(() => JSON.stringify({ type: 'worker alive' }))
173
+ console.log(r)
174
+ }
175
+ ```
176
+
177
+ exec:browser
178
+ const result = await page.evaluate(() => JSON.stringify({
179
+ entityCount: window.debug?.scene?.children?.length,
180
+ playerId: window.debug?.client?.playerId
181
+ }))
182
+ console.log(result)
183
+ ```
184
+
185
+ exec:browser
186
+ const start = Date.now()
187
+ while (Date.now() - start < 12000) {
188
+ const el = await page.$('#status')
189
+ if (el) { console.log('found:', await el.textContent()); break }
190
+ await new Promise(r => setTimeout(r, 300))
191
+ }
192
+ ```
193
+
194
+ ## Key Rules
195
+
196
+ - `browser:` prefix → playwriter session management (one command per block)
197
+ - `exec:browser` → JS in page context (multi-line JS allowed, 15s live window)
198
+ - Never mix pathways in the same Bash call
199
+ - `-e` argument: single quotes on outside, double quotes inside for JS strings
200
+ - One `playwriter` command per `browser:` block
201
+ - Never `await setTimeout(N)` with N > 10000 — use short poll loops instead
202
+ - All running browser tasks drain automatically on every plugkit interaction
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: code-search
3
+ description: Mandatory codebase search workflow. Use whenever you need to find anything in the codebase. Start with two words, iterate by changing or adding words until found.
4
+ ---
5
+
6
+ # CODEBASE SEARCH — Mandatory Workflow
7
+
8
+ **Use gm subagents for all independent work items. Invoke all skills in the chain: planning → gm-execute → gm-emit → gm-complete → update-docs.**
9
+
10
+
11
+ `exec:codesearch` is the only way to search the codebase. Glob, Grep, Find, Explore are hook-blocked.
12
+
13
+ **PDFs are indexed like code.** Any `.pdf` in the repository — specs, papers, manuals, RFCs, datasheets, design docs — is extracted page-by-page at scan time and enters the BM25 + vector index alongside source files. Treat PDF hits as first-class search results. A PDF chunk reports `line_start = line_end = page_number`; cite as `path/to/doc.pdf:<page>`. Unscanned digital PDFs are a search gap — if you know a doc exists and it isn't returning, check it is not under an ignored dir and that extraction succeeded (encrypted / image-only PDFs yield empty chunks silently).
14
+
15
+ ## Syntax
16
+
17
+ ```
18
+ exec:codesearch
19
+ <natural language query>
20
+ ```
21
+
22
+ ## Mandatory Search Protocol
23
+
24
+ **Start with exactly two words.** Never start broader. Never start with one word.
25
+
26
+ **Iterate by changing or adding words** — do not switch approach or give up until the content is found:
27
+
28
+ 1. Start: two-word query most likely to match
29
+ 2. No results → change one word (synonym, related term)
30
+ 3. Still no results → add a third word (narrow scope)
31
+ 4. Still no results → swap the changed word again
32
+ 5. Keep iterating — changing or adding words each pass — until content is found
33
+
34
+ **Never**: start with one word | start with a sentence | give up after one miss | switch to a different tool | declare content missing after fewer than 4 search attempts
35
+
36
+ **Each search is one `exec:codesearch` call.** Run them sequentially — use each result to inform the next query.
37
+
38
+ ## Examples
39
+
40
+ Finding where a function is defined:
41
+ ```
42
+ exec:codesearch
43
+ session cleanup idle
44
+ ```
45
+ → no results →
46
+ ```
47
+ exec:codesearch
48
+ cleanup sessions timeout
49
+ ```
50
+ → found.
51
+
52
+ Finding config format:
53
+ ```
54
+ exec:codesearch
55
+ plugin registration format
56
+ ```
57
+ → no results →
58
+ ```
59
+ exec:codesearch
60
+ plugin config array
61
+ ```
62
+ → found.
63
+
64
+ Finding content inside a spec PDF:
65
+ ```
66
+ exec:codesearch
67
+ usb descriptor endpoint
68
+ ```
69
+ → returns `docs/usb-spec.pdf:42` — cite page, open via Read if you need the surrounding page text.
@@ -0,0 +1,183 @@
1
+ ---
2
+ name: create-lang-plugin
3
+ description: Create a lang/ plugin that wires any CLI tool or language runtime into gm-cc — adds exec:<id> dispatch, optional LSP diagnostics, and optional prompt context injection. Zero hook configuration required.
4
+ ---
5
+
6
+ # CREATE LANG PLUGIN
7
+
8
+ **Use gm subagents for all independent work items. Invoke all skills in the chain: planning → gm-execute → gm-emit → gm-complete → update-docs.**
9
+
10
+
11
+ A lang plugin is a single CommonJS file at `<projectDir>/lang/<id>.js`. gm-cc's hooks auto-discover it — no hook editing, no settings changes. The plugin gets three integration points: **exec dispatch**, **LSP diagnostics**, and **context injection**.
12
+
13
+ ## PLUGIN SHAPE
14
+
15
+ ```js
16
+ 'use strict';
17
+ module.exports = {
18
+ id: 'mytool', // must match filename: lang/mytool.js
19
+ exec: {
20
+ match: /^exec:mytool/, // regex tested against full "exec:mytool\n<code>" string
21
+ run(code, cwd) { // returns string or Promise<string>
22
+ // ...
23
+ }
24
+ },
25
+ lsp: { // optional — synchronous only
26
+ check(fileContent, cwd) { // returns Diagnostic[] synchronously
27
+ // ...
28
+ }
29
+ },
30
+ extensions: ['.ext'], // optional — file extensions lsp.check applies to
31
+ context: `=== mytool ===\n...` // optional — string or () => string
32
+ };
33
+ ```
34
+
35
+ ```ts
36
+ type Diagnostic = { line: number; col: number; severity: 'error'|'warning'; message: string };
37
+ ```
38
+
39
+ ## HOW IT WORKS
40
+
41
+ - **`exec.run`** is called in a child process (30s timeout) when Claude writes `exec:mytool\n<code>`. Output is returned as `exec:mytool output:\n\n<result>`. Async is fine here.
42
+ - **`lsp.check`** is called synchronously in the hook process on each prompt submit — must NOT be async. Use `execFileSync` or `spawnSync`.
43
+ - **`context`** is injected into every prompt's `additionalContext` (truncated to 2000 chars) and into the session-start context.
44
+ - **`match`** regex is tested against the full command string `exec:mytool\n<code>` — keep it simple: `/^exec:mytool/`.
45
+
46
+ ## STEP 1 — IDENTIFY THE TOOL
47
+
48
+ Answer these before writing any code:
49
+
50
+ 1. What is the tool's CLI name or npm package? (`gdlint`, `tsc`, `deno`, `ruff`, ...)
51
+ 2. How do you run a single expression/snippet? (`tool eval <expr>`, `tool -e <code>`, HTTP POST, ...)
52
+ 3. How do you run a file? (`tool run <file>`, `tool <file>`, ...)
53
+ 4. Does it have a lint/check mode? What does its output format look like?
54
+ 5. What file extensions does it apply to?
55
+ 6. Is the game/server running required, or does it work headlessly?
56
+
57
+ ## STEP 2 — IMPLEMENT exec.run
58
+
59
+ Pattern for **HTTP eval** (tool has a running server):
60
+
61
+ ```js
62
+ const http = require('http');
63
+ function httpPost(port, urlPath, body) {
64
+ return new Promise((resolve, reject) => {
65
+ const data = JSON.stringify(body);
66
+ const req = http.request(
67
+ { hostname: '127.0.0.1', port, path: urlPath, method: 'POST',
68
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
69
+ (res) => { let raw = ''; res.on('data', c => raw += c); res.on('end', () => { try { resolve(JSON.parse(raw)); } catch { resolve({ raw }); } }); }
70
+ );
71
+ req.setTimeout(8000, () => { req.destroy(); reject(new Error('timeout')); });
72
+ req.on('error', reject);
73
+ req.write(data); req.end();
74
+ });
75
+ }
76
+ ```
77
+
78
+ Pattern for **file-based execution** (write temp file, run headlessly):
79
+
80
+ ```js
81
+ const fs = require('fs');
82
+ const os = require('os');
83
+ const path = require('path');
84
+ const { execFileSync } = require('child_process');
85
+
86
+ function runFile(code, cwd) {
87
+ const tmp = path.join(os.tmpdir(), `plugin_${Date.now()}.ext`);
88
+ fs.writeFileSync(tmp, code);
89
+ try {
90
+ return execFileSync('mytool', ['run', tmp], { cwd, encoding: 'utf8', timeout: 10000 });
91
+ } finally {
92
+ try { fs.unlinkSync(tmp); } catch (_) {}
93
+ }
94
+ }
95
+ ```
96
+
97
+ **Distinguish single expression vs multi-line** when both modes exist:
98
+
99
+ ```js
100
+ function isSingleExpr(code) {
101
+ return !code.trim().includes('\n') && !/\b(func|def|fn |class|import)\b/.test(code);
102
+ }
103
+ ```
104
+
105
+ ## STEP 3 — IMPLEMENT lsp.check (if applicable)
106
+
107
+ Must be **synchronous**. Parse the tool's stderr/stdout for diagnostics:
108
+
109
+ ```js
110
+ const { spawnSync } = require('child_process');
111
+ const fs = require('fs');
112
+ const os = require('os');
113
+ const path = require('path');
114
+
115
+ function check(fileContent, cwd) {
116
+ const tmp = path.join(os.tmpdir(), `lsp_${Math.random().toString(36).slice(2)}.ext`);
117
+ try {
118
+ fs.writeFileSync(tmp, fileContent);
119
+ const r = spawnSync('mytool', ['check', tmp], { encoding: 'utf8', cwd });
120
+ const output = r.stdout + r.stderr;
121
+ return output.split('\n').reduce((acc, line) => {
122
+ const m = line.match(/^.+:(\d+):(\d+):\s+(error|warning):\s+(.+)$/);
123
+ if (m) acc.push({ line: parseInt(m[1]), col: parseInt(m[2]), severity: m[3], message: m[4].trim() });
124
+ return acc;
125
+ }, []);
126
+ } catch (_) {
127
+ return [];
128
+ } finally {
129
+ try { fs.unlinkSync(tmp); } catch (_) {}
130
+ }
131
+ }
132
+ ```
133
+
134
+ Common output patterns to parse:
135
+ - `file:line:col: error: message` → standard
136
+ - `file:line: E001: message` → gdlint style (`E`=error, `W`=warning)
137
+ - JSON output → `JSON.parse(r.stdout).errors.map(...)`
138
+
139
+ ## STEP 4 — WRITE context STRING
140
+
141
+ Describe what `exec:<id>` does and when to use it. This appears in every prompt. Keep it under 300 chars:
142
+
143
+ ```js
144
+ context: `=== mytool exec: support ===
145
+ exec:mytool
146
+ <expression or code block>
147
+
148
+ Runs via <how>. Use for <when>.`
149
+ ```
150
+
151
+ ## STEP 5 — WRITE THE FILE
152
+
153
+ File goes at `lang/<id>.js` in the project root. The `id` field must match the filename (without `.js`).
154
+
155
+ Verify after writing:
156
+
157
+ ```
158
+ exec:nodejs
159
+ const p = require('/abs/path/to/lang/mytool.js');
160
+ console.log(p.id, typeof p.exec.run, p.exec.match.toString());
161
+ ```
162
+
163
+ Then test dispatch:
164
+
165
+ ```
166
+ exec:mytool
167
+ <a simple test expression>
168
+ ```
169
+
170
+ If it returns `exec:mytool output:` → working. If it errors → fix `exec.run`.
171
+
172
+ ## CONSTRAINTS
173
+
174
+ - `exec.run` may be async — it runs in a child process with a 30s timeout
175
+ - `lsp.check` must be synchronous — no Promises, no async/await
176
+ - Plugin must be CommonJS (`module.exports = { ... }`) — no ES module syntax
177
+ - No persistent processes — `exec.run` must complete and exit cleanly
178
+ - `id` must match the filename exactly
179
+ - First match wins — if multiple plugins could match, make `match` specific
180
+
181
+ ## EXAMPLE — gdscript plugin (reference implementation)
182
+
183
+ See `C:/dev/godot-kit/lang/gdscript.js` for a complete working example combining HTTP eval (single expressions via port 6009) with headless file execution fallback, synchronous gdlint LSP, and a context string.