refactor: route Grok installs through canonical adapter

This commit is contained in:
MistyStar committed 2026-10-20 10:35:37 +08:00
1 parent 0724e2d8bc
commit f69f945398
55 files changed
+1132 -2220

No files matched your search

+19 -10
View File
@@ -11,10 +11,19 @@ thin subdirectory copy drops parent-relative runtime files.
## Install
ECC trusted install is `node scripts/grok-install.js` (`previewInstall` /
`applyInstall` in `scripts/lib/grok-harness-adapter.js`, install target `grok`,
state at `~/.grok/ecc/install-state.json`). That plan is receipt-backed and
requires explicit consent for hooks and each MCP server.
ECC installs through the canonical installer and receipt lifecycle:
```bash
node scripts/grok-install.js --dry-run --trust --consent-hooks --consent-mcp chrome-devtools
node scripts/grok-install.js --trust --consent-hooks --consent-mcp chrome-devtools
node scripts/repair.js --target grok
node scripts/uninstall.js --target grok
```
`scripts/grok-install.js` is a thin alias for `install-apply.js --target grok
--profile full`; state stays at `~/.grok/ecc/install-state.json`. Upgrade and
rollback use the same canonical apply path; pass `--source-sha <40-hex>` to
reapply a previous pinned commit.
Grok CLI marketplace add / install / enable is **discovery only**.
`grok plugin install --trust` skips Grok's confirmation prompt; it is **not
@@ -39,14 +48,14 @@ grok plugin validate /absolute/path/to/ECC
Do not also run `./install.sh --target claude` (or a full manual Claude copy)
into the same Grok session. Pick one install path per harness.
`./install.sh --target grok` does not copy hooks or root MCP without the same
adapter consent flags.
`./install.sh --target grok --profile full` does not copy hooks or root MCP
without `--trust` and the matching consent flags.
## Hooks
Grok sets `GROK_PLUGIN_ROOT`. Shared hook code does not read that alias.
`applyInstall` maps it onto `PLUGIN_ROOT` at the installed-tree boundary when
hooks are consented. Without hook consent, the installed copy has no
The Grok target adapter maps it onto the shared root contract in the installed
hook configuration when hooks are consented. Without hook consent, the installed copy has no
`hooks/hooks.json`.
Grok does not honor Claude `userConfig`. Hook enablement and profile stay on
@@ -62,8 +71,8 @@ export ECC_HOOK_PROFILE=standard # minimal | standard | strict
`.grok-plugin/plugin.json` sets `"mcpServers": ""` so a trusted Grok plugin
install does not attach repo-root `.mcp.json` or `chrome-devtools`. That root
file remains for Claude/Codex. Additional connectors stay opt-in via
`mcp-configs/mcp-servers.json`. Adapter receipts still require explicit
per-server consent before any MCP name is copied into an install plan.
`mcp-configs/mcp-servers.json`. Canonical receipts record explicit per-server
consent before any MCP name is copied into an install plan.
## Known limits
+1 -1
View File
@@ -9,7 +9,7 @@
{
"name": "ecc",
"description": "Harness-native ECC operator layer - 68 agents, 286 skills, 94 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Grok Build, Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
"version": "2.2.0",
"version": "2.2.1",
"author": {
"name": "Affaan Mustafa",
"email": "me@affaanmustafa.com"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ecc",
"version": "2.2.0",
"version": "2.2.1",
"description": "Harness-native ECC plugin for engineering teams - 68 agents, 286 skills, 94 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Grok Build plus adjacent agent harnesses",
"author": {
"name": "Affaan Mustafa",
+1 -1
View File
@@ -11,7 +11,7 @@ Update ECC from its upstream repo and regenerate the current context's managed i
```bash
# Preview the update without mutating anything
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot({probe:p.join('scripts','auto-update.js')})}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot({probe:p.join('scripts','auto-update.js')})}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
# Update only Cursor-managed files in the current project
+1 -1
View File
@@ -18,7 +18,7 @@ while a legacy `~/.claude/skills/continuous-learning-v2/` directory still
exists (#2037).
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status
```
+6 -6
View File
@@ -29,7 +29,7 @@ Use `/sessions info` when you need operator-surface context for a swarm: branch,
**Script:**
```bash
node -e "
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const sm = require(_r + '/scripts/lib/session-manager');
const aa = require(_r + '/scripts/lib/session-aliases');
const path = require('path');
@@ -71,7 +71,7 @@ Load and display a session's content (by ID or alias).
**Script:**
```bash
node -e "
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const sm = require(_r + '/scripts/lib/session-manager');
const aa = require(_r + '/scripts/lib/session-aliases');
const id = process.argv[1];
@@ -145,7 +145,7 @@ Create a memorable alias for a session.
**Script:**
```bash
node -e "
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const sm = require(_r + '/scripts/lib/session-manager');
const aa = require(_r + '/scripts/lib/session-aliases');
@@ -186,7 +186,7 @@ Delete an existing alias.
**Script:**
```bash
node -e "
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const aa = require(_r + '/scripts/lib/session-aliases');
const aliasName = process.argv[1];
@@ -216,7 +216,7 @@ Show detailed information about a session.
**Script:**
```bash
node -e "
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const sm = require(_r + '/scripts/lib/session-manager');
const aa = require(_r + '/scripts/lib/session-aliases');
@@ -267,7 +267,7 @@ Show all session aliases.
**Script:**
```bash
node -e "
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const _r = (function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();
const aa = require(_r + '/scripts/lib/session-aliases');
const aliases = aa.listAliases();
+3 -3
View File
@@ -13,21 +13,21 @@ Shows a comprehensive health dashboard for all skills in the portfolio with succ
Run the skill health CLI in dashboard mode:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard
```
For a specific panel only:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard --panel failures
```
For machine-readable output:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard --json
```
@@ -38,7 +38,7 @@ The matrix below is rendered from
| --- | --- | --- | --- | --- | --- | --- |
| Claude Code | Native | Claude plugin assets; skills; commands; hooks; MCP config; local rules; statusline-oriented workflows | Claude-native hooks do not imply parity in other harnesses | `./install.sh --profile minimal --target claude`; Claude plugin install | `npm run harness:audit -- --format json`; `node scripts/session-inspect.js --list-adapters` | Avoid loading every skill by default; keep hooks opt-in and inspectable. |
| Codex | Instruction-backed | `AGENTS.md`; Codex plugin metadata; skills; MCP reference config; command patterns | Native hook enforcement and Claude slash-command semantics are not equivalent | `./install.sh --profile minimal --target codex`; repo-local `AGENTS.md` review | `npm run harness:audit -- --format json` | Treat hooks as policy text unless a native Codex hook surface exists. |
| Grok Build | Adapter-backed | Grok plugin and marketplace manifests; shared skills; commands; hooks only after adapter hook consent; MCP opted out on the native plugin surface (`mcpServers: ""` and `hooks: ""`) | Claude userConfig hook profiles are ignored; use ECC_HOOK_PROFILE; ECC agent tool allowlists use Claude names and are not mapped in Grok agent frontmatter; No plugin rules/ surface | `node scripts/grok-install.js --dry-run` / `node scripts/grok-install.js` (ECC 2 `grok` install-state under ~/.grok/ecc/); `grok plugin marketplace add affaan-m/ECC && grok plugin install ecc && grok plugin enable ecc` (discovery only; not ECC consent) | `grok plugin validate .`; `node tests/lib/grok-harness-adapter.test.js`; `node tests/plugin-manifest.test.js` | Grok rejects Claude marketplace source "./"; pin a url+sha catalog in .grok-plugin/marketplace.json to a commit that contains the Grok adapter; ECC consent is grok-install.js / applyInstall; native Grok CLI discovery opts hooks and MCP out via empty plugin.json fields; the full skill catalog is large |
| Grok Build | Adapter-backed | Grok plugin and marketplace manifests; shared skills; commands; hooks only after adapter hook consent; MCP opted out on the native plugin surface (`mcpServers: ""` and `hooks: ""`) | Claude userConfig hook profiles are ignored; use ECC_HOOK_PROFILE; ECC agent tool allowlists use Claude names and are not mapped in Grok agent frontmatter; No plugin rules/ surface | `node scripts/grok-install.js --dry-run` / `node scripts/grok-install.js` (canonical `grok` install-state under ~/.grok/ecc/); `grok plugin marketplace add affaan-m/ECC && grok plugin install ecc && grok plugin enable ecc` (discovery only; not ECC consent) | `grok plugin validate .`; `node tests/lib/grok-harness-adapter.test.js`; `node tests/plugin-manifest.test.js` | Grok rejects Claude marketplace source "./"; pin a url+sha catalog in .grok-plugin/marketplace.json to a commit that contains the Grok adapter; ECC consent is the canonical install-apply receipt path; native Grok CLI discovery opts hooks and MCP out via empty plugin.json fields; the full skill catalog is large |
| OpenCode | Adapter-backed | OpenCode package/plugin metadata; shared skills; MCP config; event adapter patterns | Event names, plugin packaging, and command dispatch differ from Claude Code | OpenCode package or plugin surface from this repo | `node tests/scripts/build-opencode.test.js`; `npm run harness:audit -- --format json` | Keep hook logic in shared scripts and adapt only event shape at the edge. |
| Pi | Adapter-backed | Pi package manifest; canonical ECC skills (skills/); canonical ECC commands as prompt templates (commands/); canonical ECC engineering rules (rules/common/) injected into the system prompt; session lifecycle hook adapter; /ecc-doctor diagnostics command; canonical ECC agents (agents/) portable via scripts/agent-convert.js (claude -> pi) | Agent chains, approval prompts, and persistent todos require companion Pi packages and are not part of this adapter (individual agent definitions are portable via scripts/agent-convert.js); Pi core has no MCP surface, though ECC MCP configs load verbatim through the community pi-mcp-adapter package, which ECC neither installs nor depends on | `pi install git:github.com/affaan-m/ECC`; `pi install /path/to/ECC` from a local checkout | `node tests/pi/pi-package-manifest.test.js`; `node tests/pi/pi-extension-adapter.test.js`; `node tests/pi/agent-convert.test.js`; `npm run harness:adapters -- --check` | Pi extensions execute with full user permissions, and hooks run without a shell and resolve from the installed package rather than the user project; Keep canonical skills and commands as the single source of truth, and never generate copies under .pi/ |
| Cursor | Adapter-backed | Cursor rules; project-local skills; hook adapter; shared scripts | Cursor hook events and rule loading differ from Claude Code | `./install.sh --profile minimal --target cursor` | `node tests/lib/install-targets.test.js`; `npm run harness:audit -- --format json` | Cursor adapters must preserve existing project rules and avoid silent overwrite. |
+1 -1
View File
@@ -11,7 +11,7 @@ ECCをアップストリームリポジトリから更新し、元のインス
```bash
# 何も変更せずに更新をプレビュー
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot({probe:p.join('scripts','auto-update.js')})}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot({probe:p.join('scripts','auto-update.js')})}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
# 現在のプロジェクトのCursor管理ファイルのみ更新
+1 -1
View File
@@ -18,7 +18,7 @@ command: true
残っているときに発生するパスの分岐を回避します (#2037)。
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status
```
+3 -3
View File
@@ -13,21 +13,21 @@ command: true
ダッシュボードモードでスキルヘルスCLIを実行:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard
```
特定のパネルのみ:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard --panel failures
```
機械読み取り可能な出力:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard --json
```
+1 -1
View File
@@ -18,7 +18,7 @@ Bu, `CLAUDE_PLUGIN_ROOT` ayarlanmamışken eski bir
yol sapmasını önler (#2037).
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status
```
+8 -8
View File
@@ -29,8 +29,8 @@ Bir swarm için operatör-yüzey context'e ihtiyacınız olduğunda `/sessions i
**Script:**
```bash
node -e "
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const path = require('path');
const result = sm.getAllSessions({ limit: 20 });
@@ -70,8 +70,8 @@ Session içeriğini yükle ve göster (ID veya alias ile).
**Script:**
```bash
node -e "
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const id = process.argv[1];
// Önce alias olarak çözümlemeyi dene
@@ -143,8 +143,8 @@ Session için akılda kalıcı bir alias oluştur.
**Script:**
```bash
node -e "
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sessionId = process.argv[1];
const aliasName = process.argv[2];
@@ -183,7 +183,7 @@ Mevcut bir alias'ı sil.
**Script:**
```bash
node -e "
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aliasName = process.argv[1];
if (!aliasName) {
@@ -222,7 +222,7 @@ Tüm session aliaslarını göster.
**Script:**
```bash
node -e "
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aliases = aa.listAliases();
console.log('Session Aliases (' + aliases.length + '):');
+1 -1
View File
@@ -11,7 +11,7 @@ disable-model-invocation: true
```bash
# Preview the update without mutating anything
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot({probe:p.join('scripts','auto-update.js')})}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot({probe:p.join('scripts','auto-update.js')})}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
# Update only Cursor-managed files in the current project
+1 -1
View File
@@ -16,7 +16,7 @@ command: true
`~/.claude/skills/continuous-learning-v2/` 目录仍然存在时发生的路径分歧 (#2037)。
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status
```
+10 -10
View File
@@ -30,8 +30,8 @@ description: 管理Claude Code会话历史、别名和会话元数据。
```bash
node -e "
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const path = require('path');
const result = sm.getAllSessions({ limit: 20 });
@@ -72,8 +72,8 @@ for (const s of result.sessions) {
```bash
node -e "
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const id = process.argv[1];
// First try to resolve as alias
@@ -146,8 +146,8 @@ if (session.metadata.worktree) {
```bash
node -e "
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sessionId = process.argv[1];
const aliasName = process.argv[2];
@@ -187,7 +187,7 @@ if (result.success) {
```bash
node -e "
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aliasName = process.argv[1];
if (!aliasName) {
@@ -217,8 +217,8 @@ if (result.success) {
```bash
node -e "
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const sm = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-manager');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const id = process.argv[1];
const resolved = aa.resolveAlias(id);
@@ -268,7 +268,7 @@ if (aliases.length > 0) {
```bash
node -e "
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()+'/scripts/lib/session-aliases');
const aliases = aa.listAliases();
console.log('Session Aliases (' + aliases.length + '):');
+3 -3
View File
@@ -13,21 +13,21 @@ command: true
在仪表盘模式下运行技能健康 CLI:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard
```
仅针对特定面板:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard --panel failures
```
获取机器可读输出:
```bash
ECC_ROOT="${GROK_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}}"
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
node "$ECC_ROOT/scripts/skills-health.js" --dashboard --json
```
-6
View File
@@ -105,12 +105,6 @@ Remove or comment out the hook entry in `hooks.json`. If installed as a plugin,
}
```
Grok Build plugin-root layout and `GROK_PLUGIN_ROOT` live in
`scripts/lib/grok-harness-adapter.js`. Shared hook runners stay on
`PLUGIN_ROOT` / `CLAUDE_PLUGIN_ROOT` / `ECC_PLUGIN_ROOT`. Grok does not honor
Claude `userConfig`; use the runtime variables below (`ECC_HOOK_PROFILE`,
`ECC_HOOKS_ENABLED`) after explicit hook consent.
### Runtime Hook Controls (Recommended)
Use environment variables to control hook behavior without editing `hooks.json`:
+1 -1
View File
@@ -7,7 +7,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"if(!process.env.PLUGIN_ROOT)throw new Error('Missing Codex PLUGIN_ROOT');process.env.CLAUDE_PLUGIN_ROOT=process.env.PLUGIN_ROOT;const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/session-start-bootstrap.js"
"command": "node -e \"if(!process.env.PLUGIN_ROOT)throw new Error('Missing Codex PLUGIN_ROOT');process.env.CLAUDE_PLUGIN_ROOT=process.env.PLUGIN_ROOT;const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/session-start-bootstrap.js"
}
],
"description": "Load previous context and detect package manager on new session",
+23 -23
View File
@@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/pre-bash-dispatcher.js"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/pre-bash-dispatcher.js"
}
]
},
@@ -25,7 +25,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:write:doc-file-warning scripts/hooks/doc-file-warning.js standard,strict"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:write:doc-file-warning scripts/hooks/doc-file-warning.js standard,strict"
}
]
},
@@ -34,7 +34,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:edit-write:suggest-compact scripts/hooks/suggest-compact.js standard,strict"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:edit-write:suggest-compact scripts/hooks/suggest-compact.js standard,strict"
}
]
},
@@ -43,7 +43,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:observe scripts/hooks/observe-runner.js standard,strict",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:observe scripts/hooks/observe-runner.js standard,strict",
"async": true,
"timeout": 10
}
@@ -54,7 +54,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:governance-capture scripts/hooks/governance-capture.js standard,strict",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:governance-capture scripts/hooks/governance-capture.js standard,strict",
"timeout": 10
}
]
@@ -64,7 +64,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:config-protection scripts/hooks/config-protection.js standard,strict",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:config-protection scripts/hooks/config-protection.js standard,strict",
"timeout": 5
}
]
@@ -93,7 +93,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:mcp-health-check scripts/hooks/mcp-health-check.js standard,strict"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:mcp-health-check scripts/hooks/mcp-health-check.js standard,strict"
}
]
}
@@ -103,7 +103,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:edit-write:gateguard-fact-force scripts/hooks/gateguard-fact-force.js standard,strict",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:edit-write:gateguard-fact-force scripts/hooks/gateguard-fact-force.js standard,strict",
"timeout": 5
}
]
@@ -115,7 +115,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:compact scripts/hooks/pre-compact.js standard,strict"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js pre:compact scripts/hooks/pre-compact.js standard,strict"
}
]
}
@@ -126,7 +126,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/session-start-bootstrap.js"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/session-start-bootstrap.js"
}
]
},
@@ -135,7 +135,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js session-start:plan-canvas-sessions scripts/hooks/plan-canvas-sessions.js standard,strict"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js session-start:plan-canvas-sessions scripts/hooks/plan-canvas-sessions.js standard,strict"
}
]
}
@@ -146,7 +146,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.env.ECC_POSTTOOLUSE_PASSTHROUGH='1';process.argv.splice(1,0,s);require(s).cli()\" sync",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.env.ECC_POSTTOOLUSE_PASSTHROUGH='1';process.argv.splice(1,0,s);require(s).cli()\" sync",
"timeout": 30
}
]
@@ -156,7 +156,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.env.ECC_POSTTOOLUSE_PASSTHROUGH='1';process.argv.splice(1,0,s);require(s).cli()\" async",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/posttooluse-dispatcher.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.env.ECC_POSTTOOLUSE_PASSTHROUGH='1';process.argv.splice(1,0,s);require(s).cli()\" async",
"async": true,
"timeout": 45
}
@@ -169,7 +169,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js post:mcp-health-check scripts/hooks/mcp-health-check.js standard,strict"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js post:mcp-health-check scripts/hooks/mcp-health-check.js standard,strict"
}
]
},
@@ -178,7 +178,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js post:skill:track scripts/hooks/skill-run-tracker.js standard,strict"
"command": "node -e \"const p=require('path');const r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const s=p.join(r,'scripts/hooks/plugin-hook-bootstrap.js');process.env.CLAUDE_PLUGIN_ROOT=r;process.argv.splice(1,0,s);require(s)\" node scripts/hooks/run-with-flags.js post:skill:track scripts/hooks/skill-run-tracker.js standard,strict"
}
]
}
@@ -189,7 +189,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:plan-canvas-pending','scripts/hooks/plan-canvas-pending.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\""
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:plan-canvas-pending','scripts/hooks/plan-canvas-pending.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\""
}
]
},
@@ -198,7 +198,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:format-typecheck','scripts/hooks/stop-format-typecheck.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:300000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:format-typecheck','scripts/hooks/stop-format-typecheck.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:300000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"timeout": 300
}
]
@@ -208,7 +208,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:check-console-log','scripts/hooks/check-console-log.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\""
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:check-console-log','scripts/hooks/check-console-log.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\""
}
]
},
@@ -217,7 +217,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:session-end','scripts/hooks/session-end.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:session-end','scripts/hooks/session-end.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"async": true,
"timeout": 10
}
@@ -228,7 +228,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:evaluate-session','scripts/hooks/evaluate-session.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:evaluate-session','scripts/hooks/evaluate-session.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"async": true,
"timeout": 10
}
@@ -239,7 +239,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:cost-tracker','scripts/hooks/cost-tracker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:cost-tracker','scripts/hooks/cost-tracker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"async": true,
"timeout": 10
}
@@ -250,7 +250,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:desktop-notify','scripts/hooks/desktop-notify.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const finish=(out,err,code)=>{let pending=1;const done=()=>{pending-=1;if(pending===0)process.exit(code);};if(out){pending+=1;process.stdout.write(out,done);}if(err){pending+=1;process.stderr.write(err,done);}process.nextTick(done);};const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'stop:desktop-notify','scripts/hooks/desktop-notify.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000,maxBuffer:16*1024*1024});const failed=result.error||result.status===null||result.signal;const stdout=!failed&&typeof result.stdout==='string'?result.stdout:'';let stderr=typeof result.stderr==='string'?result.stderr:'';let code=Number.isInteger(result.status)?result.status:0;if(failed){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');stderr+='[Stop] ERROR: hook runner failed: '+reason+String.fromCharCode(10);code=1;}finish(stdout,stderr,code);}else{finish(raw,'[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10),0);}\"",
"async": true,
"timeout": 10
}
@@ -263,7 +263,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'session:end:marker','scripts/hooks/session-end-marker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});const stdout=typeof result.stdout==='string'?result.stdout:'';if(stdout)process.stdout.write(stdout);else process.stdout.write(raw);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[SessionEnd] ERROR: hook runner failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);}process.stderr.write('[SessionEnd] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);\"",
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const rel=path.join('scripts','hooks','run-with-flags.js');const root=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'session:end:marker','scripts/hooks/session-end-marker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});const stdout=typeof result.stdout==='string'?result.stdout:'';if(stdout)process.stdout.write(stdout);else process.stdout.write(raw);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[SessionEnd] ERROR: hook runner failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);}process.stderr.write('[SessionEnd] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);\"",
"async": true,
"timeout": 10
}
+1
View File
@@ -33,6 +33,7 @@
"hermes",
"openclaw",
"kimi",
"grok",
"adal"
]
},
+16
View File
@@ -114,6 +114,16 @@
"declined",
null
]
},
"trust": {
"type": "boolean"
},
"consentMcp": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
}
},
@@ -156,6 +166,12 @@
"null"
]
},
"repoUrl": {
"type": [
"string",
"null"
]
},
"repoCommit": {
"type": [
"string",
-36
View File
@@ -25,8 +25,6 @@ const DOCS_ZH_CN_README_PATH = path.join(ROOT, 'docs', 'zh-CN', 'README.md');
const DOCS_ZH_CN_AGENTS_PATH = path.join(ROOT, 'docs', 'zh-CN', 'AGENTS.md');
const PLUGIN_JSON_PATH = path.join(ROOT, '.claude-plugin', 'plugin.json');
const MARKETPLACE_JSON_PATH = path.join(ROOT, '.claude-plugin', 'marketplace.json');
const GROK_PLUGIN_JSON_PATH = path.join(ROOT, '.grok-plugin', 'plugin.json');
const GROK_MARKETPLACE_JSON_PATH = path.join(ROOT, '.grok-plugin', 'marketplace.json');
const WRITE_MODE = process.argv.includes('--write');
const OUTPUT_MODE = process.argv.includes('--md')
@@ -649,8 +647,6 @@ function createDocumentSpecs(paths = {}) {
zhDocsAgentsPath = DOCS_ZH_CN_AGENTS_PATH,
pluginJsonPath = PLUGIN_JSON_PATH,
marketplaceJsonPath = MARKETPLACE_JSON_PATH,
grokPluginJsonPath = GROK_PLUGIN_JSON_PATH,
grokMarketplaceJsonPath = GROK_MARKETPLACE_JSON_PATH,
} = paths;
return [
@@ -719,36 +715,6 @@ function createDocumentSpecs(paths = {}) {
(parsed, description) => { parsed.plugins[0].description = description; }
),
},
{
filePath: grokPluginJsonPath,
parseExpectations: content => parseCatalogDescriptionExpectations(
content,
'.grok-plugin/plugin.json description',
parsed => parsed.description
),
syncContent: (content, catalog) => syncCatalogDescription(
content,
catalog,
'.grok-plugin/plugin.json description',
parsed => parsed.description,
(parsed, description) => { parsed.description = description; }
),
},
{
filePath: grokMarketplaceJsonPath,
parseExpectations: content => parseCatalogDescriptionExpectations(
content,
'.grok-plugin/marketplace.json plugin description',
parsed => parsed.plugins?.[0]?.description
),
syncContent: (content, catalog) => syncCatalogDescription(
content,
catalog,
'.grok-plugin/marketplace.json plugin description',
parsed => parsed.plugins?.[0]?.description,
(parsed, description) => { parsed.plugins[0].description = description; }
),
},
];
}
@@ -763,8 +729,6 @@ function createDocumentSpecsForRoot(root) {
zhDocsAgentsPath: path.join(root, 'docs', 'zh-CN', 'AGENTS.md'),
pluginJsonPath: path.join(root, '.claude-plugin', 'plugin.json'),
marketplaceJsonPath: path.join(root, '.claude-plugin', 'marketplace.json'),
grokPluginJsonPath: path.join(root, '.grok-plugin', 'plugin.json'),
grokMarketplaceJsonPath: path.join(root, '.grok-plugin', 'marketplace.json'),
});
}
+20 -108
View File
@@ -2,126 +2,38 @@
'use strict';
const path = require('path');
const {
parseMcpConsentList,
resolveHomeDir,
runGrokInstall,
} = require('./lib/grok-harness-adapter');
const { spawnSync } = require('child_process');
const IGNORED_VALUE_FLAGS = new Set([
'--target',
'--profile',
'--modules',
'--with',
'--without',
'--config',
'--locale',
'--skill',
'--skills',
]);
const SELECTION_FLAGS = new Set(['--profile', '--modules', '--with', '--skill', '--skills', '--config']);
function printHelp() {
console.log(`Usage: node scripts/grok-install.js [--dry-run] [--json] [--trust] [--consent-hooks] [--consent-mcp <name,name>]
ECC trusted Grok install. This is the consent/receipt path.
Grok CLI marketplace add/install/enable is discovery only.
Options:
--dry-run Preview the plan without copying files
--json Emit JSON
--trust Mark the plan trusted (still requires per-capability consent)
--consent-hooks Consent to hooks
--consent-mcp Comma-separated MCP server names to attach
--help Show this help
`);
}
function parseArgs(argv) {
const parsed = {
dryRun: false,
json: false,
trust: false,
help: false,
consent: { hooks: false, mcp: {} },
};
for (let index = 2; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === '--dry-run') {
parsed.dryRun = true;
} else if (arg === '--json') {
parsed.json = true;
} else if (arg === '--trust') {
parsed.trust = true;
} else if (arg === '--consent-hooks') {
parsed.consent.hooks = true;
} else if (arg === '--consent-mcp') {
parsed.consent.mcp = parseMcpConsentList(argv[index + 1] || '');
function canonicalArgs(argv = process.argv.slice(2)) {
const args = [];
for (let index = 0; index < argv.length; index += 1) {
if (argv[index] === '--target') {
index += 1;
} else if (IGNORED_VALUE_FLAGS.has(arg)) {
index += 1;
} else if (arg === '--help' || arg === '-h') {
parsed.help = true;
} else {
throw new Error(`Unknown argument: ${arg}`);
continue;
}
args.push(argv[index]);
}
return parsed;
}
function printHuman(result) {
const plan = result.plan;
console.log(result.dryRun ? 'Grok adapter dry-run plan\n' : 'Grok adapter install\n');
console.log(`Identity: ${plan.identity}`);
console.log(`Trust: ${plan.trust}`);
console.log(`Hooks enabled: ${plan.hooksEnabled}`);
console.log(`MCP attached: ${plan.mcpAttached.join(', ') || '(none)'}`);
console.log(`Chrome DevTools: ${plan.attachChromeDevtools}`);
console.log(`Native MCP opted out: ${result.nativeMcpOptedOut}`);
console.log(`Native hooks opted out: ${result.nativeHooksOptedOut}`);
if (result.receipt) {
console.log(`Receipt: ${result.receipt.id}`);
console.log(`Install-state: ${result.receipt.installStatePath}`);
console.log(`Installed root: ${result.receipt.installedRoot}`);
if (!args.some((argument) => SELECTION_FLAGS.has(argument)) && !args.includes('--help') && !args.includes('-h')) {
args.unshift('--profile', 'full');
}
return ['--target', 'grok', ...args];
}
function main() {
let parsed;
try {
parsed = parseArgs(process.argv);
} catch (error) {
process.stderr.write(`Error: ${error.message}\n`);
printHelp();
process.exit(1);
}
if (parsed.help) {
printHelp();
return;
}
const result = runGrokInstall({
dryRun: parsed.dryRun,
trust: parsed.trust,
consent: parsed.consent,
homeDir: resolveHomeDir(),
repoRoot: path.join(__dirname, '..'),
});
if (parsed.json) {
console.log(JSON.stringify(result, null, 2));
return;
}
printHuman(result);
const result = spawnSync(
process.execPath,
[path.join(__dirname, 'install-apply.js'), ...canonicalArgs()],
{ stdio: 'inherit' }
);
if (result.error) throw result.error;
process.exitCode = result.status === null ? 1 : result.status;
}
if (require.main === module) {
main();
}
if (require.main === module) main();
module.exports = {
parseArgs,
canonicalArgs,
main,
};
-4
View File
@@ -13,9 +13,6 @@ function getPluginRoot(options = {}) {
if (options.pluginRoot && String(options.pluginRoot).trim()) {
return String(options.pluginRoot).trim();
}
if (process.env.GROK_PLUGIN_ROOT && process.env.GROK_PLUGIN_ROOT.trim()) {
return process.env.GROK_PLUGIN_ROOT.trim();
}
if (process.env.CLAUDE_PLUGIN_ROOT && process.env.CLAUDE_PLUGIN_ROOT.trim()) {
return process.env.CLAUDE_PLUGIN_ROOT.trim();
}
@@ -136,7 +133,6 @@ function run(raw, options = {}) {
encoding: 'utf8',
env: {
...process.env,
GROK_PLUGIN_ROOT: pluginRoot,
CLAUDE_PLUGIN_ROOT: pluginRoot,
ECC_PLUGIN_ROOT: pluginRoot
},
+1 -3
View File
@@ -173,7 +173,6 @@ function spawnNode(rootDir, relPath, raw, args, options = {}) {
ensureAgentDataHomeEnv();
const hookEnv = {
...process.env,
GROK_PLUGIN_ROOT: rootDir,
CLAUDE_PLUGIN_ROOT: rootDir,
ECC_PLUGIN_ROOT: rootDir,
ECC_HOOK_INPUT_MAX_BYTES: String(options.maxStdin),
@@ -207,7 +206,6 @@ function spawnShell(rootDir, relPath, raw, args, options = {}) {
ensureAgentDataHomeEnv();
const hookEnv = {
...process.env,
GROK_PLUGIN_ROOT: rootDir,
CLAUDE_PLUGIN_ROOT: rootDir,
ECC_PLUGIN_ROOT: rootDir,
ECC_HOOK_INPUT_MAX_BYTES: String(options.maxStdin),
@@ -268,7 +266,7 @@ async function main() {
contextScanner.push(contextDecoder.end());
const hookContext = contextScanner.context;
const rootDir = normalizePluginRootForPlatform(
process.env.GROK_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT || process.env.ECC_PLUGIN_ROOT
process.env.CLAUDE_PLUGIN_ROOT || process.env.ECC_PLUGIN_ROOT
);
if (!mode || !relPath || !rootDir) {
+1 -1
View File
@@ -69,7 +69,7 @@ const ASYNC_HOOKS = [
];
function getPluginRoot(env = process.env) {
return env.GROK_PLUGIN_ROOT || env.CLAUDE_PLUGIN_ROOT || env.ECC_PLUGIN_ROOT || path.resolve(__dirname, '..', '..');
return env.CLAUDE_PLUGIN_ROOT || env.ECC_PLUGIN_ROOT || path.resolve(__dirname, '..', '..');
}
function matchesTool(matcher, toolName) {
-4
View File
@@ -142,9 +142,6 @@ function truncatedInputResult(hookId, maxStdin) {
}
function getPluginRoot() {
if (process.env.GROK_PLUGIN_ROOT && process.env.GROK_PLUGIN_ROOT.trim()) {
return process.env.GROK_PLUGIN_ROOT;
}
if (process.env.CLAUDE_PLUGIN_ROOT && process.env.CLAUDE_PLUGIN_ROOT.trim()) {
return process.env.CLAUDE_PLUGIN_ROOT;
}
@@ -322,7 +319,6 @@ async function main() {
encoding: 'utf8',
env: {
...process.env,
GROK_PLUGIN_ROOT: pluginRoot,
CLAUDE_PLUGIN_ROOT: pluginRoot,
ECC_PLUGIN_ROOT: pluginRoot,
ECC_HOOK_ID: hookId,
+13 -58
View File
@@ -21,16 +21,6 @@ const { getComputeSponsorCopy } = require('./lib/compute-sponsor');
const { stripAnsi } = require('./lib/utils');
const { describeMissingDependencyError } = require('./lib/missing-dependency');
function argvTarget(argv) {
const args = argv.slice(2);
for (let index = 0; index < args.length; index += 1) {
if (args[index] === '--target') {
return args[index + 1] || null;
}
}
return null;
}
function getHelpText() {
const languages = listLegacyCompatibilityLanguages();
const locales = listSupportedLocales();
@@ -58,7 +48,7 @@ Targets:
zed - Install project settings, commands, agents, skills, and flattened rules into ./.zed/
hermes - Install shared rules/skills/commands into ~/.hermes/
kimi - Install Kimi Code project instructions, skills, and MCP config into ./.kimi-code/ (ECC hooks not configured)
grok - ECC Grok adapter install (previewInstall/applyInstall). Discovery-only Grok CLI is separate.
grok - Install through the canonical plan/apply/receipt lifecycle into ~/.grok/plugins/ecc/
openclaw - Install shared rules/skills/commands into ~/.openclaw/
adal - Install shared rules/skills/commands into ./.adal/
@@ -75,6 +65,10 @@ Options:
--enable-hooks Confirm installing the automatic hook runtime (required
when the selected profile/modules materialize hooks)
--no-hooks Install everything except the automatic hook runtime
--trust Trust executable Grok capabilities (still requires each consent flag)
--consent-hooks Allow Grok hooks for this install
--consent-mcp <ids> Allow named Grok MCP servers, comma-separated
--source-sha <sha> Install or roll back Grok to an exact 40-character Git commit
--dry-run Show the install plan without copying files
--json Emit machine-readable plan/result JSON
--help Show this help text
@@ -166,46 +160,8 @@ function printHumanPlan(plan, dryRun) {
}
async function main() {
let rawPlan = null;
try {
if (argvTarget(process.argv) === 'grok') {
const { parseArgs } = require('./grok-install');
const { parseMcpConsentList, resolveHomeDir, runGrokInstall } = require('./lib/grok-harness-adapter');
const parsed = parseArgs(process.argv);
if (parsed.help) {
showHelp(0);
}
const grokResult = runGrokInstall({
dryRun: parsed.dryRun === true,
trust: parsed.trust === true || process.env.ECC_GROK_TRUST === '1',
consent: {
hooks: parsed.consent.hooks === true || process.env.ECC_GROK_CONSENT_HOOKS === '1',
mcp: {
...parseMcpConsentList(process.env.ECC_GROK_CONSENT_MCP || ''),
...parsed.consent.mcp,
},
},
homeDir: resolveHomeDir(),
repoRoot: process.cwd(),
});
const options = { dryRun: parsed.dryRun, json: parsed.json };
if (options.json) {
console.log(JSON.stringify({ dryRun: options.dryRun === true, grok: grokResult }, null, 2));
} else {
console.log(options.dryRun ? 'Grok adapter dry-run plan\n' : 'Grok adapter install\n');
console.log(`Identity: ${grokResult.plan.identity}`);
console.log(`Trust: ${grokResult.plan.trust}`);
console.log(`Hooks enabled: ${grokResult.plan.hooksEnabled}`);
console.log(`MCP attached: ${grokResult.plan.mcpAttached.join(', ') || '(none)'}`);
console.log(`Chrome DevTools: ${grokResult.plan.attachChromeDevtools}`);
if (grokResult.receipt) {
console.log(`Install-state: ${grokResult.receipt.installStatePath}`);
}
console.log('\nConsent flags: ECC_GROK_TRUST=1 ECC_GROK_CONSENT_HOOKS=1 ECC_GROK_CONSENT_MCP=name,name');
console.log('Or: node scripts/grok-install.js --dry-run --trust --consent-hooks');
}
return;
}
const options = parseInstallArgs(process.argv);
if (options.help) {
@@ -232,7 +188,7 @@ async function main() {
config,
});
const rawPlan = createInstallPlanFromRequest(request, {
rawPlan = createInstallPlanFromRequest(request, {
projectRoot: process.cwd(),
homeDir: process.env.HOME || os.homedir(),
env: process.env,
@@ -267,13 +223,12 @@ async function main() {
printHumanPlan(result, false);
}
} catch (error) {
const missingDependencyMessage = describeMissingDependencyError(error);
process.stderr.write(
missingDependencyMessage
? `Error: ${missingDependencyMessage}\n`
: `Error: ${error.message}${getHelpText()}`
);
process.exit(1);
process.stderr.write(`Error: ${error.message}${getHelpText()}`);
process.exitCode = 1;
} finally {
if (rawPlan && typeof rawPlan.dispose === 'function') {
rawPlan.dispose();
}
}
}
+46 -754
View File
@@ -4,35 +4,13 @@ const fs = require('fs');
const os = require('os');
const path = require('path');
const {
createInstallState,
readInstallState,
writeInstallState,
} = require('./install-state');
const grokHomeTarget = require('./install-targets/grok-home');
const {
isGitWorkTreeRoot,
resolvePinnedGitSha,
copyInstallSource,
assertSourceIdentity,
writePinnedMarketplace,
} = require('./grok-source-identity');
const { SHA_PATTERN } = require('./grok-source-identity');
const GROK_PLUGIN_ROOT_ENV = 'GROK_PLUGIN_ROOT';
const GROK_HOME_DIRNAME = '.grok';
const INSTALLED_PLUGINS_DIR = 'installed-plugins';
const PREVIOUS_STATE_FILE = 'install-state.previous.json';
const SOURCE_IDENTITY_FILE = '.ecc-source.json';
const SHA_PATTERN = /^[0-9a-f]{40}$/;
const HOOKS_CAPABILITY_ID = 'hooks';
const CHROME_DEVTOOLS_MCP = 'chrome-devtools';
const CURRENT_PLUGIN_SLUG = 'ecc';
const PLUGIN_CACHE_SLUGS = [CURRENT_PLUGIN_SLUG, 'everything-claude-code'];
const PLUGIN_ROOT_SEGMENTS = [
[CURRENT_PLUGIN_SLUG],
[`${CURRENT_PLUGIN_SLUG}@${CURRENT_PLUGIN_SLUG}`],
['marketplaces', CURRENT_PLUGIN_SLUG],
];
const DEFAULT_SCRIPT_PROBE = path.join('scripts', 'lib', 'utils.js');
const DEFAULT_SKILL_PROBE = path.join('skills', 'continuous-learning-v2');
const SHARED_PLUGIN_ROOT_KEYS = Object.freeze([
@@ -49,163 +27,29 @@ function grokPluginRootFromEnv(env = {}) {
return trimEnv(env[GROK_PLUGIN_ROOT_ENV]);
}
function hasSharedPluginRoot(env = {}) {
return SHARED_PLUGIN_ROOT_KEYS.some((key) => trimEnv(env[key]));
}
/**
* Map Grok-only env onto the harness-neutral PLUGIN_ROOT without leaving
* GROK_PLUGIN_ROOT for shared hook/root code to select.
*/
function toSharedPluginEnv(env = {}) {
const next = { ...env };
const grokRoot = grokPluginRootFromEnv(env);
delete next[GROK_PLUGIN_ROOT_ENV];
if (grokRoot && !hasSharedPluginRoot(next)) {
if (grokRoot && !SHARED_PLUGIN_ROOT_KEYS.some((key) => trimEnv(next[key]))) {
next.PLUGIN_ROOT = grokRoot;
}
return next;
}
/**
* Grok-facing plugin.json `mcpServers: ""` opts the native trusted plugin
* surface out of repo-root `.mcp.json` (including chrome-devtools).
*/
function nativePluginMcpOptedOut(pluginManifest = {}) {
const value = pluginManifest.mcpServers;
if (value === '' || value === undefined || value === null) {
return true;
}
if (Array.isArray(value)) {
return value.length === 0;
}
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}
return false;
}
function nativeTrustedMcpServerNames(pluginManifest = {}) {
if (nativePluginMcpOptedOut(pluginManifest)) {
return [];
}
if (pluginManifest.mcpServers && typeof pluginManifest.mcpServers === 'object'
&& !Array.isArray(pluginManifest.mcpServers)) {
return Object.keys(pluginManifest.mcpServers);
}
return [];
}
function nativeTrustedInstallAttachesChromeDevtools(pluginManifest = {}) {
return nativeTrustedMcpServerNames(pluginManifest).includes(CHROME_DEVTOOLS_MCP);
}
function readGrokPluginManifest(pluginJsonPath, readFile = (file) => fs.readFileSync(file, 'utf8')) {
return JSON.parse(readFile(pluginJsonPath));
}
function resolveHomeDir(env = process.env) {
const explicit = trimEnv(env.ECC_GROK_HOME);
if (explicit) {
return explicit;
}
return os.homedir();
return trimEnv(env.ECC_GROK_HOME) || os.homedir();
}
function grokHomeDir(homeDir, pathModule = path) {
if (pathModule === path) {
return grokHomeTarget.resolveRoot({ homeDir });
}
return pathModule.join(homeDir, GROK_HOME_DIRNAME);
}
function grokInstallStatePath(homeDir, pathModule = path) {
if (pathModule === path) {
return grokHomeTarget.getInstallStatePath({ homeDir });
}
if (pathModule === path) return grokHomeTarget.getInstallStatePath({ homeDir });
return pathModule.join(homeDir, GROK_HOME_DIRNAME, 'ecc', 'install-state.json');
}
function grokPreviousStatePath(homeDir, pathModule = path) {
return pathModule.join(pathModule.dirname(grokInstallStatePath(homeDir, pathModule)), PREVIOUS_STATE_FILE);
}
function normalizeContainedPath(candidate, pathModule = path) {
const raw = String(candidate || '');
if (!raw) {
return '';
}
return pathModule.normalize(raw);
}
function isContained(candidate, root, pathModule = path) {
const resolvedRoot = pathModule.resolve(normalizeContainedPath(root, pathModule));
const resolvedCandidate = pathModule.resolve(resolvedRoot, normalizeContainedPath(candidate, pathModule));
if (resolvedCandidate === resolvedRoot) {
return true;
}
const prefix = resolvedRoot.endsWith(pathModule.sep)
? resolvedRoot
: resolvedRoot + pathModule.sep;
return resolvedCandidate.startsWith(prefix);
}
function assertRootContainment(candidate, root, pathModule = path) {
if (!isContained(candidate, root, pathModule)) {
throw new Error(`install path escapes Grok root: ${candidate}`);
}
return true;
}
function assertPinnedSource(source) {
if (!source || typeof source !== 'object') {
throw new Error('marketplace source must be an object');
}
if (source.source !== 'url') {
throw new Error('marketplace source must be type url');
}
if (!source.url || typeof source.url !== 'string') {
throw new Error('marketplace source must include a Git URL');
}
if (!SHA_PATTERN.test(source.sha || '')) {
throw new Error('marketplace source must pin a 40-character lowercase commit SHA');
}
return {
source: 'url',
url: source.url,
sha: source.sha,
};
}
function sourceIdentity(source) {
const pinned = assertPinnedSource(source);
return `${pinned.url}@${pinned.sha}`;
}
function readMarketplaceSource(catalogPath, readFile = (file) => fs.readFileSync(file, 'utf8')) {
const catalog = JSON.parse(readFile(catalogPath));
const plugin = catalog && Array.isArray(catalog.plugins) ? catalog.plugins[0] : null;
if (!plugin) {
throw new Error('marketplace catalog has no plugins');
}
return {
name: plugin.name,
version: plugin.version,
source: assertPinnedSource(plugin.source),
};
}
function listMcpCapabilities(mcpConfig) {
const servers = mcpConfig && mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object'
? mcpConfig.mcpServers
: {};
return Object.keys(servers).map((name) => ({
id: name,
kind: 'mcp',
command: servers[name] && servers[name].command,
}));
}
function readJsonIfPresent(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
@@ -215,310 +59,25 @@ function readJsonIfPresent(filePath) {
}
function isCompleteRoot(dir, probe, pathModule = path) {
if (probe) {
return fs.existsSync(pathModule.join(dir, probe));
}
if (probe) return fs.existsSync(pathModule.join(dir, probe));
return fs.existsSync(pathModule.join(dir, DEFAULT_SCRIPT_PROBE))
&& fs.existsSync(pathModule.join(dir, DEFAULT_SKILL_PROBE));
}
function findRootInGrokHome(homeDir, probe, pathModule = path) {
const vendorDir = grokHomeDir(homeDir, pathModule);
if (isCompleteRoot(vendorDir, probe, pathModule)) {
return vendorDir;
}
for (const segments of PLUGIN_ROOT_SEGMENTS) {
const candidate = pathModule.join(vendorDir, 'plugins', ...segments);
if (isCompleteRoot(candidate, probe, pathModule)) {
return candidate;
}
}
try {
for (const slug of PLUGIN_CACHE_SLUGS) {
const cacheBase = pathModule.join(vendorDir, 'plugins', 'cache', slug);
const orgDirs = fs.readdirSync(cacheBase, { withFileTypes: true });
for (const orgEntry of orgDirs) {
if (!orgEntry.isDirectory()) continue;
const orgPath = pathModule.join(cacheBase, orgEntry.name);
let versionDirs;
try {
versionDirs = fs.readdirSync(orgPath, { withFileTypes: true });
} catch {
continue;
}
for (const verEntry of versionDirs) {
if (!verEntry.isDirectory()) continue;
const candidate = pathModule.join(orgPath, verEntry.name);
if (isCompleteRoot(candidate, probe, pathModule)) {
return candidate;
}
}
}
}
} catch {
// Grok plugin cache is optional.
}
try {
const installedBase = pathModule.join(vendorDir, INSTALLED_PLUGINS_DIR);
const installedDirs = fs.readdirSync(installedBase, { withFileTypes: true });
for (const entry of installedDirs) {
if (!entry.isDirectory()) continue;
const candidate = pathModule.join(installedBase, entry.name);
if (isCompleteRoot(candidate, probe, pathModule)) {
return candidate;
}
}
} catch {
// installed-plugins is Grok-specific and optional.
}
return null;
}
function receiptIdFromState(state) {
const sha = state && state.source && state.source.repoCommit
? String(state.source.repoCommit).slice(0, 12)
: 'unknown';
return `ecc-${sha}-${state.installedAt}`;
}
function receiptFromInstallState(state) {
if (!state) {
return null;
}
const copyOp = (state.operations || []).find((operation) => operation.moduleId === 'ecc');
const mcpAttached = (state.operations || [])
.filter((operation) => operation.kind === 'mcp-attach')
.map((operation) => operation.moduleId);
const hooksOp = (state.operations || []).find((operation) => operation.moduleId === 'hooks');
const sourcePin = (state.operations || []).find((operation) => operation.kind === 'source-pin');
const enabled = Array.isArray(state.resolution.selectedModules)
&& state.resolution.selectedModules.includes(CURRENT_PLUGIN_SLUG);
return {
id: receiptIdFromState(state),
pluginId: CURRENT_PLUGIN_SLUG,
version: state.source.repoVersion,
source: {
source: 'url',
url: sourcePin ? sourcePin.destinationPath : '',
sha: state.source.repoCommit,
},
identity: sourcePin
? `${sourcePin.destinationPath}@${state.source.repoCommit}`
: state.source.repoCommit,
installedRoot: copyOp ? copyOp.destinationPath : state.target.root,
enabled,
trust: (state.operations || []).some((operation) => operation.strategy === 'consent-allow'),
capabilities: (state.operations || [])
.filter((operation) => operation.moduleId === 'hooks' || operation.kind === 'mcp-attach' || operation.kind === 'mcp-omit')
.map((operation) => ({
id: operation.moduleId,
kind: operation.moduleId === 'hooks' ? 'hooks' : 'mcp',
consented: operation.strategy === 'consent-allow',
})),
hooksEnabled: Boolean(hooksOp && hooksOp.strategy === 'consent-allow'),
mcpAttached,
previousReceiptId: null,
createdAt: state.installedAt,
operation: (state.operations || []).some((operation) => operation.kind === 'rollback')
? 'rollback'
: (state.operations || []).some((operation) => operation.kind === 'uninstall')
? 'uninstall'
: 'install',
installStatePath: state.target.installStatePath,
schemaVersion: state.schemaVersion,
};
}
function loadCurrentReceipt(homeDir, pathModule = path) {
const statePath = grokInstallStatePath(homeDir, pathModule);
if (!fs.existsSync(statePath)) {
return null;
}
try {
const state = readInstallState(statePath);
const receipt = receiptFromInstallState(state);
const previousPath = grokPreviousStatePath(homeDir, pathModule);
if (fs.existsSync(previousPath)) {
try {
const previous = readInstallState(previousPath);
receipt.previousReceiptId = receiptIdFromState(previous);
} catch {
receipt.previousReceiptId = null;
}
}
return receipt;
} catch {
return null;
}
}
function installDestination(homeDir, plan, pathModule = path) {
const grokRoot = grokHomeDir(homeDir, pathModule);
const dest = pathModule.join(
grokRoot,
INSTALLED_PLUGINS_DIR,
`${plan.pluginId || CURRENT_PLUGIN_SLUG}-${String(plan.source.sha).slice(0, 12)}`
);
assertRootContainment(dest, grokRoot, pathModule);
return dest;
}
function readInstalledSharedEnv(installedRoot, pathModule = path) {
return readJsonIfPresent(pathModule.join(installedRoot, '.grok-plugin', 'shared-env.json'));
}
function listManagedInstallRoots(homeDir, pathModule = path) {
const roots = [];
const base = pathModule.join(grokHomeDir(homeDir, pathModule), INSTALLED_PLUGINS_DIR);
try {
const entries = fs.readdirSync(base, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const installedRoot = pathModule.join(base, entry.name);
if (readJsonIfPresent(pathModule.join(installedRoot, SOURCE_IDENTITY_FILE))) {
roots.push(installedRoot);
}
}
} catch {
return roots;
}
return roots;
}
function planToOperations(plan, dest, pathModule) {
const operations = [
{
kind: 'copy-path',
moduleId: CURRENT_PLUGIN_SLUG,
sourceRelativePath: '.',
destinationPath: dest,
strategy: 'preserve-relative-path',
ownership: 'managed',
scaffoldOnly: false,
},
{
kind: 'source-pin',
moduleId: 'marketplace',
sourceRelativePath: '.grok-plugin/marketplace.json',
destinationPath: plan.source.url,
strategy: 'pin-sha',
ownership: 'managed',
scaffoldOnly: false,
},
{
kind: plan.hooksEnabled ? 'copy-path' : 'omit-path',
moduleId: 'hooks',
sourceRelativePath: 'hooks/hooks.json',
destinationPath: pathModule.join(dest, 'hooks', 'hooks.json'),
strategy: plan.hooksEnabled ? 'consent-allow' : 'consent-deny',
ownership: 'managed',
scaffoldOnly: false,
},
];
for (const capability of plan.capabilities.filter((item) => item.kind === 'mcp')) {
operations.push({
kind: capability.consented ? 'mcp-attach' : 'mcp-omit',
moduleId: capability.id,
sourceRelativePath: '.mcp.json',
destinationPath: pathModule.join(dest, '.mcp.json'),
strategy: capability.consented ? 'consent-allow' : 'consent-deny',
ownership: 'managed',
scaffoldOnly: false,
});
}
return operations;
}
function writeGrokInstallState(homeDir, plan, dest, now, pathModule, extraOperations = []) {
const installStatePath = grokInstallStatePath(homeDir, pathModule);
const operations = planToOperations(plan, dest, pathModule).concat(extraOperations);
const state = createInstallState({
installedAt: now,
adapter: {
id: grokHomeTarget.id,
target: grokHomeTarget.target,
kind: grokHomeTarget.kind,
},
targetRoot: grokHomeDir(homeDir, pathModule),
installStatePath,
request: {
profile: null,
modules: plan.enabled !== false ? [CURRENT_PLUGIN_SLUG] : [],
includeComponents: plan.mcpAttached.slice(),
excludeComponents: plan.hooksEnabled ? [] : ['hooks'],
legacyLanguages: [],
legacyMode: false,
},
resolution: {
selectedModules: plan.enabled !== false ? [CURRENT_PLUGIN_SLUG] : [],
skippedModules: plan.enabled !== false ? [] : [CURRENT_PLUGIN_SLUG],
},
source: {
repoVersion: plan.version || null,
repoCommit: plan.source.sha,
manifestVersion: 1,
},
operations,
});
writeInstallState(installStatePath, state);
return state;
}
function resolveGrokPluginRoot(options = {}) {
const pathModule = options.pathModule || path;
const env = options.env || {};
const envRoot = options.envRoot !== undefined
? trimEnv(options.envRoot)
: grokPluginRootFromEnv(env);
if (envRoot) {
return envRoot;
}
const homeDir = options.homeDir || os.homedir();
const requireEnabled = options.requireEnabled !== false;
const current = loadCurrentReceipt(homeDir, pathModule);
if (requireEnabled && current && current.enabled === false) {
return null;
}
const pinnedSha = options.pinnedSha || (options.source && options.source.sha) || '';
if (pinnedSha) {
const pinned = selectPinnedCachedVersion(
listCachedGrokVersions(homeDir, pathModule),
pinnedSha
);
if (pinned && isCompleteRoot(pinned.installedRoot, options.probe, pathModule)) {
return pinned.installedRoot;
}
}
if (current && current.installedRoot && isCompleteRoot(current.installedRoot, options.probe, pathModule)) {
return current.installedRoot;
}
return findRootInGrokHome(homeDir, options.probe, pathModule);
}
function listCachedGrokVersions(homeDir, pathModule = path) {
const versions = [];
const cacheBase = pathModule.join(grokHomeDir(homeDir, pathModule), 'plugins', 'cache', CURRENT_PLUGIN_SLUG);
try {
const orgDirs = fs.readdirSync(cacheBase, { withFileTypes: true });
for (const orgEntry of orgDirs) {
for (const orgEntry of fs.readdirSync(cacheBase, { withFileTypes: true })) {
if (!orgEntry.isDirectory()) continue;
const orgPath = pathModule.join(cacheBase, orgEntry.name);
const versionDirs = fs.readdirSync(orgPath, { withFileTypes: true });
for (const verEntry of versionDirs) {
if (!verEntry.isDirectory()) continue;
const installedRoot = pathModule.join(orgPath, verEntry.name);
for (const versionEntry of fs.readdirSync(orgPath, { withFileTypes: true })) {
if (!versionEntry.isDirectory()) continue;
const installedRoot = pathModule.join(orgPath, versionEntry.name);
const identity = readJsonIfPresent(pathModule.join(installedRoot, SOURCE_IDENTITY_FILE));
versions.push({
org: orgEntry.name,
version: verEntry.name,
version: versionEntry.name,
installedRoot,
sha: identity && identity.sha,
});
@@ -531,331 +90,64 @@ function listCachedGrokVersions(homeDir, pathModule = path) {
}
function selectPinnedCachedVersion(versions, sha) {
if (!SHA_PATTERN.test(sha || '')) {
return null;
}
if (!SHA_PATTERN.test(sha || '')) return null;
return versions.find((entry) => entry.sha === sha) || null;
}
function capabilityConsented(consent, capability) {
if (!consent || typeof consent !== 'object') {
return false;
function findRootInGrokHome(homeDir, probe, pathModule = path) {
const grokRoot = grokHomeDir(homeDir, pathModule);
const candidates = [
pathModule.join(grokRoot, 'plugins', CURRENT_PLUGIN_SLUG),
pathModule.join(grokRoot, 'plugins', `${CURRENT_PLUGIN_SLUG}@${CURRENT_PLUGIN_SLUG}`),
pathModule.join(grokRoot, 'plugins', 'marketplaces', CURRENT_PLUGIN_SLUG),
];
for (const candidate of candidates) {
if (isCompleteRoot(candidate, probe, pathModule)) return candidate;
}
if (capability.kind === 'hooks') {
return consent.hooks === true;
}
if (capability.kind === 'mcp') {
const mcp = consent.mcp && typeof consent.mcp === 'object' ? consent.mcp : {};
return mcp[capability.id] === true;
}
return false;
}
function previewInstall(options = {}) {
const pathModule = options.pathModule || path;
const source = assertPinnedSource(options.source);
const trust = options.trust === true;
const consent = options.consent && typeof options.consent === 'object' ? options.consent : {};
const mcpCapabilities = listMcpCapabilities(options.mcpConfig || {});
const capabilities = [
{ id: HOOKS_CAPABILITY_ID, kind: 'hooks' },
...mcpCapabilities,
].map((capability) => {
const consented = trust && capabilityConsented(consent, capability);
return {
...capability,
consented,
};
});
const mcpAttached = capabilities
.filter((capability) => capability.kind === 'mcp' && capability.consented)
.map((capability) => capability.id);
const hooksEnabled = capabilities.some(
(capability) => capability.id === HOOKS_CAPABILITY_ID && capability.consented
);
return {
kind: 'grok-install-plan',
pluginId: options.pluginId || CURRENT_PLUGIN_SLUG,
version: options.version || null,
trust,
enabled: options.enabled !== false,
source,
identity: sourceIdentity(source),
capabilities,
hooksEnabled,
mcpAttached,
attachRootMcp: mcpAttached.length > 0,
attachChromeDevtools: mcpAttached.includes(CHROME_DEVTOOLS_MCP),
homeDir: options.homeDir,
sourceRoot: options.sourceRoot || null,
pathModule: pathModule === path.win32 ? 'win32' : 'posix',
};
}
function writeJson(filePath, value) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
}
function filterMcpConfig(mcpConfig, attachedNames) {
const servers = mcpConfig && mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object'
? mcpConfig.mcpServers
: {};
const mcpServers = {};
for (const name of attachedNames) {
if (servers[name]) {
mcpServers[name] = servers[name];
for (const dirname of ['installed-plugins', 'plugins']) {
const base = pathModule.join(grokRoot, dirname);
try {
for (const entry of fs.readdirSync(base, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const candidate = pathModule.join(base, entry.name);
if (isCompleteRoot(candidate, probe, pathModule)) return candidate;
}
} catch {
// Optional native discovery root.
}
}
return { mcpServers };
return null;
}
const SHARED_INLINE_ENV = 'process.env.PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT';
const INSTALLED_INLINE_ENV = `${SHARED_INLINE_ENV}||process.env.GROK_PLUGIN_ROOT`;
function applyInstalledHookBoundary(dest, pathModule) {
const mapped = toSharedPluginEnv({ GROK_PLUGIN_ROOT: dest });
writeJson(pathModule.join(dest, '.grok-plugin', 'shared-env.json'), {
PLUGIN_ROOT: mapped.PLUGIN_ROOT,
});
const destHooks = pathModule.join(dest, 'hooks', 'hooks.json');
if (!fs.existsSync(destHooks)) {
return;
}
const text = fs.readFileSync(destHooks, 'utf8');
fs.writeFileSync(destHooks, text.split(SHARED_INLINE_ENV).join(INSTALLED_INLINE_ENV));
}
function applyInstall(plan, options = {}) {
if (!plan || plan.kind !== 'grok-install-plan') {
throw new Error('applyInstall requires a grok-install-plan');
}
function resolveGrokPluginRoot(options = {}) {
const pathModule = options.pathModule || path;
const homeDir = plan.homeDir || options.homeDir;
if (!homeDir) {
throw new Error('homeDir is required to apply a Grok install');
const envRoot = options.envRoot !== undefined
? trimEnv(options.envRoot)
: grokPluginRootFromEnv(options.env || {});
if (envRoot) return envRoot;
if (options.enabled === false) return null;
const homeDir = options.homeDir || os.homedir();
const pinnedSha = options.pinnedSha || (options.source && options.source.sha) || '';
if (pinnedSha) {
const pinned = selectPinnedCachedVersion(listCachedGrokVersions(homeDir, pathModule), pinnedSha);
if (pinned && isCompleteRoot(pinned.installedRoot, options.probe, pathModule)) {
return pinned.installedRoot;
}
}
const dest = installDestination(homeDir, plan, pathModule);
if (!plan.sourceRoot) {
throw new Error('sourceRoot is required to apply a Grok install');
}
assertSourceIdentity(plan.sourceRoot, plan.source, pathModule);
fs.mkdirSync(dest, { recursive: true });
copyInstallSource(plan.sourceRoot, dest, pathModule, plan.source.sha);
writeJson(pathModule.join(dest, SOURCE_IDENTITY_FILE), plan.source);
writePinnedMarketplace(dest, plan.source, pathModule);
writeJson(
pathModule.join(dest, '.mcp.json'),
filterMcpConfig(options.mcpConfig || plan.mcpConfig, plan.mcpAttached)
);
if (!plan.hooksEnabled) {
fs.rmSync(pathModule.join(dest, 'hooks'), { recursive: true, force: true });
} else {
applyInstalledHookBoundary(dest, pathModule);
}
const now = options.now || new Date().toISOString();
const statePath = grokInstallStatePath(homeDir, pathModule);
const previousPath = grokPreviousStatePath(homeDir, pathModule);
if (fs.existsSync(statePath)) {
fs.mkdirSync(pathModule.dirname(previousPath), { recursive: true });
fs.copyFileSync(statePath, previousPath);
}
writeGrokInstallState(homeDir, plan, dest, now, pathModule);
const receipt = loadCurrentReceipt(homeDir, pathModule);
receipt.operation = fs.existsSync(previousPath) ? 'upgrade' : 'install';
receipt.trust = plan.trust === true;
return receipt;
}
function uninstall(homeDir, options = {}) {
const pathModule = options.pathModule || path;
const current = loadCurrentReceipt(homeDir, pathModule);
if (!current) {
return { operation: 'uninstall', removed: false };
}
const managedRoots = listManagedInstallRoots(homeDir, pathModule);
for (const installedRoot of managedRoots) {
fs.rmSync(installedRoot, { recursive: true, force: true });
}
const statePath = grokInstallStatePath(homeDir, pathModule);
const previousPath = grokPreviousStatePath(homeDir, pathModule);
fs.rmSync(statePath, { force: true });
fs.rmSync(previousPath, { force: true });
return {
...current,
enabled: false,
operation: 'uninstall',
removedRoots: managedRoots,
uninstalledAt: options.now || new Date().toISOString(),
};
}
function rollback(homeDir, options = {}) {
const pathModule = options.pathModule || path;
const current = loadCurrentReceipt(homeDir, pathModule);
const previousPath = grokPreviousStatePath(homeDir, pathModule);
if (!current || !fs.existsSync(previousPath)) {
throw new Error('no previous receipt to roll back to');
}
const previousState = readInstallState(previousPath);
const previousReceipt = receiptFromInstallState(previousState);
if (!previousReceipt.installedRoot || !fs.existsSync(previousReceipt.installedRoot)) {
throw new Error('previous install is not available for rollback');
}
const statePath = grokInstallStatePath(homeDir, pathModule);
fs.copyFileSync(previousPath, statePath);
fs.rmSync(previousPath, { force: true });
if (current.installedRoot && current.installedRoot !== previousReceipt.installedRoot) {
fs.rmSync(current.installedRoot, { recursive: true, force: true });
}
return {
...loadCurrentReceipt(homeDir, pathModule),
operation: 'rollback',
rolledBackFrom: current.id,
};
}
function setPluginEnabled(homeDir, enabled, options = {}) {
const pathModule = options.pathModule || path;
const statePath = grokInstallStatePath(homeDir, pathModule);
if (!fs.existsSync(statePath)) {
throw new Error('no current Grok install to enable or disable');
}
const state = readInstallState(statePath);
const next = createInstallState({
installedAt: state.installedAt,
adapter: {
id: state.target.id,
target: state.target.target,
kind: state.target.kind,
},
targetRoot: state.target.root,
installStatePath: state.target.installStatePath,
request: {
...state.request,
modules: enabled ? [CURRENT_PLUGIN_SLUG] : [],
},
resolution: {
selectedModules: enabled ? [CURRENT_PLUGIN_SLUG] : [],
skippedModules: enabled ? [] : [CURRENT_PLUGIN_SLUG],
},
source: state.source,
operations: state.operations,
});
writeInstallState(statePath, next);
return loadCurrentReceipt(homeDir, pathModule);
}
function parseMcpConsentList(raw) {
const mcp = {};
String(raw || '')
.split(',')
.map((name) => name.trim())
.filter(Boolean)
.forEach((name) => {
mcp[name] = true;
});
return mcp;
}
function resolveRepoRoot(candidate) {
const start = candidate || path.join(__dirname, '..', '..');
try {
const { execFileSync } = require('child_process');
return execFileSync('git', ['-C', start, 'rev-parse', '--show-toplevel'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
} catch {
return start;
}
}
function runGrokInstall(options = {}) {
const repoRoot = resolveRepoRoot(options.repoRoot || path.join(__dirname, '..', '..'));
const homeDir = options.homeDir || resolveHomeDir();
const marketplace = readMarketplaceSource(path.join(repoRoot, '.grok-plugin', 'marketplace.json'));
const pluginManifest = readGrokPluginManifest(path.join(repoRoot, '.grok-plugin', 'plugin.json'));
let mcpConfig = { mcpServers: {} };
try {
mcpConfig = JSON.parse(fs.readFileSync(path.join(repoRoot, '.mcp.json'), 'utf8'));
} catch {
mcpConfig = { mcpServers: {} };
}
const plan = previewInstall({
source: marketplace.source,
version: marketplace.version,
sourceRoot: repoRoot,
homeDir,
trust: options.trust === true,
consent: options.consent && typeof options.consent === 'object' ? options.consent : {},
mcpConfig,
enabled: true,
});
const result = {
kind: 'grok-install-result',
dryRun: options.dryRun === true,
plan,
nativeMcpOptedOut: nativePluginMcpOptedOut(pluginManifest),
nativeHooksOptedOut: pluginManifest.hooks === '',
};
if (options.dryRun) {
return result;
}
result.receipt = applyInstall(plan, { mcpConfig, homeDir });
return result;
return findRootInGrokHome(homeDir, options.probe, pathModule);
}
module.exports = {
GROK_PLUGIN_ROOT_ENV,
GROK_HOME_DIRNAME,
SOURCE_IDENTITY_FILE,
SHA_PATTERN,
HOOKS_CAPABILITY_ID,
CHROME_DEVTOOLS_MCP,
grokPluginRootFromEnv,
toSharedPluginEnv,
nativePluginMcpOptedOut,
nativeTrustedMcpServerNames,
nativeTrustedInstallAttachesChromeDevtools,
readGrokPluginManifest,
resolveHomeDir,
grokHomeDir,
grokInstallStatePath,
isContained,
assertRootContainment,
assertPinnedSource,
sourceIdentity,
readMarketplaceSource,
listMcpCapabilities,
resolveGrokPluginRoot,
listCachedGrokVersions,
selectPinnedCachedVersion,
installDestination,
readInstalledSharedEnv,
listManagedInstallRoots,
planToOperations,
previewInstall,
applyInstall,
uninstall,
rollback,
setPluginEnabled,
loadCurrentReceipt,
parseMcpConsentList,
resolveRepoRoot,
runGrokInstall,
isGitWorkTreeRoot,
resolvePinnedGitSha,
assertSourceIdentity,
};
+152 -46
View File
@@ -2,9 +2,17 @@
const { execFileSync } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const SHA_PATTERN = /^[0-9a-f]{40}$/;
const TEMP_SOURCE_ROOTS = new Set();
process.once('exit', () => {
for (const tempRoot of TEMP_SOURCE_ROOTS) {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
function isGitWorkTreeRoot(dir, pathModule = path) {
try {
@@ -74,65 +82,163 @@ function copyGitArchive(sourceRoot, sha, dest, pathModule = path) {
}
}
function copyInstallSource(sourceRoot, dest, pathModule = path, sha) {
if (isGitWorkTreeRoot(sourceRoot, pathModule)) {
if (!resolvePinnedGitSha(sourceRoot, sha)) {
throw new Error(`pinned SHA ${sha} is not in source git`);
}
copyGitArchive(sourceRoot, sha, dest, pathModule);
return { mode: 'git-archive', sha };
}
fs.cpSync(sourceRoot, dest, { recursive: true });
return { mode: 'tree' };
}
function assertSourceIdentity(sourceRoot, source, pathModule = path) {
if (isGitWorkTreeRoot(sourceRoot, pathModule)) {
if (!resolvePinnedGitSha(sourceRoot, source && source.sha)) {
throw new Error(`pinned SHA ${source && source.sha} is not in source git`);
}
return 'git-archive';
}
function readPinnedMarketplaceSource(sourceRoot, pathModule = path) {
const catalogPath = pathModule.join(sourceRoot, '.grok-plugin', 'marketplace.json');
if (!fs.existsSync(catalogPath)) {
return 'tree';
let catalog;
try {
catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
} catch (error) {
throw new Error(`Failed to read Grok marketplace source at ${catalogPath}: ${error.message}`);
}
const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
const plugin = catalog && Array.isArray(catalog.plugins) ? catalog.plugins[0] : null;
const catalogSha = plugin && plugin.source && plugin.source.sha;
if (catalogSha && catalogSha !== source.sha) {
throw new Error('sourceRoot marketplace sha does not match install plan pin');
const source = plugin && plugin.source;
if (!source || source.source !== 'url' || typeof source.url !== 'string' || !source.url.trim()) {
throw new Error('Grok marketplace source must be a Git URL');
}
return 'tree';
}
function writePinnedMarketplace(dest, source, pathModule = path) {
const catalogPath = pathModule.join(dest, '.grok-plugin', 'marketplace.json');
if (!fs.existsSync(catalogPath)) {
return false;
if (!SHA_PATTERN.test(source.sha || '')) {
throw new Error('Grok marketplace source must pin a 40-character lowercase commit SHA');
}
const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
if (!catalog.plugins || !catalog.plugins[0]) {
return false;
if (typeof plugin.version !== 'string' || !plugin.version.trim()) {
throw new Error('Grok marketplace source must declare a plugin version');
}
catalog.plugins[0].source = {
source: source.source,
return {
url: source.url,
sha: source.sha,
version: plugin.version,
};
fs.mkdirSync(pathModule.dirname(catalogPath), { recursive: true });
fs.writeFileSync(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`);
return true;
}
function normalizeGitLocation(value, pathModule = path) {
const location = String(value || '').trim().replace(/\/+$/, '').replace(/\.git$/, '');
if (/^(?:[a-z][a-z0-9+.-]*:\/\/|git@)/i.test(location)) {
return location;
}
return pathModule.resolve(location);
}
function findRegistrySource(sourceRoot, homeDir, pathModule = path) {
const registryPath = pathModule.join(homeDir, '.grok', 'installed-plugins', 'registry.json');
let registry;
try {
registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
} catch (error) {
if (error.code === 'ENOENT') return null;
throw new Error(`Failed to read Grok plugin registry at ${registryPath}: ${error.message}`);
}
const expectedPath = fs.realpathSync(sourceRoot);
for (const record of Object.values(registry.repos || {})) {
let recordPath;
try {
recordPath = fs.realpathSync(record.path);
} catch {
continue;
}
if (recordPath !== expectedPath || String(record.kind && record.kind.type).toLowerCase() !== 'git') {
continue;
}
return {
url: record.kind.url,
sha: record.kind.commit,
};
}
return null;
}
function fetchPinnedGitSource(sourceUrl, sha, parentDir) {
const gitDir = path.join(parentDir, 'git');
fs.mkdirSync(gitDir, { recursive: true });
execFileSync('git', ['init', '--bare', '--quiet', gitDir], { stdio: 'ignore' });
execFileSync('git', ['-C', gitDir, 'fetch', '--quiet', '--depth=1', '--no-tags', sourceUrl, sha], {
stdio: ['ignore', 'ignore', 'pipe'],
});
const fetched = execFileSync('git', ['-C', gitDir, 'rev-parse', 'FETCH_HEAD^{commit}'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
if (fetched !== sha) {
throw new Error(`Fetched Grok source commit ${fetched} does not match pinned SHA ${sha}`);
}
return gitDir;
}
function assertNoSymlinks(root) {
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
const entryPath = path.join(root, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(`Pinned Grok source contains unsupported symlink: ${entryPath}`);
}
if (entry.isDirectory()) assertNoSymlinks(entryPath);
}
}
function preparePinnedGrokSource(options = {}) {
const sourceRoot = path.resolve(options.sourceRoot || '');
const homeDir = path.resolve(options.homeDir || os.homedir());
const catalogSource = readPinnedMarketplaceSource(sourceRoot);
const sourceUrl = options.sourceUrl || catalogSource.url;
const sourceSha = options.sourceSha || catalogSource.sha;
if (normalizeGitLocation(sourceUrl) !== normalizeGitLocation(catalogSource.url)) {
throw new Error('Recorded Grok source URL does not match the marketplace source');
}
if (!SHA_PATTERN.test(sourceSha || '')) {
throw new Error('Recorded Grok source must pin a 40-character lowercase commit SHA');
}
let gitSourceRoot = null;
if (isGitWorkTreeRoot(sourceRoot) && resolvePinnedGitSha(sourceRoot, sourceSha)) {
gitSourceRoot = sourceRoot;
} else {
const registrySource = findRegistrySource(sourceRoot, homeDir);
if (!registrySource
|| normalizeGitLocation(registrySource.url) !== normalizeGitLocation(sourceUrl)
|| registrySource.sha !== sourceSha) {
throw new Error('Non-Git Grok source is unverifiable without matching registry URL/SHA evidence');
}
}
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-grok-source-'));
TEMP_SOURCE_ROOTS.add(tempRoot);
const snapshotRoot = path.join(tempRoot, 'snapshot');
try {
if (!gitSourceRoot) {
gitSourceRoot = fetchPinnedGitSource(sourceUrl, sourceSha, tempRoot);
}
copyGitArchive(gitSourceRoot, sourceSha, snapshotRoot);
assertNoSymlinks(snapshotRoot);
const snapshotManifest = JSON.parse(
fs.readFileSync(path.join(snapshotRoot, '.grok-plugin', 'plugin.json'), 'utf8')
);
if (snapshotManifest.version !== catalogSource.version) {
throw new Error(
`Pinned Grok plugin version ${snapshotManifest.version || '(missing)'} does not match marketplace version ${catalogSource.version}`
);
}
fs.writeFileSync(path.join(snapshotRoot, '.ecc-source.json'), `${JSON.stringify({
source: 'url',
url: sourceUrl,
sha: sourceSha,
}, null, 2)}\n`);
return {
sourceRoot: snapshotRoot,
sourceUrl,
sourceSha,
sourceVersion: catalogSource.version,
cleanup: () => {
TEMP_SOURCE_ROOTS.delete(tempRoot);
fs.rmSync(tempRoot, { recursive: true, force: true });
},
};
} catch (error) {
fs.rmSync(tempRoot, { recursive: true, force: true });
TEMP_SOURCE_ROOTS.delete(tempRoot);
throw error;
}
}
module.exports = {
SHA_PATTERN,
isGitWorkTreeRoot,
resolvePinnedGitSha,
copyInstallSource,
assertSourceIdentity,
writePinnedMarketplace,
readPinnedMarketplaceSource,
preparePinnedGrokSource,
};
+2 -2
View File
@@ -114,7 +114,7 @@ const ADAPTER_RECORDS = Object.freeze([
'No plugin rules/ surface',
],
install_or_onramp: [
'`node scripts/grok-install.js --dry-run` / `node scripts/grok-install.js` (ECC 2 `grok` install-state under ~/.grok/ecc/)',
'`node scripts/grok-install.js --dry-run` / `node scripts/grok-install.js` (canonical `grok` install-state under ~/.grok/ecc/)',
'`grok plugin marketplace add affaan-m/ECC && grok plugin install ecc && grok plugin enable ecc` (discovery only; not ECC consent)',
],
verification_commands: [
@@ -124,7 +124,7 @@ const ADAPTER_RECORDS = Object.freeze([
],
risk_notes: [
'Grok rejects Claude marketplace source "./"; pin a url+sha catalog in .grok-plugin/marketplace.json to a commit that contains the Grok adapter',
'ECC consent is grok-install.js / applyInstall; native Grok CLI discovery opts hooks and MCP out via empty plugin.json fields; the full skill catalog is large',
'ECC consent is the canonical install-apply receipt path; native Grok CLI discovery opts hooks and MCP out via empty plugin.json fields; the full skill catalog is large',
],
last_verified_at: '2026-09-03',
owner: 'ECC maintainers',
+2 -2
View File
@@ -265,12 +265,12 @@ const HARNESS_CAPABILITIES = deepFreeze([
installMode: 'native-plugin',
guidedReady: false,
availability: 'advanced',
destination: '~/.grok through the ECC Grok install-state adapter',
destination: '~/.grok',
scopes: [scope('native', 'grok', '~/.grok')],
hooks: hooks(
'native-trust',
true,
'ECC Grok installs use previewInstall/applyInstall with per-capability consent. Native Grok CLI --trust opts MCP out via empty mcpServers in .grok-plugin/plugin.json.'
'ECC Grok installs use the canonical plan/apply receipt with per-capability consent. Native Grok CLI --trust opts MCP out via empty mcpServers in .grok-plugin/plugin.json.'
),
aliases: ['grok-build'],
},
+1 -1
View File
@@ -41,7 +41,7 @@ function sanitizeDiagnostic(value) {
function readManagedHookConfig(env = process.env) {
const pluginRoot = String(
env.GROK_PLUGIN_ROOT || env.CLAUDE_PLUGIN_ROOT || env.ECC_PLUGIN_ROOT || ''
env.CLAUDE_PLUGIN_ROOT || env.ECC_PLUGIN_ROOT || ''
).trim();
const configPath = String(env.ECC_HOOK_CONFIG || '').trim()
|| (pluginRoot ? path.join(pluginRoot, 'ecc', 'setup.json') : '');
+3 -1
View File
@@ -552,7 +552,9 @@ function createLegacyCompatInstallPlan(options = {}) {
requestModuleIds: [],
requestIncludeComponentIds: includeComponentIds,
requestExcludeComponentIds: excludeComponentIds,
mode: 'legacy-compat'
mode: 'legacy-compat',
trust: options.trust === true,
consent: options.consent || {},
});
}
+9 -2
View File
@@ -5,7 +5,7 @@ const { getInstallTargetAdapter, planInstallTargetScaffold } = require('./instal
const { resolveInvocationEnvironment } = require('./invocation-environment');
const DEFAULT_REPO_ROOT = path.join(__dirname, '../..');
const SUPPORTED_INSTALL_TARGETS = ['claude', 'claude-project', 'cursor', 'antigravity', 'codex', 'copilot', 'gemini', 'opencode', 'codebuddy', 'joycode', 'qwen', 'zed', 'hermes', 'openclaw', 'kimi', 'adal'];
const SUPPORTED_INSTALL_TARGETS = ['claude', 'claude-project', 'cursor', 'antigravity', 'codex', 'gemini', 'opencode', 'codebuddy', 'joycode', 'qwen', 'zed', 'hermes', 'openclaw', 'kimi', 'adal', 'grok'];
const COMPONENT_FAMILY_PREFIXES = {
baseline: 'baseline:',
language: 'lang:',
@@ -618,6 +618,9 @@ function resolveInstallPlan(options = {}) {
projectRoot: validatedProjectRoot || manifests.repoRoot,
homeDir: validatedHomeDir || os.homedir(),
env: resolveInvocationEnvironment(options),
trust: options.trust === true,
consent: options.consent || {},
sourceSha: options.sourceSha || null,
}
: null;
const targetAdapter = target ? getInstallTargetAdapter(target) : null;
@@ -661,7 +664,8 @@ function resolveInstallPlan(options = {}) {
const supportsTarget = !target
|| (
readModuleTargetsOrThrow(module).includes(target)
(readModuleTargetsOrThrow(module).includes(target)
|| targetAdapter?.acceptsUndeclaredModuleTargets === true)
&& (!targetAdapter || targetAdapter.supportsModule(module, targetPlanningInput))
);
@@ -730,6 +734,9 @@ function resolveInstallPlan(options = {}) {
projectRoot: targetPlanningInput.projectRoot,
homeDir: targetPlanningInput.homeDir,
env: targetPlanningInput.env,
trust: targetPlanningInput.trust,
consent: targetPlanningInput.consent,
sourceSha: targetPlanningInput.sourceSha,
modules: selectedModules,
exemptValidationCodes: options.exemptValidationCodes || [],
})
+14 -2
View File
@@ -132,7 +132,7 @@ function createFallbackValidator() {
validateNoAdditionalProperties(
request,
'/request',
['profile', 'modules', 'includeComponents', 'excludeComponents', 'legacyLanguages', 'legacyMode', 'hookConsent']
['profile', 'modules', 'includeComponents', 'excludeComponents', 'legacyLanguages', 'legacyMode', 'hookConsent', 'trust', 'consentMcp']
);
if (!(Object.prototype.hasOwnProperty.call(request, 'profile') && (request.profile === null || typeof request.profile === 'string'))) {
pushError('/request/profile', 'must be string or null');
@@ -152,6 +152,12 @@ function createFallbackValidator() {
) {
pushError('/request/hookConsent', 'must be enabled, declined, or null');
}
if (request.trust !== undefined && typeof request.trust !== 'boolean') {
pushError('/request/trust', 'must be boolean');
}
if (request.consentMcp !== undefined) {
validateStringArray(request.consentMcp, '/request/consentMcp');
}
}
const resolution = state.resolution;
@@ -167,8 +173,9 @@ function createFallbackValidator() {
if (!source || typeof source !== 'object' || Array.isArray(source)) {
pushError('/source', 'must be object');
} else {
validateNoAdditionalProperties(source, '/source', ['repoVersion', 'repoCommit', 'manifestVersion']);
validateNoAdditionalProperties(source, '/source', ['repoVersion', 'repoUrl', 'repoCommit', 'manifestVersion']);
validateOptionalString(source.repoVersion, '/source/repoVersion');
validateOptionalString(source.repoUrl, '/source/repoUrl');
validateOptionalString(source.repoCommit, '/source/repoCommit');
if (!Number.isInteger(source.manifestVersion) || source.manifestVersion < 1) {
pushError('/source/manifestVersion', 'must be integer >= 1');
@@ -306,6 +313,10 @@ function createInstallState(options) {
hookConsent: Object.prototype.hasOwnProperty.call(options.request, 'hookConsent')
? options.request.hookConsent
: null,
trust: options.request.trust === true,
consentMcp: Array.isArray(options.request.consentMcp)
? [...options.request.consentMcp]
: [],
},
resolution: {
selectedModules: Array.isArray(options.resolution.selectedModules)
@@ -317,6 +328,7 @@ function createInstallState(options) {
},
source: {
repoVersion: options.source.repoVersion || null,
repoUrl: options.source.repoUrl || null,
repoCommit: options.source.repoCommit || null,
manifestVersion: options.source.manifestVersion,
},
+84 -7
View File
@@ -1,5 +1,9 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { preparePinnedGrokSource } = require('../grok-source-identity');
const {
createInstallTargetAdapter,
isForeignPlatformPath,
@@ -19,31 +23,104 @@ function isHooksPath(relativePath) {
return normalized === 'hooks' || normalized.startsWith('hooks/');
}
function readMcpConfig(repoRoot) {
const configPath = path.join(repoRoot, '.mcp.json');
let config;
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (error) {
if (error.code === 'ENOENT') return { mcpServers: {} };
throw new Error(`Failed to parse Grok MCP config at ${configPath}: ${error.message}`);
}
if (!config || typeof config !== 'object' || Array.isArray(config)
|| !config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
throw new Error(`Invalid Grok MCP config at ${configPath}: expected an mcpServers object`);
}
return config;
}
module.exports = createInstallTargetAdapter({
id: 'grok-home',
target: 'grok',
kind: 'home',
acceptsUndeclaredModuleTargets: true,
rootSegments: ['.grok'],
installStatePathSegments: ['ecc', 'install-state.json'],
nativeRootRelativePath: '.grok',
prepareSource(input = {}) {
return preparePinnedGrokSource({
sourceRoot: input.sourceRoot || input.repoRoot,
homeDir: input.homeDir,
sourceUrl: input.sourceUrl,
sourceSha: input.sourceSha,
});
},
planOperations(input = {}, adapter) {
const trust = input.trust === true;
const consent = input.consent && typeof input.consent === 'object' ? input.consent : {};
const allowHooks = trust && consent.hooks === true;
const requestedMcpIds = Object.entries(consent.mcp || {})
.filter(([, allowed]) => allowed === true)
.map(([id]) => id);
const mcpConfig = readMcpConfig(input.repoRoot);
const unknownMcpIds = requestedMcpIds.filter((id) => !Object.hasOwn(mcpConfig.mcpServers, id));
if (unknownMcpIds.length > 0) {
throw new Error(`Unknown Grok MCP capability: ${unknownMcpIds.join(', ')}`);
}
const allowedMcpIds = trust ? requestedMcpIds : [];
const modules = Array.isArray(input.modules) ? input.modules : [];
return modules.flatMap((module) => {
const operations = modules.flatMap((module) => {
const paths = Array.isArray(module.paths) ? module.paths : [];
return paths
.filter((sourceRelativePath) => !isForeignPlatformPath(sourceRelativePath, 'grok'))
// Root .mcp.json attach is owned by applyInstall's per-server filter.
// Root MCP files are emitted only by the adapter's per-server consent operation.
.filter((sourceRelativePath) => !isMcpPath(sourceRelativePath))
.filter((sourceRelativePath) => allowHooks || !isHooksPath(sourceRelativePath))
.map((sourceRelativePath) => adapter.createScaffoldOperation(
module.id,
sourceRelativePath,
input
));
.map((sourceRelativePath) => {
const operation = adapter.createScaffoldOperation(module.id, sourceRelativePath, input);
return {
...operation,
destinationPath: path.join(adapter.resolveRoot(input), 'plugins', 'ecc', sourceRelativePath),
...(normalizeRelativePath(sourceRelativePath) === 'hooks/hooks.json'
? { contentTransform: 'grok-hook-boundary' }
: {}),
};
});
});
const hooksEnabled = allowHooks && operations.some((operation) => isHooksPath(operation.sourceRelativePath));
const pluginRoot = path.join(adapter.resolveRoot(input), 'plugins', 'ecc');
if (hooksEnabled) {
operations.push({
...adapter.createScaffoldOperation('hooks-runtime', 'hooks/hooks.json', input),
destinationPath: path.join(pluginRoot, 'hooks', 'hooks.json'),
contentTransform: 'grok-hook-boundary',
});
}
operations.push({
...adapter.createScaffoldOperation('grok-source-identity', '.ecc-source.json', input),
destinationPath: path.join(pluginRoot, '.ecc-source.json'),
});
if (allowedMcpIds.length > 0) {
operations.push({
...adapter.createScaffoldOperation('grok-mcp', '.mcp.json', input),
destinationPath: path.join(pluginRoot, '.mcp.json'),
contentTransform: 'grok-mcp-consent',
grokMcpIds: allowedMcpIds,
});
}
const manifestOperation = adapter.createScaffoldOperation(
'grok-manifest',
'.grok-plugin/plugin.json',
input
);
operations.push({
...manifestOperation,
destinationPath: path.join(pluginRoot, '.grok-plugin', 'plugin.json'),
contentTransform: 'grok-plugin-consent',
grokHooksEnabled: hooksEnabled,
grokMcpEnabled: allowedMcpIds.length > 0,
});
return operations;
},
});
+8 -2
View File
@@ -318,6 +318,7 @@ function createInstallTargetAdapter(config) {
id: config.id,
target: config.target,
kind: config.kind,
acceptsUndeclaredModuleTargets: config.acceptsUndeclaredModuleTargets === true,
nativeRootRelativePath: config.nativeRootRelativePath || null,
supports(target) {
return target === config.target || target === config.id;
@@ -367,8 +368,13 @@ function createInstallTargetAdapter(config) {
strategy: adapter.determineStrategy(normalizedSourcePath),
});
},
excludesSourcePath(sourceRelativePath) {
return isExcludedSourcePath(sourceRelativePath, config.excludedSourcePaths);
prepareSource(input = {}) {
if (typeof config.prepareSource === 'function') {
return config.prepareSource(input, adapter);
}
return {
sourceRoot: input.sourceRoot || input.repoRoot,
};
},
planOperations(input = {}) {
if (typeof config.planOperations === 'function') {
+3
View File
@@ -60,6 +60,9 @@ function planInstallTargetScaffold(options = {}) {
projectRoot: options.projectRoot || options.repoRoot,
homeDir: options.homeDir,
env: resolveInvocationEnvironment(options),
trust: options.trust === true,
consent: options.consent || {},
sourceSha: options.sourceSha || null,
};
const validationIssues = adapter.validate(planningInput);
const blockingIssues = validationIssues.filter(issue => (
+2 -3
View File
@@ -57,8 +57,7 @@ const {
withoutStaleOperations,
} = require('./stale-operations-reconciliation');
const { buildInstallIndex, rewriteRelativeLinks } = require('./link-rewrite');
const { transformInstallContent } = require('./content-transform');
const { assertScriptBoundary, isScriptBoundary } = require('./opencode-script-boundary');
const { transformCopyFileContent } = require('./content-transform');
function isMarkdownPath(filePath) {
return /\.(md|mdx|markdown)$/i.test(String(filePath || ''));
@@ -910,7 +909,7 @@ function applyInstallPlanLocked(plan, dependencies = {}, settingsLockHeld = fals
&& isMarkdownPath(operation.destinationPath)
);
if (operation.kind === 'copy-file' && (operation.contentTransform || needsLinkRewrite)) {
const transformed = transformInstallContent(
const transformed = transformCopyFileContent(
operation,
fs.readFileSync(operation.sourcePath, 'utf8')
);
+27 -38
View File
@@ -1,59 +1,48 @@
'use strict';
const { stripPluginNamespace } = require('./plugin-namespace');
const { adaptAntigravityAgent } = require('./antigravity-agent');
const { adaptCopilotAgent } = require('./copilot-agent');
const { disableOpenCodeHookPluginRegistration, getDisabledOpenCodePluginContent } = require('./hook-consent');
function rewriteCopilotWorkflowPaths(content, sourceRelativePath) {
if (!/\.md$/i.test(sourceRelativePath)) return content;
// Commands run from the installed project root. Rewrite only the supported
// helper entrypoint, leaving unrelated project scripts and URLs untouched.
return content.replace(/(^|[\s`"'(])scripts\/setup-package-manager\.js\b/g,
'$1.github/ecc/scripts/setup-package-manager.js');
function formatJson(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
function transformInstallContent(operation, content) {
function transformCopyFileContent(operation, content) {
if (!operation.contentTransform) {
return content;
}
if (operation.contentTransform === 'claude-manual-plugin-namespace') {
return stripPluginNamespace(content);
}
if (operation.contentTransform === 'qwen-agent-frontmatter') {
return require('../../qwen-adapt-agents').adaptFrontmatter(content).text;
}
if (operation.contentTransform === 'antigravity-agent-frontmatter') {
return adaptAntigravityAgent(content, operation.sourceRelativePath);
}
if (operation.contentTransform === 'copilot-agent-frontmatter') {
return rewriteCopilotWorkflowPaths(adaptCopilotAgent(content, operation.sourceRelativePath), operation.sourceRelativePath);
if (operation.contentTransform === 'grok-plugin-consent') {
const manifest = JSON.parse(content);
return formatJson({
...manifest,
hooks: operation.grokHooksEnabled ? 'hooks/hooks.json' : '',
mcpServers: operation.grokMcpEnabled ? '.mcp.json' : '',
});
}
if (operation.contentTransform === 'copilot-workflow-paths') {
return rewriteCopilotWorkflowPaths(content, operation.sourceRelativePath);
if (operation.contentTransform === 'grok-hook-boundary') {
return content.split('var e=process.env.CLAUDE_PLUGIN_ROOT;').join(
'var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT;'
);
}
if (operation.contentTransform === 'opencode-disable-ecc-hooks') {
return transformInstallContent({ ...operation, contentTransform: 'opencode-home-skills-path' },
disableOpenCodeHookPluginRegistration(content, operation.sourceRelativePath));
}
if (operation.contentTransform === 'opencode-disable-plugin-entrypoint') {
return getDisabledOpenCodePluginContent();
}
if (operation.contentTransform === 'opencode-home-skills-path') {
if (operation.contentTransform === 'grok-mcp-consent') {
const config = JSON.parse(content);
if (!Array.isArray(config?.skills?.paths) || !config.skills.paths.includes('../skills')) {
return content;
if (!config || typeof config !== 'object' || Array.isArray(config)
|| !config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
throw new Error(`Invalid Grok MCP config: ${operation.sourceRelativePath}`);
}
const installedConfig = {
const allowed = new Set(operation.grokMcpIds || []);
return formatJson({
...config,
skills: {
...config.skills,
paths: config.skills.paths.map(skillPath => skillPath === '../skills' ? './skills' : skillPath),
},
};
return `${JSON.stringify(installedConfig, null, 2)}\n`;
mcpServers: Object.fromEntries(
Object.entries(config.mcpServers).filter(([id]) => allowed.has(id))
),
});
}
throw new Error(`Unknown install content transform: ${operation.contentTransform}`);
}
module.exports = { transformInstallContent };
module.exports = {
transformCopyFileContent,
};
+46 -8
View File
@@ -103,6 +103,9 @@ function buildCopyFileOperation({
destinationPath,
strategy,
contentTransform,
grokHooksEnabled,
grokMcpEnabled,
grokMcpIds,
}) {
// Copilot's agent and workflow transforms are textual. Their directories may
// also contain binary assets and executable helpers, which must keep bytes.
@@ -118,7 +121,10 @@ function buildCopyFileOperation({
strategy,
ownership: 'managed',
scaffoldOnly: false,
...(applicableTransform ? { contentTransform: applicableTransform } : {}),
...(contentTransform ? { contentTransform } : {}),
...(contentTransform === 'grok-plugin-consent' ? { grokHooksEnabled } : {}),
...(contentTransform === 'grok-plugin-consent' ? { grokMcpEnabled } : {}),
...(contentTransform === 'grok-mcp-consent' ? { grokMcpIds: [...grokMcpIds] } : {}),
};
}
@@ -198,6 +204,9 @@ function materializeScaffoldOperation(sourceRoot, operation) {
destinationPath: operation.destinationPath,
strategy: operation.strategy,
contentTransform: operation.contentTransform,
grokHooksEnabled: operation.grokHooksEnabled,
grokMcpEnabled: operation.grokMcpEnabled,
grokMcpIds: operation.grokMcpIds,
})
];
}
@@ -215,6 +224,9 @@ function materializeScaffoldOperation(sourceRoot, operation) {
destinationPath: path.join(operation.destinationPath, relativeFile),
strategy: operation.strategy,
contentTransform: operation.contentTransform,
grokHooksEnabled: operation.grokHooksEnabled,
grokMcpEnabled: operation.grokMcpEnabled,
grokMcpIds: operation.grokMcpIds,
});
});
}
@@ -256,9 +268,20 @@ function dedupeCopyFileOperations(operations) {
}
function createManifestInstallPlan(options = {}) {
const sourceRoot = options.sourceRoot || getSourceRoot();
const requestedSourceRoot = options.sourceRoot || getSourceRoot();
const projectRoot = options.projectRoot || process.cwd();
const target = options.target || 'claude';
const adapter = getInstallTargetAdapter(target);
const preparedSource = adapter.prepareSource({
sourceRoot: requestedSourceRoot,
repoRoot: requestedSourceRoot,
projectRoot,
homeDir: options.homeDir,
env: resolveInvocationEnvironment(options),
sourceUrl: options.sourceUrl || null,
sourceSha: options.sourceSha || null,
});
const sourceRoot = preparedSource.sourceRoot || requestedSourceRoot;
const legacyLanguages = Array.isArray(options.legacyLanguages) ? [...options.legacyLanguages] : [];
const requestProfileId = Object.hasOwn(options, 'requestProfileId') ? options.requestProfileId : options.profileId || null;
const requestModuleIds = Object.hasOwn(options, 'requestModuleIds') ? [...options.requestModuleIds] : Array.isArray(options.moduleIds) ? [...options.moduleIds] : [];
@@ -283,8 +306,10 @@ function createManifestInstallPlan(options = {}) {
excludeComponentIds: options.excludeComponentIds || [],
target,
exemptValidationCodes: options.exemptValidationCodes || [],
trust: options.trust === true,
consent: options.consent || {},
sourceSha: options.sourceSha || null,
});
const adapter = getInstallTargetAdapter(target);
const materializedOperations = plan.operations.flatMap(operation => (
materializeScaffoldOperation(sourceRoot, operation)
)).map(operation => {
@@ -307,8 +332,9 @@ function createManifestInstallPlan(options = {}) {
: materializedOperations
);
const source = {
repoVersion: getPackageVersion(sourceRoot),
repoCommit: getRepoCommit(sourceRoot),
repoVersion: preparedSource.sourceVersion || getPackageVersion(sourceRoot),
repoUrl: preparedSource.sourceUrl || null,
repoCommit: preparedSource.sourceSha || getRepoCommit(sourceRoot),
manifestVersion: getManifestVersion(sourceRoot)
};
const statePreview = createStatePreview({
@@ -321,7 +347,11 @@ function createManifestInstallPlan(options = {}) {
includeComponents: requestIncludeComponentIds,
excludeComponents: requestExcludeComponentIds,
legacyLanguages,
legacyMode: Boolean(options.legacyMode)
legacyMode: Boolean(options.legacyMode),
trust: options.trust === true,
consentMcp: Object.entries(options.consent && options.consent.mcp || {})
.filter(([, allowed]) => allowed === true)
.map(([id]) => id),
},
resolution: {
selectedModules: plan.selectedModuleIds,
@@ -331,7 +361,7 @@ function createManifestInstallPlan(options = {}) {
source
});
return disableUnselectedOpenCodeHooks({
const installPlan = {
mode: options.mode || 'manifest',
sourceRoot,
target,
@@ -357,7 +387,15 @@ function createManifestInstallPlan(options = {}) {
excludedModuleIds: plan.excludedModuleIds,
operations,
statePreview
});
};
if (typeof preparedSource.cleanup === 'function') {
Object.defineProperty(installPlan, 'dispose', {
configurable: false,
enumerable: false,
value: preparedSource.cleanup,
});
}
return installPlan;
}
module.exports = {
+27 -1
View File
@@ -32,6 +32,10 @@ function parseInstallArgs(argv) {
locale: null,
enableHooks: false,
noHooks: false,
trust: false,
consentHooks: false,
consentMcp: [],
sourceSha: null,
};
for (let index = 0; index < args.length; index += 1) {
@@ -76,6 +80,16 @@ function parseInstallArgs(argv) {
parsed.enableHooks = true;
} else if (arg === '--no-hooks') {
parsed.noHooks = true;
} else if (arg === '--trust') {
parsed.trust = true;
} else if (arg === '--consent-hooks') {
parsed.consentHooks = true;
} else if (arg === '--consent-mcp') {
parsed.consentMcp = dedupeStrings((args[index + 1] || '').split(','));
index += 1;
} else if (arg === '--source-sha') {
parsed.sourceSha = args[index + 1] || null;
index += 1;
} else if (arg === '--dry-run') {
parsed.dryRun = true;
} else if (arg === '--json') {
@@ -134,7 +148,16 @@ function normalizeInstallRequest(options = {}) {
...(Array.isArray(options.legacyLanguages) ? options.legacyLanguages : []),
...(Array.isArray(options.languages) ? options.languages : []),
]).map(language => language.toLowerCase()));
const hookConsent = resolveHookConsentFlags(options);
const hookConsent = resolveHookConsentFlags({
...options,
enableHooks: options.enableHooks === true || options.consentHooks === true,
});
const trust = options.trust === true;
const consentMcp = dedupeStrings(options.consentMcp || []);
const sourceSha = options.sourceSha || null;
if (sourceSha && (target !== 'grok' || !/^[0-9a-f]{40}$/.test(sourceSha))) {
throw new Error('--source-sha requires --target grok and a 40-character lowercase commit SHA');
}
if (hookConsent === 'declined' && moduleIds.includes('hooks-runtime')) {
throw new Error('--no-hooks cannot be combined with an explicit hooks-runtime module selection');
}
@@ -167,6 +190,9 @@ function normalizeInstallRequest(options = {}) {
excludeComponentIds,
legacyLanguages,
hookConsent,
trust,
consentMcp,
sourceSha,
configPath: config?.path || options.configPath || null,
};
}
+7
View File
@@ -28,6 +28,13 @@ function createRawInstallPlan(request, options = {}) {
homeDir: options.homeDir,
env: resolveInvocationEnvironment(options),
sourceRoot: options.sourceRoot,
sourceUrl: options.sourceUrl,
sourceSha: options.sourceSha || request.sourceSha,
trust: request.trust,
consent: {
hooks: request.hookConsent === 'enabled',
mcp: Object.fromEntries((request.consentMcp || []).map(id => [id, true])),
},
exemptValidationCodes: options.exemptValidationCodes || [],
});
}
+68 -93
View File
@@ -29,33 +29,75 @@ const PLUGIN_ROOT_SEGMENTS = [
// If that skill is ever renamed, move this sentinel with it.
const DEFAULT_SCRIPT_PROBE = path.join('scripts', 'lib', 'utils.js');
const DEFAULT_SKILL_PROBE = path.join('skills', 'continuous-learning-v2');
const PLUGIN_ROOT_ENV_KEYS = ['GROK_PLUGIN_ROOT', 'CLAUDE_PLUGIN_ROOT', 'ECC_PLUGIN_ROOT'];
function pluginRootFromEnv(env = process.env) {
for (const key of PLUGIN_ROOT_ENV_KEYS) {
const value = env[key];
if (value && String(value).trim()) {
return String(value).trim();
}
}
return '';
}
/**
* Resolve the ECC source root directory.
*
* Tries, in order:
* 1. CLAUDE_PLUGIN_ROOT env var (set by Claude Code for hooks, or by user)
* 2. Standard install location (~/.claude/) — when scripts exist there
* 3. Known plugin roots under ~/.claude/plugins/ (current + legacy slugs)
* 4. Plugin cache auto-detection — scans ~/.claude/plugins/cache/{ecc,everything-claude-code}/
* 5. Fallback to ~/.claude/ (original behaviour)
*
* @param {object} [options]
* @param {string} [options.homeDir] Override home directory (for testing)
* @param {string} [options.envRoot] Override CLAUDE_PLUGIN_ROOT (for testing)
* @param {string} [options.probe] Relative path used to verify a candidate
* root contains what the caller needs. When
* given, it is honored exactly (script
* consumers pass their own script path). When
* omitted, a candidate must contain BOTH the
* ECC script tree and a sentinel ECC skill,
* so a partial install (scripts without
* skills) is rejected for skill consumers.
* @returns {string} Resolved ECC root path
*/
function resolveEccRoot(options = {}) {
const envRoot = options.envRoot !== undefined
? options.envRoot
: (process.env.CLAUDE_PLUGIN_ROOT || '');
function findRootInVendorHome(vendorDir, isRoot) {
if (isRoot(vendorDir)) {
return vendorDir;
if (envRoot && envRoot.trim()) {
return envRoot.trim();
}
for (const segments of PLUGIN_ROOT_SEGMENTS) {
const candidate = path.join(vendorDir, 'plugins', ...segments);
const homeDir = options.homeDir || os.homedir();
const claudeDir = path.join(homeDir, '.claude');
// Decide whether a candidate directory is a usable ECC root. An explicit
// caller probe is honored exactly (script consumers know the artifact they
// need). With the default probe the caller is a skill consumer, so a
// candidate must contain both ECC's scripts and a sentinel ECC skill —
// otherwise a scripts-only ~/.claude short-circuits and every skill path
// resolves to a location that does not exist (#2544).
const isRoot = options.probe
? (dir) => fs.existsSync(path.join(dir, options.probe))
: (dir) => fs.existsSync(path.join(dir, DEFAULT_SCRIPT_PROBE))
&& fs.existsSync(path.join(dir, DEFAULT_SKILL_PROBE));
// Standard install — files are copied directly into ~/.claude/
if (isRoot(claudeDir)) {
return claudeDir;
}
// Exact legacy plugin install locations. These preserve backwards
// compatibility without scanning arbitrary plugin trees.
const legacyPluginRoots = PLUGIN_ROOT_SEGMENTS.map((segments) =>
path.join(claudeDir, 'plugins', ...segments)
);
for (const candidate of legacyPluginRoots) {
if (isRoot(candidate)) {
return candidate;
}
}
// Plugin cache — Claude Code stores marketplace plugins under
// ~/.claude/plugins/cache/<plugin-name>/<org>/<version>/
try {
for (const slug of PLUGIN_CACHE_SLUGS) {
const cacheBase = path.join(vendorDir, 'plugins', 'cache', slug);
const cacheBase = path.join(claudeDir, 'plugins', 'cache', slug);
const orgDirs = fs.readdirSync(cacheBase, { withFileTypes: true });
for (const orgEntry of orgDirs) {
@@ -79,77 +121,10 @@ function findRootInVendorHome(vendorDir, isRoot) {
}
}
} catch {
// Vendor plugin cache doesn't exist or isn't readable.
// Plugin cache doesn't exist or isn't readable — continue to fallback
}
try {
const installedBase = path.join(vendorDir, 'installed-plugins');
const installedDirs = fs.readdirSync(installedBase, { withFileTypes: true });
for (const entry of installedDirs) {
if (!entry.isDirectory()) continue;
const candidate = path.join(installedBase, entry.name);
if (isRoot(candidate)) {
return candidate;
}
}
} catch {
// Grok (and similar) installed-plugins homes are optional.
}
return null;
}
/**
* Resolve the ECC source root directory.
*
* Tries, in order:
* 1. GROK_PLUGIN_ROOT, CLAUDE_PLUGIN_ROOT, or ECC_PLUGIN_ROOT
* 2. Standard install location (~/.claude/) — when a complete root exists there
* 3. Known plugin roots under ~/.claude/plugins/ (current + legacy slugs)
* 4. Plugin cache auto-detection — scans ~/.claude/plugins/cache/{ecc,everything-claude-code}/
* 5. The same layout under ~/.grok/, plus ~/.grok/installed-plugins/<hash>/
* 6. Fallback to ~/.claude/ (original behaviour)
*
* @param {object} [options]
* @param {string} [options.homeDir] Override home directory (for testing)
* @param {string} [options.envRoot] Override plugin-root env (for testing)
* @param {string} [options.probe] Relative path used to verify a candidate
* root contains what the caller needs. When
* given, it is honored exactly (script
* consumers pass their own script path). When
* omitted, a candidate must contain BOTH the
* ECC script tree and a sentinel ECC skill,
* so a partial install (scripts without
* skills) is rejected for skill consumers.
* @returns {string} Resolved ECC root path
*/
function resolveEccRoot(options = {}) {
const envRoot = options.envRoot !== undefined
? options.envRoot
: pluginRootFromEnv(process.env);
if (envRoot && envRoot.trim()) {
return envRoot.trim();
}
const homeDir = options.homeDir || os.homedir();
const claudeDir = path.join(homeDir, '.claude');
const grokDir = path.join(homeDir, '.grok');
// Decide whether a candidate directory is a usable ECC root. An explicit
// caller probe is honored exactly (script consumers know the artifact they
// need). With the default probe the caller is a skill consumer, so a
// candidate must contain both ECC's scripts and a sentinel ECC skill —
// otherwise a scripts-only ~/.claude short-circuits and every skill path
// resolves to a location that does not exist (#2544).
const isRoot = options.probe
? (dir) => fs.existsSync(path.join(dir, options.probe))
: (dir) => fs.existsSync(path.join(dir, DEFAULT_SCRIPT_PROBE))
&& fs.existsSync(path.join(dir, DEFAULT_SKILL_PROBE));
return findRootInVendorHome(claudeDir, isRoot)
|| findRootInVendorHome(grokDir, isRoot)
|| claudeDir;
return claudeDir;
}
function normalizePluginRootForPlatform(rootDir, platform = process.platform) {
@@ -171,21 +146,21 @@ function normalizePluginRootForPlatform(rootDir, platform = process.platform) {
*
* This minified form contains no spread, no nested array literals, and no
* escaped double quotes, so it survives `node -e "..."` quoting on every shell.
* When GROK_PLUGIN_ROOT, CLAUDE_PLUGIN_ROOT, or ECC_PLUGIN_ROOT is set it is
* used directly. Grok trusted plugin hooks set both GROK_PLUGIN_ROOT and the
* CLAUDE_PLUGIN_ROOT alias. Otherwise the inline probes ~/.claude plugin
* locations only far enough to load this module, then delegates to
* resolveEccRoot() (which also searches ~/.grok).
* When CLAUDE_PLUGIN_ROOT is set (as Claude Code does for plugin hooks and
* commands) it is used directly. Otherwise the inline probes the same set of
* locations resolveEccRoot() knows about — ~/.claude, the exact plugin roots
* under ~/.claude/plugins/, and the versioned plugin cache — only far enough to
* load the committed resolve-ecc-root module, then delegates the authoritative
* decision to resolveEccRoot(). This keeps discovery behaviour identical to the
* old inline while centralising the real logic in one tested module.
*
* Usage in commands:
* const _r = <paste INLINE_RESOLVE>;
* const sm = require(_r + '/scripts/lib/session-manager');
*/
const INLINE_RESOLVE = `(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.GROK_PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()`;
const INLINE_RESOLVE = `(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})()`;
module.exports = {
resolveEccRoot,
pluginRootFromEnv,
PLUGIN_ROOT_ENV_KEYS,
INLINE_RESOLVE,
};
-19
View File
@@ -83,19 +83,6 @@ function writePluginMetadata(root, counts) {
description: `Fixture marketplace plugin — ${counts.agents} agents, ${counts.skills} skills, ${counts.commands} legacy command shims`,
}],
}, null, 2));
const grokDir = path.join(root, '.grok-plugin');
fs.mkdirSync(grokDir, { recursive: true });
fs.writeFileSync(path.join(grokDir, 'plugin.json'), JSON.stringify({
name: 'ecc',
description: `Fixture Grok plugin — ${counts.agents} agents, ${counts.skills} skills, ${counts.commands} legacy command shims`,
}, null, 2));
fs.writeFileSync(path.join(grokDir, 'marketplace.json'), JSON.stringify({
plugins: [{
name: 'ecc',
description: `Fixture Grok marketplace plugin — ${counts.agents} agents, ${counts.skills} skills, ${counts.commands} legacy command shims`,
}],
}, null, 2));
}
function writeEnglishAgents(root, counts, options = {}) {
@@ -297,8 +284,6 @@ function runTests() {
assert.ok(formatted.includes('.gemini/GEMINI.md'));
assert.ok(formatted.includes('.claude-plugin/plugin.json description'));
assert.ok(formatted.includes('.claude-plugin/marketplace.json plugin description'));
assert.ok(formatted.includes('.grok-plugin/plugin.json description'));
assert.ok(formatted.includes('.grok-plugin/marketplace.json plugin description'));
assert.ok(formatted.includes('README.zh-CN.md quick-start summary'));
assert.ok(formatted.includes('docs/zh-CN/README.md parity table'));
assert.ok(formatted.includes('docs/zh-CN/AGENTS.md project structure'));
@@ -329,8 +314,6 @@ function runTests() {
const zhAgentsDoc = fs.readFileSync(path.join(testDir, 'docs', 'zh-CN', 'AGENTS.md'), 'utf8');
const pluginJson = fs.readFileSync(path.join(testDir, '.claude-plugin', 'plugin.json'), 'utf8');
const marketplaceJson = fs.readFileSync(path.join(testDir, '.claude-plugin', 'marketplace.json'), 'utf8');
const grokPluginJson = fs.readFileSync(path.join(testDir, '.grok-plugin', 'plugin.json'), 'utf8');
const grokMarketplaceJson = fs.readFileSync(path.join(testDir, '.grok-plugin', 'marketplace.json'), 'utf8');
assert.ok(readme.includes('Access to 1 agents, 1 skills, and 1 legacy command shims'));
assert.ok(readme.includes('actual OSS surface: 7 agents, 7 skills, and 7 legacy command shims'));
@@ -346,8 +329,6 @@ function runTests() {
assert.ok(zhAgentsDoc.includes('skills/ - 1+ 个工作流技能和领域知识'));
assert.ok(pluginJson.includes('1 agents, 1 skills, 1 legacy command shims'));
assert.ok(marketplaceJson.includes('1 agents, 1 skills, 1 legacy command shims'));
assert.ok(grokPluginJson.includes('1 agents, 1 skills, 1 legacy command shims'));
assert.ok(grokMarketplaceJson.includes('1 agents, 1 skills, 1 legacy command shims'));
} finally {
cleanupTestDir(testDir);
}
-31
View File
@@ -91,7 +91,6 @@ function runTests() {
ECC_HOOKS_ENABLED: undefined,
CLAUDE_PLUGIN_OPTION_HOOKS_ENABLED: undefined,
ECC_HOOK_CONFIG: undefined,
GROK_PLUGIN_ROOT: undefined,
CLAUDE_PLUGIN_ROOT: undefined,
ECC_PLUGIN_ROOT: undefined,
}, () => {
@@ -99,36 +98,6 @@ function runTests() {
});
})) passed++; else failed++;
if (test('GROK_PLUGIN_ROOT locates managed hook config when Claude root is unset', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-hook-flags-grok-'));
const configPath = path.join(root, 'ecc', 'setup.json');
try {
fs.mkdirSync(path.dirname(configPath), { recursive: true });
fs.writeFileSync(configPath, JSON.stringify({
hooks: { enabled: false, profile: 'minimal' },
}));
withEnv({
ECC_HOOKS_ENABLED: undefined,
ECC_HOOK_PROFILE: undefined,
CLAUDE_PLUGIN_OPTION_HOOKS_ENABLED: undefined,
CLAUDE_PLUGIN_OPTION_HOOK_PROFILE: undefined,
ECC_HOOK_CONFIG: undefined,
CLAUDE_PLUGIN_ROOT: undefined,
ECC_PLUGIN_ROOT: undefined,
GROK_PLUGIN_ROOT: root,
}, () => {
assert.deepStrictEqual(readManagedHookConfig(), {
enabled: false,
profile: 'minimal',
});
assert.strictEqual(areHooksEnabled(), false);
assert.strictEqual(getHookProfile(), 'minimal');
});
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('Claude plugin options control enabled state and profile', () => {
withEnv({
ECC_HOOKS_ENABLED: undefined,
+370
View File
@@ -0,0 +1,370 @@
/**
* Canonical Grok install contract tests.
*/
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync, spawnSync } = require('child_process');
const { applyInstallPlan, previewInstallPlan } = require('../../scripts/lib/install-executor');
const { repairInstalledStates, uninstallInstalledStates } = require('../../scripts/lib/install-lifecycle');
const { createInstallPlanFromRequest } = require('../../scripts/lib/install/runtime');
const repoRoot = path.resolve(__dirname, '..', '..');
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(` \u2713 ${name}`);
return true;
} catch (error) {
console.log(` \u2717 ${name}`);
console.log(` Error: ${error.stack || error.message}`);
return false;
}
}
function git(cwd, args, options = {}) {
return execFileSync('git', args, {
cwd,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
...options,
}).trim();
}
function commit(root, message) {
git(root, ['add', '.']);
git(root, ['-c', 'user.email=grok-canonical@test', '-c', 'user.name=GrokCanonical', 'commit', '-m', message]);
return git(root, ['rev-parse', 'HEAD']);
}
function writeJson(filePath, value) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
}
function createPinnedFixture() {
const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-grok-canonical-'));
const sourceRoot = path.join(parent, 'source');
const homeDir = path.join(parent, 'home');
git(parent, ['clone', '--quiet', '--no-local', repoRoot, sourceRoot]);
git(sourceRoot, ['remote', 'add', 'pinned-source', sourceRoot]);
fs.mkdirSync(homeDir, { recursive: true });
fs.writeFileSync(path.join(sourceRoot, 'commands', 'plan.md'), 'pinned payload\n');
writeJson(path.join(sourceRoot, '.mcp.json'), {
mcpServers: {
'chrome-devtools': { command: 'npx', args: ['-y', 'chrome-devtools-mcp@latest'] },
'other-mcp': { command: 'node', args: ['mcp.js'] },
},
});
const pluginManifestPath = path.join(sourceRoot, '.grok-plugin', 'plugin.json');
const pluginManifest = JSON.parse(fs.readFileSync(pluginManifestPath, 'utf8'));
writeJson(pluginManifestPath, { ...pluginManifest, version: '2.2.1' });
const payloadSha = commit(sourceRoot, 'fixture payload');
const marketplacePath = path.join(sourceRoot, '.grok-plugin', 'marketplace.json');
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
marketplace.plugins[0] = {
...marketplace.plugins[0],
version: '2.2.1',
source: {
source: 'url',
url: sourceRoot,
sha: payloadSha,
},
};
writeJson(marketplacePath, marketplace);
commit(sourceRoot, 'fixture catalog');
return { parent, sourceRoot, homeDir, payloadSha };
}
function request({ trust = true, hooks = true, mcp = ['other-mcp'] } = {}) {
return {
mode: 'manifest',
target: 'grok',
profileId: null,
moduleIds: ['commands-core', 'hooks-runtime'],
includeComponentIds: [],
excludeComponentIds: [],
legacyLanguages: [],
hookConsent: hooks ? 'enabled' : 'declined',
trust,
consentMcp: mcp,
};
}
function createPlan(fixture, requestOptions = {}, extra = {}) {
return createInstallPlanFromRequest(request(requestOptions), {
sourceRoot: fixture.sourceRoot,
projectRoot: fixture.sourceRoot,
homeDir: fixture.homeDir,
env: {},
...extra,
});
}
function installedRoot(fixture) {
return path.join(fixture.homeDir, '.grok', 'plugins', 'ecc');
}
function inspectWithGrok(fixture) {
const projectRoot = path.join(fixture.parent, 'project');
fs.mkdirSync(projectRoot, { recursive: true });
const result = spawnSync('grok', ['inspect', '--json'], {
cwd: projectRoot,
encoding: 'utf8',
env: {
...process.env,
HOME: fixture.homeDir,
GROK_HOME: path.join(fixture.homeDir, '.grok'),
},
timeout: 30000,
});
if (result.error && result.error.code === 'ENOENT') return null;
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
return JSON.parse(result.stdout);
}
function runTests() {
console.log('\n=== Canonical Grok install contract ===\n');
if (test('preview and apply use one canonical operation set with explicit capability consent', () => {
const fixture = createPinnedFixture();
try {
const rawPlan = createPlan(fixture);
const preview = previewInstallPlan(rawPlan);
const applied = applyInstallPlan(rawPlan);
const state = JSON.parse(fs.readFileSync(rawPlan.installStatePath, 'utf8'));
assert.deepStrictEqual(applied.plannedOperations, preview.plannedOperations);
assert.deepStrictEqual(
state.operations.map(operation => operation.destinationPath),
preview.plannedOperations.map(operation => operation.destinationPath)
);
assert.strictEqual(state.request.trust, true);
assert.deepStrictEqual(state.request.consentMcp, ['other-mcp']);
assert.strictEqual(state.source.repoCommit, fixture.payloadSha);
assert.strictEqual(state.source.repoUrl, fixture.sourceRoot);
const manifest = JSON.parse(fs.readFileSync(path.join(installedRoot(fixture), '.grok-plugin', 'plugin.json'), 'utf8'));
const mcp = JSON.parse(fs.readFileSync(path.join(installedRoot(fixture), '.mcp.json'), 'utf8'));
assert.strictEqual(manifest.hooks, 'hooks/hooks.json');
assert.strictEqual(manifest.mcpServers, '.mcp.json');
assert.deepStrictEqual(Object.keys(mcp.mcpServers), ['other-mcp']);
assert.ok(!JSON.stringify(mcp).includes('chrome-devtools'));
const installedHooks = fs.readFileSync(path.join(installedRoot(fixture), 'hooks', 'hooks.json'), 'utf8');
assert.ok(installedHooks.includes('GROK_PLUGIN_ROOT'));
assert.ok(!fs.readFileSync(path.join(repoRoot, 'hooks', 'hooks.json'), 'utf8').includes('GROK_PLUGIN_ROOT'));
const inspection = inspectWithGrok(fixture);
if (inspection) {
const plugin = inspection.plugins.find((item) => item.name === 'ecc');
assert.ok(plugin && plugin.enabled);
assert.strictEqual(plugin.provides.hooks, true);
assert.strictEqual(plugin.provides.mcpServers, 1);
}
} finally {
fs.rmSync(fixture.parent, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('untrusted and denied plans omit hooks and every MCP capability', () => {
const fixture = createPinnedFixture();
try {
for (const options of [
{ trust: false, hooks: true, mcp: ['chrome-devtools', 'other-mcp'] },
{ trust: true, hooks: false, mcp: [] },
]) {
fs.rmSync(path.join(fixture.homeDir, '.grok'), { recursive: true, force: true });
const plan = createPlan(fixture, options);
applyInstallPlan(plan);
const manifest = JSON.parse(fs.readFileSync(path.join(installedRoot(fixture), '.grok-plugin', 'plugin.json'), 'utf8'));
assert.strictEqual(manifest.hooks, '');
assert.strictEqual(manifest.mcpServers, '');
assert.ok(!fs.existsSync(path.join(installedRoot(fixture), '.mcp.json')));
assert.ok(!fs.existsSync(path.join(installedRoot(fixture), 'hooks', 'hooks.json')));
const inspection = inspectWithGrok(fixture);
if (inspection) {
const plugin = inspection.plugins.find((item) => item.name === 'ecc');
assert.ok(plugin && plugin.enabled);
assert.strictEqual(plugin.provides.hooks, false);
assert.strictEqual(plugin.provides.mcpServers, 0);
}
}
} finally {
fs.rmSync(fixture.parent, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('canonical source preparation installs the pinned commit, not HEAD or dirty files', () => {
const fixture = createPinnedFixture();
try {
fs.writeFileSync(path.join(fixture.sourceRoot, 'commands', 'plan.md'), 'dirty payload\n');
fs.writeFileSync(path.join(fixture.sourceRoot, 'untracked.txt'), 'do not install\n');
const plan = createPlan(fixture, { hooks: false, mcp: [] });
applyInstallPlan(plan);
assert.strictEqual(
fs.readFileSync(path.join(installedRoot(fixture), 'commands', 'plan.md'), 'utf8'),
'pinned payload\n'
);
assert.ok(!fs.existsSync(path.join(installedRoot(fixture), 'untracked.txt')));
} finally {
fs.rmSync(fixture.parent, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('source identity preserves case-sensitive remote paths', () => {
const fixture = createPinnedFixture();
try {
const marketplacePath = path.join(fixture.sourceRoot, '.grok-plugin', 'marketplace.json');
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
marketplace.plugins[0].source.url = 'https://example.com/Owner/ECC.git';
writeJson(marketplacePath, marketplace);
assert.throws(
() => createPlan(
fixture,
{ hooks: false, mcp: [] },
{ sourceUrl: 'https://example.com/owner/ECC.git' }
),
/source URL does not match/
);
} finally {
fs.rmSync(fixture.parent, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('source identity rejects a marketplace version that does not match the pinned plugin', () => {
const fixture = createPinnedFixture();
try {
const marketplacePath = path.join(fixture.sourceRoot, '.grok-plugin', 'marketplace.json');
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
marketplace.plugins[0].version = '9.9.9';
writeJson(marketplacePath, marketplace);
assert.throws(
() => createPlan(fixture, { hooks: false, mcp: [] }),
/plugin version 2\.2\.1 does not match marketplace version 9\.9\.9/
);
} finally {
fs.rmSync(fixture.parent, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('marketplace-extracted trees require matching Grok registry evidence and refetch the pin', () => {
const fixture = createPinnedFixture();
const extractedRoot = path.join(fixture.parent, 'extracted');
try {
fs.mkdirSync(path.join(extractedRoot, '.grok-plugin'), { recursive: true });
fs.copyFileSync(
path.join(fixture.sourceRoot, '.grok-plugin', 'marketplace.json'),
path.join(extractedRoot, '.grok-plugin', 'marketplace.json')
);
assert.throws(
() => createInstallPlanFromRequest(request(), {
sourceRoot: extractedRoot,
projectRoot: extractedRoot,
homeDir: fixture.homeDir,
env: {},
}),
/registry|verified source|unverifiable/i
);
writeJson(path.join(fixture.homeDir, '.grok', 'installed-plugins', 'registry.json'), {
version: 1,
repos: {
ecc: {
path: extractedRoot,
kind: { type: 'Git', url: fixture.sourceRoot, commit: fixture.payloadSha },
plugins: { ecc: { version: '2.2.1' } },
},
},
});
const plan = createInstallPlanFromRequest(request({ hooks: false, mcp: [] }), {
sourceRoot: extractedRoot,
projectRoot: extractedRoot,
homeDir: fixture.homeDir,
env: {},
});
applyInstallPlan(plan);
assert.strictEqual(
fs.readFileSync(path.join(installedRoot(fixture), 'commands', 'plan.md'), 'utf8'),
'pinned payload\n'
);
} finally {
fs.rmSync(fixture.parent, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('repair, uninstall, upgrade, and rollback stay on canonical install state', () => {
const fixture = createPinnedFixture();
try {
const firstPlan = createPlan(fixture, { hooks: false, mcp: [] });
applyInstallPlan(firstPlan);
const firstState = JSON.parse(fs.readFileSync(firstPlan.installStatePath, 'utf8'));
fs.writeFileSync(path.join(fixture.sourceRoot, 'commands', 'plan.md'), 'upgraded payload\n');
const upgradedSha = commit(fixture.sourceRoot, 'upgraded payload');
const marketplacePath = path.join(fixture.sourceRoot, '.grok-plugin', 'marketplace.json');
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
marketplace.plugins[0].source.sha = upgradedSha;
writeJson(marketplacePath, marketplace);
commit(fixture.sourceRoot, 'upgraded catalog');
const upgradePlan = createPlan(fixture, { hooks: false, mcp: [] });
applyInstallPlan(upgradePlan);
assert.strictEqual(
fs.readFileSync(path.join(installedRoot(fixture), 'commands', 'plan.md'), 'utf8'),
'upgraded payload\n'
);
fs.writeFileSync(path.join(installedRoot(fixture), 'commands', 'plan.md'), 'broken\n');
const repair = repairInstalledStates({
repoRoot: fixture.sourceRoot,
projectRoot: fixture.sourceRoot,
homeDir: fixture.homeDir,
targets: ['grok'],
env: {},
});
assert.strictEqual(repair.results[0].status, 'repaired', JSON.stringify(repair.results[0]));
assert.strictEqual(
fs.readFileSync(path.join(installedRoot(fixture), 'commands', 'plan.md'), 'utf8'),
'upgraded payload\n'
);
const rollbackPlan = createPlan(
fixture,
{ hooks: false, mcp: [] },
{ sourceSha: firstState.source.repoCommit }
);
applyInstallPlan(rollbackPlan);
const rolledBackState = JSON.parse(fs.readFileSync(rollbackPlan.installStatePath, 'utf8'));
assert.strictEqual(rolledBackState.source.repoCommit, firstState.source.repoCommit);
assert.strictEqual(
fs.readFileSync(path.join(installedRoot(fixture), 'commands', 'plan.md'), 'utf8'),
'pinned payload\n'
);
const uninstall = uninstallInstalledStates({
repoRoot: fixture.sourceRoot,
projectRoot: fixture.sourceRoot,
homeDir: fixture.homeDir,
targets: ['grok'],
env: {},
});
assert.strictEqual(uninstall.results[0].status, 'uninstalled');
assert.ok(!fs.existsSync(firstPlan.installStatePath));
assert.ok(!fs.existsSync(installedRoot(fixture)));
} finally {
fs.rmSync(fixture.parent, { recursive: true, force: true });
}
})) passed++; else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}
runTests();
+61 -767
View File
@@ -1,8 +1,5 @@
/**
* Conformance tests for the Grok harness adapter.
*
* These tests require the shipped adapter (`scripts/lib/grok-harness-adapter.js`)
* and the shared resolver. They do not reimplement adapter logic.
* Read-only Grok harness boundary tests.
*/
const assert = require('assert');
@@ -11,33 +8,26 @@ const os = require('os');
const path = require('path');
const adapter = require('../../scripts/lib/grok-harness-adapter');
const { uninstallInstalledStates } = require('../../scripts/lib/install-lifecycle');
const {
resolveEccRoot,
pluginRootFromEnv,
PLUGIN_ROOT_ENV_KEYS,
SHA_PATTERN,
readPinnedMarketplaceSource,
} = require('../../scripts/lib/grok-source-identity');
const {
INLINE_RESOLVE,
resolveEccRoot,
} = require('../../scripts/lib/resolve-ecc-root');
const repoRoot = path.resolve(__dirname, '..', '..');
const marketplacePath = path.join(repoRoot, '.grok-plugin', 'marketplace.json');
const mcpPath = path.join(repoRoot, '.mcp.json');
const ECC_SKILL_SENTINEL = path.join('skills', 'continuous-learning-v2');
const FIXTURE_SHA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
const OTHER_SHA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const TASTEFORGE_SHA = 'd8409a4b0813771235555e32e3d8046a73988bfa';
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(` ✓ ${name}`);
console.log(` \u2713 ${name}`);
return true;
} catch (error) {
console.log(` ✗ ${name}`);
console.log(` \u2717 ${name}`);
console.log(` Error: ${error.message}`);
return false;
}
@@ -47,128 +37,47 @@ function createTempDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-grok-adapter-'));
}
function writeCompleteRoot(root) {
function writeCompleteRoot(root, sha = null) {
fs.mkdirSync(path.join(root, 'scripts', 'lib'), { recursive: true });
fs.writeFileSync(path.join(root, 'scripts', 'lib', 'utils.js'), '// stub\n');
fs.mkdirSync(path.join(root, ECC_SKILL_SENTINEL), { recursive: true });
fs.mkdirSync(path.join(root, 'hooks'), { recursive: true });
fs.writeFileSync(
path.join(root, 'hooks', 'hooks.json'),
JSON.stringify({
hooks: {
SessionStart: [{
command: 'node -e "var e=process.env.PLUGIN_ROOT||process.env.CLAUDE_PLUGIN_ROOT||process.env.ECC_PLUGIN_ROOT"',
}],
},
})
);
return root;
}
function sourceFixture(root, sha = FIXTURE_SHA) {
writeCompleteRoot(root);
fs.writeFileSync(path.join(root, '.mcp.json'), JSON.stringify({
mcpServers: {
'chrome-devtools': { command: 'npx', args: ['-y', 'chrome-devtools-mcp@latest'] },
'other-mcp': { command: 'echo' },
},
}));
return {
sourceRoot: root,
source: {
source: 'url',
url: 'https://github.com/affaan-m/ECC.git',
sha,
},
mcpConfig: JSON.parse(fs.readFileSync(path.join(root, '.mcp.json'), 'utf8')),
version: '2.2.0',
};
}
function gitCommit(sourceRoot, message) {
const { execFileSync } = require('child_process');
execFileSync('git', ['add', '.'], { cwd: sourceRoot, stdio: 'ignore' });
execFileSync('git', [
'-c', 'user.email=grok-adapter@test',
'-c', 'user.name=GrokAdapter',
'commit', '-m', message,
], { cwd: sourceRoot, stdio: 'ignore' });
return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: sourceRoot, encoding: 'utf8' }).trim();
}
function gitSourceFixture(sourceRoot) {
const { execFileSync } = require('child_process');
const fixture = sourceFixture(sourceRoot, FIXTURE_SHA);
fs.mkdirSync(path.join(sourceRoot, '.grok-plugin'), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, '.grok-plugin', 'marketplace.json'), JSON.stringify({
name: 'ecc',
plugins: [{
name: 'ecc',
version: '2.2.0',
source: {
source: 'url',
url: 'https://github.com/affaan-m/ECC.git',
sha: OTHER_SHA,
},
}],
}));
execFileSync('git', ['init'], { cwd: sourceRoot, stdio: 'ignore' });
fixture.source.sha = gitCommit(sourceRoot, 'fixture');
return fixture;
fs.mkdirSync(path.join(root, 'skills', 'continuous-learning-v2'), { recursive: true });
if (sha) fs.writeFileSync(path.join(root, '.ecc-source.json'), JSON.stringify({ sha }));
}
function runTests() {
console.log('\n=== Grok harness adapter ===\n');
console.log('\n=== Grok harness adapter boundary ===\n');
if (test('shared PLUGIN_ROOT_ENV_KEYS does not include GROK_PLUGIN_ROOT', () => {
assert.ok(Array.isArray(PLUGIN_ROOT_ENV_KEYS));
assert.ok(!PLUGIN_ROOT_ENV_KEYS.includes('GROK_PLUGIN_ROOT'));
if (test('shared root resolution has no Grok aliases or layout policy', () => {
assert.ok(!INLINE_RESOLVE.includes('GROK_PLUGIN_ROOT'));
})) passed++; else failed++;
if (test('shared pluginRootFromEnv ignores GROK_PLUGIN_ROOT', () => {
assert.strictEqual(pluginRootFromEnv({
GROK_PLUGIN_ROOT: '/grok/root',
ECC_PLUGIN_ROOT: '/ecc/root',
}), '/ecc/root');
assert.strictEqual(pluginRootFromEnv({ GROK_PLUGIN_ROOT: '/grok/root' }), '');
})) passed++; else failed++;
if (test('shared resolveEccRoot ignores GROK_PLUGIN_ROOT and ~/.grok layout', () => {
const homeDir = createTempDir();
try {
const grokRoot = path.join(homeDir, '.grok', 'installed-plugins', 'ecc-deadbeef');
writeCompleteRoot(grokRoot);
const previous = process.env.GROK_PLUGIN_ROOT;
const previousClaude = process.env.CLAUDE_PLUGIN_ROOT;
const previousPlugin = process.env.PLUGIN_ROOT;
const previousEcc = process.env.ECC_PLUGIN_ROOT;
try {
process.env.GROK_PLUGIN_ROOT = grokRoot;
delete process.env.CLAUDE_PLUGIN_ROOT;
delete process.env.PLUGIN_ROOT;
delete process.env.ECC_PLUGIN_ROOT;
const result = resolveEccRoot({ homeDir, envRoot: '' });
assert.strictEqual(result, path.join(homeDir, '.claude'));
assert.notStrictEqual(result, grokRoot);
} finally {
if (previous === undefined) delete process.env.GROK_PLUGIN_ROOT;
else process.env.GROK_PLUGIN_ROOT = previous;
if (previousClaude === undefined) delete process.env.CLAUDE_PLUGIN_ROOT;
else process.env.CLAUDE_PLUGIN_ROOT = previousClaude;
if (previousPlugin === undefined) delete process.env.PLUGIN_ROOT;
else process.env.PLUGIN_ROOT = previousPlugin;
if (previousEcc === undefined) delete process.env.ECC_PLUGIN_ROOT;
else process.env.ECC_PLUGIN_ROOT = previousEcc;
}
const root = path.join(homeDir, '.grok', 'installed-plugins', 'ecc-deadbeef');
writeCompleteRoot(root);
assert.strictEqual(resolveEccRoot({ homeDir, envRoot: '' }), path.join(homeDir, '.claude'));
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('shared hook runners do not require the Grok adapter', () => {
if (test('GROK_PLUGIN_ROOT is translated only at the adapter boundary', () => {
const mapped = adapter.toSharedPluginEnv({
GROK_PLUGIN_ROOT: '/grok/root',
PATH: '/bin',
});
assert.strictEqual(mapped.PLUGIN_ROOT, '/grok/root');
assert.strictEqual(mapped.GROK_PLUGIN_ROOT, undefined);
const existing = adapter.toSharedPluginEnv({
GROK_PLUGIN_ROOT: '/grok/root',
CLAUDE_PLUGIN_ROOT: '/claude/root',
});
assert.strictEqual(existing.PLUGIN_ROOT, undefined);
assert.strictEqual(resolveEccRoot({ envRoot: existing.CLAUDE_PLUGIN_ROOT }), '/claude/root');
})) passed++; else failed++;
if (test('shared hook and resolver files contain no Grok provider policy', () => {
const files = [
'scripts/lib/hook-flags.js',
'scripts/lib/resolve-ecc-root.js',
'scripts/hooks/plugin-hook-bootstrap.js',
'scripts/hooks/run-with-flags.js',
'scripts/hooks/observe-runner.js',
@@ -176,670 +85,55 @@ function runTests() {
];
for (const relative of files) {
const source = fs.readFileSync(path.join(repoRoot, relative), 'utf8');
assert.ok(!source.includes('grok-harness-adapter'), `${relative} must not require the Grok adapter`);
assert.ok(!source.includes('GROK_PLUGIN_ROOT'), `${relative} must not select GROK_PLUGIN_ROOT`);
}
const resolveSource = fs.readFileSync(path.join(repoRoot, 'scripts/lib/resolve-ecc-root.js'), 'utf8');
assert.ok(!resolveSource.includes('GROK_PLUGIN_ROOT'));
})) passed++; else failed++;
if (test('toSharedPluginEnv maps GROK_PLUGIN_ROOT onto PLUGIN_ROOT and drops the Grok alias', () => {
const mapped = adapter.toSharedPluginEnv({
GROK_PLUGIN_ROOT: '/grok/root',
PATH: '/bin',
});
assert.strictEqual(mapped.PLUGIN_ROOT, '/grok/root');
assert.strictEqual(mapped.GROK_PLUGIN_ROOT, undefined);
assert.strictEqual(pluginRootFromEnv(mapped), '/grok/root');
})) passed++; else failed++;
if (test('toSharedPluginEnv does not let GROK override an existing Claude/Codex root', () => {
const mapped = adapter.toSharedPluginEnv({
CLAUDE_PLUGIN_ROOT: '/claude/root',
GROK_PLUGIN_ROOT: '/grok/root',
});
assert.strictEqual(mapped.GROK_PLUGIN_ROOT, undefined);
assert.strictEqual(pluginRootFromEnv(mapped), '/claude/root');
})) passed++; else failed++;
if (test('adapter resolves GROK_PLUGIN_ROOT and ~/.grok installed-plugins', () => {
const homeDir = createTempDir();
try {
assert.strictEqual(
adapter.resolveGrokPluginRoot({ env: { GROK_PLUGIN_ROOT: ' /from/grok ' }, homeDir }),
'/from/grok'
);
const installed = path.join(homeDir, '.grok', 'installed-plugins', 'ecc-ab12cd34');
writeCompleteRoot(installed);
assert.strictEqual(adapter.resolveGrokPluginRoot({ env: {}, homeDir }), installed);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
assert.ok(!source.includes('GROK_PLUGIN_ROOT'), relative);
assert.ok(!source.includes('grok-harness-adapter'), relative);
}
})) passed++; else failed++;
if (test('runGrokInstall dry-run is the ECC consent path and denies Chrome DevTools by default', () => {
const homeDir = createTempDir();
try {
const result = adapter.runGrokInstall({ dryRun: true, homeDir, repoRoot });
assert.strictEqual(result.kind, 'grok-install-result');
assert.strictEqual(result.dryRun, true);
assert.strictEqual(result.plan.attachChromeDevtools, false);
assert.strictEqual(result.plan.hooksEnabled, false);
assert.strictEqual(result.nativeMcpOptedOut, true);
assert.strictEqual(result.nativeHooksOptedOut, true);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('preview exposes canonical operations and their concrete destinations', () => {
const homeDir = createTempDir();
try {
const plan = adapter.previewInstall({
homeDir,
source: { source: 'url', url: 'https://github.com/affaan-m/ECC.git', sha: FIXTURE_SHA },
trust: true,
consent: { hooks: true, mcp: { 'other-mcp': true } },
mcpConfig: { mcpServers: { 'other-mcp': { command: 'echo' } } },
});
assert.ok(Array.isArray(plan.operations) && plan.operations.length > 0);
assert.ok(plan.operations.every((operation) => (
['copy-file', 'render-template', 'merge-json', 'remove'].includes(operation.kind)
)));
assert.ok(plan.operations.every((operation) => path.isAbsolute(operation.destinationPath)));
assert.ok(plan.operations.some((operation) => operation.destinationPath.includes(path.join('.grok', 'plugins'))));
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('applyInstall copies the pinned git archive, not HEAD or untracked files', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = gitSourceFixture(sourceRoot);
const pinnedSha = fixture.source.sha;
fs.writeFileSync(path.join(sourceRoot, 'untracked-extra.txt'), 'should-not-copy\n');
fs.writeFileSync(path.join(sourceRoot, 'later-commit.txt'), 'later\n');
gitCommit(sourceRoot, 'later');
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true },
});
const receipt = adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig });
assert.ok(!fs.existsSync(path.join(receipt.installedRoot, 'untracked-extra.txt')));
assert.ok(!fs.existsSync(path.join(receipt.installedRoot, 'later-commit.txt')));
assert.ok(fs.existsSync(path.join(receipt.installedRoot, 'scripts', 'lib', 'utils.js')));
const identity = JSON.parse(fs.readFileSync(
path.join(receipt.installedRoot, adapter.SOURCE_IDENTITY_FILE),
'utf8'
));
assert.strictEqual(identity.sha, pinnedSha);
const installedCatalog = JSON.parse(fs.readFileSync(
path.join(receipt.installedRoot, '.grok-plugin', 'marketplace.json'),
'utf8'
));
assert.strictEqual(installedCatalog.plugins[0].source.sha, pinnedSha);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('applyInstall rejects a git pin that is not in the source repository', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = gitSourceFixture(sourceRoot);
fixture.source.sha = OTHER_SHA;
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true },
});
assert.throws(
() => adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig }),
/not in source git/
);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('applyInstall rejects a non-Git source tree with only self-asserted identity', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = sourceFixture(sourceRoot);
fs.mkdirSync(path.join(sourceRoot, '.grok-plugin'), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, '.grok-plugin', 'marketplace.json'), JSON.stringify({
plugins: [{ name: 'ecc', version: fixture.version, source: fixture.source }],
}));
const plan = adapter.previewInstall({ ...fixture, homeDir, trust: true });
assert.throws(
() => adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig }),
/unverifiable|verified source|source identity/i
);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('runGrokInstall rejects malformed .mcp.json instead of silently disabling capabilities', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
fs.mkdirSync(path.join(sourceRoot, '.grok-plugin'), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, '.grok-plugin', 'marketplace.json'), JSON.stringify({
plugins: [{
name: 'ecc',
version: '2.2.0',
source: { source: 'url', url: 'https://github.com/affaan-m/ECC.git', sha: FIXTURE_SHA },
}],
}));
fs.writeFileSync(path.join(sourceRoot, '.grok-plugin', 'plugin.json'), JSON.stringify({
name: 'ecc',
hooks: '',
mcpServers: '',
}));
fs.writeFileSync(path.join(sourceRoot, '.mcp.json'), '{not-json');
assert.throws(
() => adapter.runGrokInstall({ dryRun: true, homeDir, repoRoot: sourceRoot }),
/\.mcp\.json|JSON/i
);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('missing Grok root returns null', () => {
const homeDir = createTempDir();
try {
assert.strictEqual(adapter.resolveGrokPluginRoot({ env: {}, homeDir }), null);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('in-repo marketplace source is a pinned Git URL', () => {
const { execFileSync } = require('child_process');
const plugin = adapter.readMarketplaceSource(marketplacePath);
assert.strictEqual(plugin.source.url, 'https://github.com/affaan-m/ECC.git');
assert.match(plugin.source.sha, adapter.SHA_PATTERN);
assert.notStrictEqual(plugin.source.sha, TASTEFORGE_SHA);
const tracked = execFileSync('git', ['ls-files', 'scripts/lib/grok-harness-adapter.js'], {
cwd: repoRoot,
encoding: 'utf8',
}).trim();
if (tracked) {
execFileSync('git', ['show', `${plugin.source.sha}:scripts/lib/grok-harness-adapter.js`], {
cwd: repoRoot,
stdio: ['ignore', 'pipe', 'ignore'],
});
}
assert.ok(adapter.sourceIdentity(plugin.source).includes(plugin.source.sha));
})) passed++; else failed++;
if (test('native Grok plugin manifest opts trusted installs out of chrome-devtools', () => {
const pluginJsonPath = path.join(repoRoot, '.grok-plugin', 'plugin.json');
const manifest = adapter.readGrokPluginManifest(pluginJsonPath);
assert.strictEqual(manifest.mcpServers, '');
if (test('native trusted manifest defaults deny hooks and root MCP', () => {
const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, '.grok-plugin', 'plugin.json'), 'utf8'));
assert.strictEqual(manifest.hooks, '');
assert.strictEqual(adapter.nativePluginMcpOptedOut(manifest), true);
assert.deepStrictEqual(adapter.nativeTrustedMcpServerNames(manifest), []);
assert.strictEqual(adapter.nativeTrustedInstallAttachesChromeDevtools(manifest), false);
const rootMcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
assert.ok(rootMcp.mcpServers['chrome-devtools'], 'Claude/Codex root .mcp.json may still list chrome-devtools');
assert.strictEqual(manifest.mcpServers, '');
const rootMcp = JSON.parse(fs.readFileSync(path.join(repoRoot, '.mcp.json'), 'utf8'));
assert.ok(rootMcp.mcpServers['chrome-devtools']);
})) passed++; else failed++;
if (test('unpinned marketplace source is rejected', () => {
assert.throws(
() => adapter.assertPinnedSource({ source: 'url', url: 'https://github.com/affaan-m/ECC.git' }),
/40-character lowercase commit SHA/
);
if (test('marketplace source is the pinned whole repository', () => {
const source = readPinnedMarketplaceSource(repoRoot);
assert.strictEqual(source.url, 'https://github.com/affaan-m/ECC.git');
assert.match(source.sha, SHA_PATTERN);
})) passed++; else failed++;
if (test('preview names hooks and each MCP capability; trusted default denies Chrome DevTools', () => {
const mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
const plan = adapter.previewInstall({
source: { source: 'url', url: 'https://github.com/affaan-m/ECC.git', sha: FIXTURE_SHA },
trust: true,
mcpConfig,
});
const ids = plan.capabilities.map((item) => item.id);
assert.ok(ids.includes('hooks'));
assert.ok(ids.includes('chrome-devtools'));
assert.strictEqual(plan.hooksEnabled, false);
assert.strictEqual(plan.attachChromeDevtools, false);
assert.ok(!plan.mcpAttached.includes('chrome-devtools'));
assert.ok(plan.capabilities.every((item) => item.consented === false));
})) passed++; else failed++;
if (test('untrusted install cannot enable hooks or MCP even with consent', () => {
const plan = adapter.previewInstall({
source: { source: 'url', url: 'https://github.com/affaan-m/ECC.git', sha: FIXTURE_SHA },
trust: false,
consent: { hooks: true, mcp: { 'chrome-devtools': true } },
mcpConfig: { mcpServers: { 'chrome-devtools': { command: 'npx' } } },
});
assert.strictEqual(plan.trust, false);
assert.strictEqual(plan.hooksEnabled, false);
assert.strictEqual(plan.attachChromeDevtools, false);
})) passed++; else failed++;
if (test('capability denial keeps Chrome DevTools out of a trusted receipt', () => {
if (test('missing, disabled, native, cached, and pinned roots resolve deterministically', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
const sha = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
try {
const fixture = sourceFixture(sourceRoot);
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true, mcp: { 'chrome-devtools': false, 'other-mcp': true } },
});
assert.strictEqual(plan.hooksEnabled, true);
assert.strictEqual(plan.attachChromeDevtools, false);
assert.deepStrictEqual(plan.mcpAttached, ['other-mcp']);
const receipt = adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig });
const installedMcp = JSON.parse(fs.readFileSync(path.join(receipt.installedRoot, '.mcp.json'), 'utf8'));
assert.ok(!installedMcp.mcpServers['chrome-devtools']);
assert.ok(installedMcp.mcpServers['other-mcp']);
assert.ok(receipt.hooksEnabled);
assert.ok(!receipt.mcpAttached.includes('chrome-devtools'));
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
assert.strictEqual(adapter.resolveGrokPluginRoot({ homeDir, env: {} }), null);
const nativeRoot = path.join(homeDir, '.grok', 'plugins', 'ecc');
writeCompleteRoot(nativeRoot);
assert.strictEqual(adapter.resolveGrokPluginRoot({ homeDir, env: {} }), nativeRoot);
assert.strictEqual(adapter.resolveGrokPluginRoot({ homeDir, enabled: false }), null);
if (test('trusted install without MCP consent does not attach root chrome-devtools', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = sourceFixture(sourceRoot);
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true },
});
const receipt = adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig });
const installedMcp = JSON.parse(fs.readFileSync(path.join(receipt.installedRoot, '.mcp.json'), 'utf8'));
assert.deepStrictEqual(installedMcp.mcpServers, {});
assert.strictEqual(receipt.attachChromeDevtools, undefined);
assert.deepStrictEqual(receipt.mcpAttached, []);
const statePath = adapter.grokInstallStatePath(homeDir);
assert.ok(fs.existsSync(statePath));
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
assert.strictEqual(state.schemaVersion, 'ecc.install.v1');
assert.strictEqual(state.target.id, 'grok-home');
assert.strictEqual(state.target.target, 'grok');
assert.strictEqual(state.source.repoCommit, fixture.source.sha);
assert.ok(!fs.existsSync(path.join(homeDir, '.grok', 'ecc-adapter')));
const mapped = adapter.toSharedPluginEnv({ GROK_PLUGIN_ROOT: receipt.installedRoot });
assert.strictEqual(mapped.PLUGIN_ROOT, receipt.installedRoot);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('applyInstall maps GROK_PLUGIN_ROOT at the installed hook boundary when hooks are consented', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = sourceFixture(sourceRoot);
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true },
});
const receipt = adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig });
assert.strictEqual(receipt.trust, true);
const destHooks = fs.readFileSync(path.join(receipt.installedRoot, 'hooks', 'hooks.json'), 'utf8');
assert.ok(destHooks.includes('GROK_PLUGIN_ROOT'));
const sharedEnv = adapter.readInstalledSharedEnv(receipt.installedRoot);
assert.strictEqual(sharedEnv.PLUGIN_ROOT, receipt.installedRoot);
const repoHooks = fs.readFileSync(path.join(repoRoot, 'hooks', 'hooks.json'), 'utf8');
assert.ok(!repoHooks.includes('GROK_PLUGIN_ROOT'));
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('applyInstall rejects a source tree whose marketplace pin does not match the plan', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = sourceFixture(sourceRoot);
fs.mkdirSync(path.join(sourceRoot, '.grok-plugin'), { recursive: true });
fs.writeFileSync(path.join(sourceRoot, '.grok-plugin', 'marketplace.json'), JSON.stringify({
name: 'ecc',
plugins: [{
name: 'ecc',
version: '2.2.0',
source: {
source: 'url',
url: 'https://github.com/affaan-m/ECC.git',
sha: OTHER_SHA,
},
}],
}));
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true },
});
assert.throws(
() => adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig }),
/marketplace sha does not match/
);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('install is root-contained; Windows-style escape is rejected', () => {
const grokRoot = 'C:\\Users\\ecc\\.grok';
assert.ok(adapter.isContained('C:\\Users\\ecc\\.grok\\installed-plugins\\ecc-aaa', grokRoot, path.win32));
assert.ok(!adapter.isContained('C:\\Windows\\System32', grokRoot, path.win32));
assert.ok(!adapter.isContained('C:\\Users\\ecc\\.grok\\..\\..\\Windows', grokRoot, path.win32));
assert.throws(
() => adapter.assertRootContainment('C:\\Windows\\System32', grokRoot, path.win32),
/escapes Grok root/
);
})) passed++; else failed++;
if (test('applyInstall rejects a symlinked plugin-directory ancestor without writing outside Grok home', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
const outsideRoot = createTempDir();
try {
const fixture = gitSourceFixture(sourceRoot);
const grokRoot = path.join(homeDir, '.grok');
fs.mkdirSync(grokRoot, { recursive: true });
try {
fs.symlinkSync(outsideRoot, path.join(grokRoot, 'plugins'), 'dir');
} catch {
console.log(' (directory symlink unsupported on this platform; skipping)');
return;
}
const plan = adapter.previewInstall({ ...fixture, homeDir, trust: true });
assert.throws(() => adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig }), /outside|symlink|root/i);
assert.deepStrictEqual(fs.readdirSync(outsideRoot), []);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
fs.rmSync(outsideRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('applyInstall rejects a final destination symlink without modifying its target', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
const outsideRoot = createTempDir();
try {
const fixture = gitSourceFixture(sourceRoot);
const plan = adapter.previewInstall({ ...fixture, homeDir, trust: true });
const destination = adapter.installDestination(homeDir, plan);
fs.mkdirSync(path.dirname(destination), { recursive: true });
fs.writeFileSync(path.join(outsideRoot, 'sentinel.txt'), 'unchanged\n');
try {
fs.symlinkSync(outsideRoot, destination, 'dir');
} catch {
console.log(' (directory symlink unsupported on this platform; skipping)');
return;
}
assert.throws(() => adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig }), /outside|symlink|root/i);
assert.deepStrictEqual(fs.readdirSync(outsideRoot), ['sentinel.txt']);
assert.strictEqual(fs.readFileSync(path.join(outsideRoot, 'sentinel.txt'), 'utf8'), 'unchanged\n');
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
fs.rmSync(outsideRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('canonical lifecycle uninstalls a Grok receipt with only supported operation kinds', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
const projectRoot = createTempDir();
try {
const fixture = gitSourceFixture(sourceRoot);
const plan = adapter.previewInstall({ ...fixture, homeDir, trust: true, consent: { hooks: true } });
const receipt = adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig });
const state = JSON.parse(fs.readFileSync(adapter.grokInstallStatePath(homeDir), 'utf8'));
assert.ok(state.operations.every((operation) => (
['copy-file', 'render-template', 'merge-json', 'remove'].includes(operation.kind)
)));
const result = uninstallInstalledStates({
repoRoot,
homeDir,
projectRoot,
targets: ['grok'],
});
assert.strictEqual(result.results[0].status, 'uninstalled');
assert.ok(!fs.existsSync(receipt.installedRoot));
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
fs.rmSync(projectRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('consented capabilities are installed in Grok native discovery and denied capabilities are absent', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = gitSourceFixture(sourceRoot);
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true, mcp: { 'other-mcp': true, 'chrome-devtools': false } },
});
const receipt = adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig });
assert.ok(receipt.installedRoot.startsWith(path.join(homeDir, '.grok', 'plugins') + path.sep));
assert.ok(fs.existsSync(path.join(receipt.installedRoot, 'hooks', 'hooks.json')));
const installedManifest = JSON.parse(fs.readFileSync(
path.join(receipt.installedRoot, '.grok-plugin', 'plugin.json'),
'utf8'
));
assert.notStrictEqual(installedManifest.hooks, '');
assert.notStrictEqual(installedManifest.mcpServers, '');
const installedMcp = JSON.parse(fs.readFileSync(path.join(receipt.installedRoot, '.mcp.json'), 'utf8'));
assert.deepStrictEqual(Object.keys(installedMcp.mcpServers), ['other-mcp']);
assert.ok(!JSON.stringify(installedMcp).includes('chrome-devtools'));
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('upgrade, uninstall, and rollback are receipt-backed on fixture trees', () => {
const homeDir = createTempDir();
const firstRoot = createTempDir();
const secondRoot = createTempDir();
try {
const first = sourceFixture(firstRoot, FIXTURE_SHA);
const second = sourceFixture(secondRoot, OTHER_SHA);
const firstPlan = adapter.previewInstall({
...first,
homeDir,
trust: true,
consent: { hooks: true },
});
const firstReceipt = adapter.applyInstall(firstPlan, { mcpConfig: first.mcpConfig, now: '2026-09-03T00:00:00.000Z' });
assert.strictEqual(firstReceipt.operation, 'install');
assert.ok(fs.existsSync(firstReceipt.installedRoot));
const secondPlan = adapter.previewInstall({
...second,
homeDir,
trust: true,
consent: { hooks: true, mcp: { 'chrome-devtools': true } },
});
const upgraded = adapter.applyInstall(secondPlan, { mcpConfig: second.mcpConfig, now: '2026-09-03T01:00:00.000Z' });
assert.strictEqual(upgraded.operation, 'upgrade');
assert.strictEqual(upgraded.previousReceiptId, firstReceipt.id);
assert.notStrictEqual(upgraded.installedRoot, firstReceipt.installedRoot);
assert.ok(upgraded.mcpAttached.includes('chrome-devtools'));
assert.ok(fs.existsSync(firstReceipt.installedRoot));
assert.ok(fs.existsSync(upgraded.installedRoot));
assert.strictEqual(adapter.listManagedInstallRoots(homeDir).length, 2);
const rolled = adapter.rollback(homeDir);
assert.strictEqual(rolled.operation, 'rollback');
assert.strictEqual(rolled.id, firstReceipt.id);
assert.strictEqual(adapter.loadCurrentReceipt(homeDir).id, firstReceipt.id);
assert.deepStrictEqual(adapter.loadCurrentReceipt(homeDir).mcpAttached, []);
assert.ok(fs.existsSync(firstReceipt.installedRoot));
assert.ok(!fs.existsSync(upgraded.installedRoot));
const removed = adapter.uninstall(homeDir);
assert.strictEqual(removed.operation, 'uninstall');
assert.ok(!fs.existsSync(firstReceipt.installedRoot));
assert.ok(!fs.existsSync(upgraded.installedRoot));
assert.deepStrictEqual(adapter.listManagedInstallRoots(homeDir), []);
assert.strictEqual(adapter.loadCurrentReceipt(homeDir), null);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(firstRoot, { recursive: true, force: true });
fs.rmSync(secondRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('disabled plugin is not resolved as the active Grok root', () => {
const homeDir = createTempDir();
const sourceRoot = createTempDir();
try {
const fixture = sourceFixture(sourceRoot);
const plan = adapter.previewInstall({
...fixture,
homeDir,
trust: true,
consent: { hooks: true },
});
const receipt = adapter.applyInstall(plan, { mcpConfig: fixture.mcpConfig });
assert.strictEqual(adapter.resolveGrokPluginRoot({ homeDir }), receipt.installedRoot);
adapter.setPluginEnabled(homeDir, false);
assert.strictEqual(adapter.resolveGrokPluginRoot({ homeDir }), null);
assert.strictEqual(adapter.resolveGrokPluginRoot({ homeDir, requireEnabled: false }), receipt.installedRoot);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(sourceRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('multiple cached versions select only the pinned SHA', () => {
const homeDir = createTempDir();
try {
const cacheBase = path.join(homeDir, '.grok', 'plugins', 'cache', 'ecc', 'affaan-m');
const oldRoot = path.join(cacheBase, '2.1.0');
const newRoot = path.join(cacheBase, '2.2.0');
writeCompleteRoot(oldRoot);
writeCompleteRoot(newRoot);
fs.writeFileSync(path.join(oldRoot, adapter.SOURCE_IDENTITY_FILE), JSON.stringify({
source: 'url',
url: 'https://github.com/affaan-m/ECC.git',
sha: OTHER_SHA,
}));
fs.writeFileSync(path.join(newRoot, adapter.SOURCE_IDENTITY_FILE), JSON.stringify({
source: 'url',
url: 'https://github.com/affaan-m/ECC.git',
sha: FIXTURE_SHA,
}));
const versions = adapter.listCachedGrokVersions(homeDir);
assert.strictEqual(versions.length, 2);
const selected = adapter.selectPinnedCachedVersion(versions, FIXTURE_SHA);
assert.strictEqual(selected.installedRoot, newRoot);
assert.strictEqual(
adapter.resolveGrokPluginRoot({ homeDir, env: {}, pinnedSha: FIXTURE_SHA }),
newRoot
);
assert.strictEqual(adapter.selectPinnedCachedVersion(versions, 'cccccccccccccccccccccccccccccccccccccccc'), null);
const cachedRoot = path.join(homeDir, '.grok', 'plugins', 'cache', 'ecc', 'affaan-m', '2.2.1');
writeCompleteRoot(cachedRoot, sha);
assert.strictEqual(adapter.selectPinnedCachedVersion(adapter.listCachedGrokVersions(homeDir), sha).installedRoot, cachedRoot);
assert.strictEqual(adapter.resolveGrokPluginRoot({ homeDir, pinnedSha: sha }), cachedRoot);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('Windows install destination stays under .grok\\installed-plugins', () => {
const dest = adapter.installDestination(
'C:\\Users\\ecc',
{ pluginId: 'ecc', source: { sha: FIXTURE_SHA } },
path.win32
);
if (test('Windows Grok roots stay adapter-owned', () => {
assert.strictEqual(
dest,
path.win32.join('C:\\Users\\ecc', '.grok', 'installed-plugins', `ecc-${FIXTURE_SHA.slice(0, 12)}`)
adapter.grokInstallStatePath('C:\\Users\\ecc', path.win32),
path.win32.join('C:\\Users\\ecc', '.grok', 'ecc', 'install-state.json')
);
assert.ok(adapter.isContained(dest, 'C:\\Users\\ecc\\.grok', path.win32));
assert.strictEqual(adapter.resolveHomeDir({ ECC_GROK_HOME: 'D:\\Users\\ecc' }), 'D:\\Users\\ecc');
})) passed++; else failed++;
if (test('previewInstall and grok-home.planOperations use the same hook/MCP consent gate', () => {
const grokHome = require('../../scripts/lib/install-targets/grok-home');
const mcpConfig = {
mcpServers: {
'chrome-devtools': { command: 'npx' },
'other-mcp': { command: 'echo' },
},
};
const source = {
source: 'url',
url: 'https://github.com/affaan-m/ECC.git',
sha: FIXTURE_SHA,
};
const modules = [
{ id: 'hooks-runtime', paths: ['hooks', 'hooks/hooks.json'] },
{ id: 'mcp', paths: ['.mcp.json', 'mcp-configs'] },
{ id: 'skills', paths: ['skills'] },
];
const consent = { hooks: true, mcp: { 'other-mcp': true, 'chrome-devtools': false } };
const plan = adapter.previewInstall({
source,
trust: true,
consent,
mcpConfig,
});
const operations = grokHome.planOperations({
homeDir: '/Users/example',
modules,
trust: true,
consent,
});
const opPaths = operations.map((operation) => String(operation.sourceRelativePath).replace(/\\/g, '/'));
assert.strictEqual(plan.hooksEnabled, true);
assert.deepStrictEqual(plan.mcpAttached, ['other-mcp']);
assert.ok(opPaths.includes('hooks') || opPaths.includes('hooks/hooks.json'));
assert.ok(!opPaths.some((item) => item === '.mcp.json' || item.startsWith('mcp-configs')));
assert.ok(opPaths.includes('skills'));
})) passed++; else failed++;
if (test('native plugin.json opt-out is the grok plugin --trust contract', () => {
const { spawnSync } = require('child_process');
const pluginJsonPath = path.join(repoRoot, '.grok-plugin', 'plugin.json');
const manifest = adapter.readGrokPluginManifest(pluginJsonPath);
assert.strictEqual(adapter.nativeTrustedInstallAttachesChromeDevtools(manifest), false);
assert.deepStrictEqual(adapter.nativeTrustedMcpServerNames(manifest), []);
const validated = spawnSync('grok', ['plugin', 'validate', '.'], {
cwd: repoRoot,
encoding: 'utf8',
});
if (!validated.error) {
assert.strictEqual(validated.status, 0, validated.stderr || validated.stdout);
assert.ok(!/chrome-devtools/i.test(validated.stdout || ''));
if (test('the adapter exports no private install lifecycle', () => {
for (const name of ['previewInstall', 'applyInstall', 'uninstall', 'rollback', 'runGrokInstall']) {
assert.strictEqual(adapter[name], undefined, name);
}
})) passed++; else failed++;
+2 -1
View File
@@ -103,6 +103,7 @@ function runTests() {
adal: ['project', './.adal'],
hermes: ['home', '~/.hermes'],
openclaw: ['home', '~/.openclaw'],
grok: ['native', '~/.grok'],
};
for (const [id, [scopeId, root]] of Object.entries(expected)) {
@@ -175,7 +176,7 @@ function runTests() {
assert.throws(() => normalizeHarnessSelection('none'), /At least one guided harness/);
assert.throws(() => normalizeHarnessSelection('all,codex'), /cannot be combined/i);
assert.throws(() => normalizeHarnessSelection('cursor'), /advanced.*not guided-ready/i);
assert.throws(() => normalizeHarnessSelection('grok'), /Unknown guided harness selection/);
assert.throws(() => normalizeHarnessSelection('grok'), /advanced.*not guided-ready/i);
})) passed++; else failed++;
if (test('exports deeply frozen records while list helpers return safe array copies', () => {
+17
View File
@@ -52,6 +52,23 @@ function runTests() {
assert.deepStrictEqual(parsed.languages, []);
})) passed++; else failed++;
if (test('parses and validates the Grok rollback source pin', () => {
const sha = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
const parsed = parseInstallArgs([
'node',
'scripts/install-apply.js',
'--target', 'grok',
'--profile', 'core',
'--source-sha', sha,
]);
assert.strictEqual(parsed.sourceSha, sha);
assert.strictEqual(normalizeInstallRequest(parsed).sourceSha, sha);
assert.throws(
() => normalizeInstallRequest({ ...parsed, target: 'claude' }),
/requires --target grok/
);
})) passed++; else failed++;
if (test('parses --locale argument', () => {
const parsed = parseInstallArgs([
'node',
+37 -1
View File
@@ -87,10 +87,46 @@ function runTests() {
consent: { mcp: { 'chrome-devtools': true } },
});
const mcpPaths = mcpAllowed.map((operation) => String(operation.sourceRelativePath).replace(/\\/g, '/'));
assert.ok(!mcpPaths.some((item) => item === '.mcp.json' || item.startsWith('mcp-configs')));
assert.ok(mcpPaths.includes('.mcp.json'));
assert.ok(mcpAllowed.some((operation) => (
operation.contentTransform === 'grok-mcp-consent'
&& operation.grokMcpIds.includes('chrome-devtools')
)));
assert.ok(!mcpPaths.some((item) => item === 'hooks' || item.startsWith('hooks/')));
})) passed++; else failed++;
if (test('grok MCP consent rejects malformed configs and unknown capabilities', () => {
const grok = getInstallTargetAdapter('grok');
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-grok-mcp-'));
try {
fs.writeFileSync(path.join(tempRoot, '.mcp.json'), '{"mcpServers": []}\n');
assert.throws(
() => grok.planOperations({
homeDir: tempRoot,
repoRoot: tempRoot,
modules: [],
trust: true,
consent: { mcp: { 'unknown-mcp': true } },
}),
/mcpServers object/
);
fs.writeFileSync(path.join(tempRoot, '.mcp.json'), '{"mcpServers": {"known-mcp": {}}}\n');
assert.throws(
() => grok.planOperations({
homeDir: tempRoot,
repoRoot: tempRoot,
modules: [],
trust: true,
consent: { mcp: { 'unknown-mcp': true } },
}),
/Unknown Grok MCP capability: unknown-mcp/
);
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('resolves cursor adapter root and install-state path from project root', () => {
const adapter = getInstallTargetAdapter('cursor');
const projectRoot = '/workspace/app';
+3 -139
View File
@@ -2,12 +2,11 @@
* Tests for scripts/lib/resolve-ecc-root.js
*
* Covers the ECC root resolution fallback chain:
* 1. GROK_PLUGIN_ROOT / CLAUDE_PLUGIN_ROOT / ECC_PLUGIN_ROOT env vars
* 1. CLAUDE_PLUGIN_ROOT env var
* 2. Standard install (~/.claude/)
* 3. Exact legacy plugin roots under ~/.claude/plugins/
* 4. Plugin cache auto-detection
* 5. Grok plugin homes (~/.grok/plugins/, ~/.grok/installed-plugins/)
* 6. Fallback to ~/.claude/
* 5. Fallback to ~/.claude/
*/
const assert = require('assert');
@@ -18,11 +17,7 @@ const CURRENT_PACKAGE_VERSION = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8')
).version;
const {
resolveEccRoot,
INLINE_RESOLVE,
pluginRootFromEnv,
} = require('../../scripts/lib/resolve-ecc-root');
const { resolveEccRoot, INLINE_RESOLVE } = require('../../scripts/lib/resolve-ecc-root');
// Sentinel ECC skill that resolveEccRoot() requires (alongside the script tree)
// before accepting a root for skill consumers. Kept in sync with the module's
@@ -62,24 +57,6 @@ function setupLegacyPluginInstall(homeDir, segments) {
fs.mkdirSync(path.join(legacyDir, ECC_SKILL_SENTINEL), { recursive: true });
return legacyDir;
}
function setupVendorPluginInstall(homeDir, vendor, segments) {
const pluginDir = path.join(homeDir, vendor, 'plugins', ...segments);
const scriptDir = path.join(pluginDir, 'scripts', 'lib');
fs.mkdirSync(scriptDir, { recursive: true });
fs.writeFileSync(path.join(scriptDir, 'utils.js'), '// stub');
fs.mkdirSync(path.join(pluginDir, ECC_SKILL_SENTINEL), { recursive: true });
return pluginDir;
}
function setupGrokInstalledPlugin(homeDir, dirName) {
const pluginDir = path.join(homeDir, '.grok', 'installed-plugins', dirName);
const scriptDir = path.join(pluginDir, 'scripts', 'lib');
fs.mkdirSync(scriptDir, { recursive: true });
fs.writeFileSync(path.join(scriptDir, 'utils.js'), '// stub');
fs.mkdirSync(path.join(pluginDir, ECC_SKILL_SENTINEL), { recursive: true });
return pluginDir;
}
function setupPluginCache(homeDir, pluginSlug, orgName, version) {
const cacheDir = path.join(
homeDir, '.claude', 'plugins', 'cache',
@@ -537,119 +514,6 @@ module.exports = { resolveEccRoot() { assert.strictEqual(process.env.HOME, ${JSO
}
})) passed++; else failed++;
if (test('pluginRootFromEnv prefers GROK_PLUGIN_ROOT then CLAUDE then ECC', () => {
assert.strictEqual(
pluginRootFromEnv({
GROK_PLUGIN_ROOT: '/grok/root',
CLAUDE_PLUGIN_ROOT: '/claude/root',
ECC_PLUGIN_ROOT: '/ecc/root',
}),
'/grok/root'
);
assert.strictEqual(
pluginRootFromEnv({
CLAUDE_PLUGIN_ROOT: '/claude/root',
ECC_PLUGIN_ROOT: '/ecc/root',
}),
'/claude/root'
);
assert.strictEqual(pluginRootFromEnv({ ECC_PLUGIN_ROOT: '/ecc/root' }), '/ecc/root');
assert.strictEqual(pluginRootFromEnv({ GROK_PLUGIN_ROOT: ' /trimmed/grok ' }), '/trimmed/grok');
assert.strictEqual(pluginRootFromEnv({ GROK_PLUGIN_ROOT: ' ' }), '');
assert.strictEqual(pluginRootFromEnv({}), '');
})) passed++; else failed++;
if (test('reads GROK_PLUGIN_ROOT from process.env when envRoot is omitted', () => {
const previous = process.env.GROK_PLUGIN_ROOT;
const previousClaude = process.env.CLAUDE_PLUGIN_ROOT;
const previousEcc = process.env.ECC_PLUGIN_ROOT;
const homeDir = createTempDir();
try {
process.env.GROK_PLUGIN_ROOT = '/from/grok/env';
delete process.env.CLAUDE_PLUGIN_ROOT;
delete process.env.ECC_PLUGIN_ROOT;
assert.strictEqual(resolveEccRoot({ homeDir }), '/from/grok/env');
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
if (previous === undefined) delete process.env.GROK_PLUGIN_ROOT;
else process.env.GROK_PLUGIN_ROOT = previous;
if (previousClaude === undefined) delete process.env.CLAUDE_PLUGIN_ROOT;
else process.env.CLAUDE_PLUGIN_ROOT = previousClaude;
if (previousEcc === undefined) delete process.env.ECC_PLUGIN_ROOT;
else process.env.ECC_PLUGIN_ROOT = previousEcc;
}
})) passed++; else failed++;
if (test('finds Grok plugin install at ~/.grok/plugins/ecc', () => {
const homeDir = createTempDir();
try {
const expected = setupVendorPluginInstall(homeDir, '.grok', ['ecc']);
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('finds Grok plugin install at ~/.grok/plugins/ecc@ecc', () => {
const homeDir = createTempDir();
try {
const expected = setupVendorPluginInstall(homeDir, '.grok', ['ecc@ecc']);
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('finds Grok marketplace plugin install at ~/.grok/plugins/marketplaces/ecc', () => {
const homeDir = createTempDir();
try {
const expected = setupVendorPluginInstall(homeDir, '.grok', ['marketplaces', 'ecc']);
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('finds Grok installed-plugins hash directory when that is the only complete root', () => {
const homeDir = createTempDir();
try {
const expected = setupGrokInstalledPlugin(homeDir, 'ecc-ab12cd34');
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('prefers a complete Claude root over a complete Grok root when env is unset', () => {
const homeDir = createTempDir();
try {
const claudeDir = setupStandardInstall(homeDir);
setupGrokInstalledPlugin(homeDir, 'ecc-ab12cd34');
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, claudeDir);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('INLINE_RESOLVE returns GROK_PLUGIN_ROOT when set', () => {
const { execFileSync } = require('child_process');
const env = { ...process.env, GROK_PLUGIN_ROOT: '/inline/grok/root' };
delete env.CLAUDE_PLUGIN_ROOT;
delete env.ECC_PLUGIN_ROOT;
const result = execFileSync('node', [
'-e', `console.log(${INLINE_RESOLVE})`,
], {
env,
encoding: 'utf8',
}).trim();
assert.strictEqual(result, '/inline/grok/root');
})) passed++; else failed++;
if (test('INLINE_RESOLVE falls back to ~/.claude/ when nothing found', () => {
const homeDir = createTempDir();
try {
+3 -3
View File
@@ -733,7 +733,7 @@ test('grok plugin.json keeps Claude-incompatible fields out of the Grok manifest
assert.strictEqual(
grokPlugin.hooks,
'',
'Empty hooks string opts the native Grok CLI surface out of hooks/hooks.json; ECC hook consent is applyInstall'
'Empty hooks string opts the native Grok CLI surface out of hooks/hooks.json; ECC hook consent is canonical install state'
);
});
@@ -805,8 +805,8 @@ test('package.json files includes the Grok plugin directory', () => {
test('.grok-plugin README documents install, trust, enable, MCP, and hook profile', () => {
const readme = fs.readFileSync(grokPluginReadmePath, 'utf8');
assert.ok(readme.includes('previewInstall'), 'ECC trusted install is previewInstall');
assert.ok(readme.includes('applyInstall'), 'ECC trusted install is applyInstall');
assert.ok(readme.includes('canonical installer'), 'ECC trusted install uses the canonical installer');
assert.ok(readme.includes('install-apply.js --target grok'), 'Grok entrypoint delegates to install-apply');
assert.ok(readme.includes('grok-install.js'), 'Operator ECC consent path is scripts/grok-install.js');
assert.ok(readme.includes('grok plugin marketplace add affaan-m/ECC'), 'Expected marketplace add for the canonical ECC repo');
assert.ok(readme.includes('grok plugin enable ecc'), 'Grok plugins stay off until enabled');