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 +11 -0
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/cli.js +37 -0
- package/hermes-skill.json +9 -0
- package/index.html +54 -0
- package/package.json +33 -0
- package/skills/software-development/browser/SKILL.md +202 -0
- package/skills/software-development/code-search/SKILL.md +69 -0
- package/skills/software-development/create-lang-plugin/SKILL.md +183 -0
- package/skills/software-development/gm/SKILL.md +28 -0
- package/skills/software-development/gm-complete/SKILL.md +185 -0
- package/skills/software-development/gm-emit/SKILL.md +126 -0
- package/skills/software-development/gm-execute/SKILL.md +159 -0
- package/skills/software-development/pages/SKILL.md +258 -0
- package/skills/software-development/planning/SKILL.md +177 -0
- package/skills/software-development/ssh/SKILL.md +89 -0
- package/skills/software-development/update-docs/SKILL.md +113 -0
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.
|