@ironbee-ai/cli 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/clients/claude/agents/ironbee-scenario.md +4 -1
  3. package/dist/clients/claude/agents/ironbee-verifier.md +21 -3
  4. package/dist/clients/claude/hooks/require-verdict.js +2 -2
  5. package/dist/clients/claude/hooks/require-verification.js +3 -3
  6. package/dist/clients/claude/hooks/track-action-monitor.js +1 -1
  7. package/dist/clients/claude/hooks/track-action.js +1 -1
  8. package/dist/clients/claude/index.js +4 -4
  9. package/dist/clients/claude/platforms/scenario.terminal.md +26 -0
  10. package/dist/clients/claude/platforms/skill.browser.md +1 -1
  11. package/dist/clients/claude/platforms/skill.terminal.md +62 -0
  12. package/dist/clients/codex/agents/ironbee-scenario.md +3 -0
  13. package/dist/clients/codex/agents/ironbee-verifier.md +20 -2
  14. package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.main.md +3 -0
  15. package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.main.md +3 -0
  16. package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.main.md +3 -0
  17. package/dist/clients/codex/commands/ironbee-verify/SKILL.main.md +3 -0
  18. package/dist/clients/codex/hooks/require-verification.js +1 -1
  19. package/dist/clients/codex/hooks/track-action.js +1 -1
  20. package/dist/clients/codex/index.js +2 -2
  21. package/dist/clients/codex/platforms/command-verify.terminal.md +61 -0
  22. package/dist/clients/codex/platforms/rule.terminal.md +31 -0
  23. package/dist/clients/codex/platforms/scenario.terminal.md +36 -0
  24. package/dist/clients/codex/platforms/skill.browser.md +1 -1
  25. package/dist/clients/codex/platforms/skill.terminal.md +57 -0
  26. package/dist/clients/codex/rules/ironbee-verification.main.md +3 -0
  27. package/dist/clients/codex/skills/ironbee-verification.main.md +3 -0
  28. package/dist/clients/codex/util.js +1 -1
  29. package/dist/clients/cursor/commands/ironbee-manage-scenario/SKILL.md +3 -0
  30. package/dist/clients/cursor/commands/ironbee-search-scenario/SKILL.md +3 -0
  31. package/dist/clients/cursor/commands/ironbee-sync-scenario/SKILL.md +3 -0
  32. package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +3 -0
  33. package/dist/clients/cursor/hooks/require-verdict.js +2 -2
  34. package/dist/clients/cursor/hooks/require-verification.js +3 -3
  35. package/dist/clients/cursor/hooks/track-action-monitor.js +1 -1
  36. package/dist/clients/cursor/hooks/track-action.js +1 -1
  37. package/dist/clients/cursor/index.js +1 -1
  38. package/dist/clients/cursor/platforms/command-verify.terminal.md +61 -0
  39. package/dist/clients/cursor/platforms/rule.terminal.md +31 -0
  40. package/dist/clients/cursor/platforms/scenario.terminal.md +29 -0
  41. package/dist/clients/cursor/platforms/skill.browser.md +1 -1
  42. package/dist/clients/cursor/platforms/skill.terminal.md +54 -0
  43. package/dist/clients/cursor/rules/ironbee-verification.mdc +3 -0
  44. package/dist/clients/cursor/skills/ironbee-verification.md +9 -0
  45. package/dist/commands/config.js +2 -2
  46. package/dist/commands/install.js +1 -1
  47. package/dist/commands/scenario.js +1 -1
  48. package/dist/commands/terminal.js +1 -0
  49. package/dist/hooks/core/verification-context.js +19 -15
  50. package/dist/hooks/core/verify-gate.js +25 -20
  51. package/dist/import/claude/events/tool-call.js +1 -1
  52. package/dist/import/codex/events/tool-call.js +1 -1
  53. package/dist/index.js +1 -1
  54. package/dist/lib/config.js +1 -1
  55. package/dist/lib/install-version.js +1 -1
  56. package/dist/lib/platform-section.js +5 -4
  57. package/dist/lib/scenario-staleness.js +1 -1
  58. package/dist/tui/config/schema.js +1 -1
  59. package/dist/tui/platforms/area.js +2 -2
  60. package/package.json +1 -1
@@ -1,3 +1,3 @@
1
- "use strict";var M=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ae=Object.prototype.hasOwnProperty;var S=(f,e)=>M(f,"name",{value:e,configurable:!0});var le=(f,e)=>{for(var o in e)M(f,o,{get:e[o],enumerable:!0})},ce=(f,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of se(e))!ae.call(f,i)&&i!==o&&M(f,i,{get:()=>e[i],enumerable:!(r=re(e,i))||r.enumerable});return f};var de=f=>ce(M({},"__esModule",{value:!0}),f);var fe={};le(fe,{CodexClient:()=>ue});module.exports=de(fe);var s=require("fs"),m=require("path"),U=require("../../lib/gitignore"),b=require("../../lib/logger"),l=require("../../lib/output"),O=require("../../lib/fs-prune"),d=require("../../lib/config"),C=require("../../lib/platform-section"),n=require("./util"),H=require("./thread-map"),q=require("../../lib/runtime-paths"),W=require("./hooks/verify-gate"),D=require("./hooks/activity-end"),X=require("./hooks/session-start"),Y=require("./hooks/activity-start"),z=require("./hooks/require-verification"),Q=require("./hooks/require-verdict"),Z=require("./hooks/clear-verdict"),j=require("./hooks/track-action"),ee=require("./hooks/track-action-monitor"),oe=require("./hooks/track-action-pre"),ne=require("./hooks/subagent-start"),te=require("./hooks/subagent-stop");const B="~/.ironbee/projects",E="browser-devtools",A="node-devtools",_="backend-devtools",I="android-devtools",ge="ironbee",$="ironbee-verifier",V=30,L="Verifies recent code changes through real browser/runtime/backend tools and submits the IronBee verdict. Spawn this custom agent (by agent_type) after editing code to run the verification cycle out-of-band \u2014 it drives the devtools tools, judges the result, and records the verdict in the shared session. It does NOT edit code.",x="ironbee-scenario",J=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"],F="Manages and searches reusable IronBee verification scenarios via the devtools scenario tools. Spawn this custom agent (by agent_type) from the scenario slash commands to author/update/delete saved scenarios and find them by name/description/metadata. NOT a verification cycle (running a saved scenario to verify is done via $ironbee-verify scenario:<name>).";function P(f){return(0,m.join)(__dirname,"..",f,"platforms")}S(P,"platformsDirFor");function y(f){return l.pc.dim(f)}S(y,"codexColor");function G(f){return f.hooks.some(e=>e.command.includes(ge))}S(G,"isIronBeeHookGroup");function me(f){const e=Object.keys(f);return e.length===0?!0:e.length===1&&e[0]==="hooks"?Object.keys(f.hooks??{}).length===0:!1}S(me,"isCodexHooksEmpty");class ue{constructor(){this.name="codex";this.supportsVerifierModel=!0}static{S(this,"CodexClient")}detect(e){return(0,s.existsSync)((0,m.join)(e,".agents","skills","ironbee-verify"))}resolveProjectDir(){return process.env.CODEX_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(e,o){const r=o??(0,d.loadConfig)(e),i=(0,d.getVerificationMode)(r),t=i!=="monitor",a=(0,d.getCodexVerifierMode)(r);this.cleanupArtifacts(e);const g=(0,n.codexHooksJsonPath)(e);if(this.mergeHooksConfig(g,i,a),this.mergeConfigToml(e,r,t,a),t&&(i==="enforce"&&this.writeAgentsMdBlock(e,r,a),this.writeSkills(e,i==="enforce",r,a),(0,C.syncPlatformSectionsToConfig)(e,P)),(0,U.ensureIronBeeGitignored)(e),console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} hooks ${l.pc.dim("\u2192")} ${l.pc.dim(g)}`),console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} config ${l.pc.dim("\u2192")} ${l.pc.dim((0,n.codexConfigTomlPath)(e))}`),t){const p=a==="main-agent"?`${l.pc.yellow("main-agent")} (the main agent drives the devtools tools directly)`:`${l.pc.bold("sub-agent")} (delegated to the ironbee-verifier custom agent)`;console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} verify ${l.pc.dim("\u2192")} ${p}`)}i==="enforce"?(console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} agents ${l.pc.dim("\u2192")} ${l.pc.dim((0,m.join)(e,"AGENTS.md"))}`),console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} skill ${l.pc.dim("\u2192")} ${l.pc.dim((0,m.join)(e,".agents","skills","ironbee-verification","SKILL.md"))}`),console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} command ${l.pc.dim("\u2192")} ${l.pc.dim((0,m.join)(e,".agents","skills","ironbee-verify","SKILL.md"))}`)):i==="assist"?(console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} ${l.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual $ironbee-verify only, no enforcement`),console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} command ${l.pc.dim("\u2192")} ${l.pc.dim((0,m.join)(e,".agents","skills","ironbee-verify","SKILL.md"))}`)):console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} ${l.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(),console.log(` ${l.pc.yellow("\u26A0")} ${l.pc.yellow("Codex requires one-time TUI setup:")}`),console.log(` ${l.pc.yellow("1.")} Run ${l.pc.bold("/hooks")} in a fresh Codex session to review and trust IronBee hooks`),console.log(` ${l.pc.yellow("2.")} Restart any open Codex sessions to pick up new hook config`)}uninstall(e){this.cleanupArtifacts(e),(0,s.existsSync)((0,n.codexHooksJsonPath)(e))||this.removeFeaturesHooksFlag(e),(0,O.pruneEmptyDirs)((0,m.join)(e,".codex"));const o=(0,H.codexThreadMapPath)(e);if((0,s.existsSync)(o))try{(0,s.unlinkSync)(o)}catch(r){b.logger.debug(`failed to remove codex thread map: ${r}`)}console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} removed hooks, MCP entries, AGENTS.md block, and skills`)}removeFeaturesHooksFlag(e){const o=(0,n.codexConfigTomlPath)(e);if((0,s.existsSync)(o))try{const r=(0,s.readFileSync)(o,"utf-8");let i=(0,n.removeFeaturesHooks)(r);i=(0,n.removeSandboxWritableRoot)(i,B),i.trim().length===0?(0,s.unlinkSync)(o):i!==r&&(0,s.writeFileSync)(o,i)}catch(r){b.logger.debug(`failed to strip [features] hooks from config.toml: ${r}`)}}cleanupArtifacts(e){this.migrateAwayFromUserLevel();const o=(0,n.codexHooksJsonPath)(e);this.removeIronBeeHooks(o),this.maybeDeleteEmptyHooks(o),this.removeIronBeeMcpServers(e),this.removeVerifierAgentToml(e),this.removeScenarioAgentToml(e);const r=(0,m.join)(e,"AGENTS.md");if((0,s.existsSync)(r))try{const t=(0,s.readFileSync)(r,"utf-8"),a=(0,n.stripAgentsMdBlock)(t);a===null?(0,s.unlinkSync)(r):a!==t&&(0,s.writeFileSync)(r,a)}catch(t){b.logger.debug(`failed to strip AGENTS.md block: ${t}`)}const i=(0,m.join)(e,".agents","skills");this.removeDir((0,m.join)(i,"ironbee-verification")),this.removeDir((0,m.join)(i,"ironbee-verify"));for(const t of J)this.removeDir((0,m.join)(i,t));this.removeDir((0,m.join)(i,"ironbee-run-scenario")),(0,O.pruneEmptyDirs)((0,m.join)(e,".agents"))}async runVerifyGate(e){await(0,W.run)(e)}async runActivityEnd(e){await(0,D.run)(e)}async runSessionStart(e){await(0,X.run)(e)}async runActivityStart(e){await(0,Y.run)(e)}async runRequireVerification(e,o){await(0,z.run)(e,o)}async runRequireVerdict(e,o){await(0,Q.run)(e,o)}async runClearVerdict(e){await(0,Z.run)(e)}async runTrackAction(e){await(0,j.run)(e)}async runTrackActionMonitor(e){await(0,ee.run)(e)}async runTrackActionPre(e){await(0,oe.run)(e)}async runSubagentStart(e){await(0,ne.run)(e)}async runSubagentStop(e){await(0,te.run)(e)}resolveAgentSessionId(e,o){const r=process.env.CODEX_THREAD_ID;if(typeof r=="string"&&r.length>0&&o)return(0,H.lookupThreadSession)(o,r)}async runSessionEnd(e){b.logger.debug("session-end: no-op on Codex (no SessionEnd hook event)")}mergeHooksConfig(e,o,r){const i=o!=="monitor",t=o==="assist"?" --soft":"";(0,s.mkdirSync)((0,m.dirname)(e),{recursive:!0});let a={hooks:{}};if((0,s.existsSync)(e))try{a=JSON.parse((0,s.readFileSync)(e,"utf-8")),a.hooks||(a.hooks={})}catch(v){b.logger.debug(`failed to parse ${e}: ${v}`),a={hooks:{}}}for(const v of Object.keys(a.hooks)){const c=a.hooks[v].filter(h=>!G(h));c.length===0?delete a.hooks[v]:a.hooks[v]=c}const g=S((v,c,h)=>{a.hooks[v]||(a.hooks[v]=[]),a.hooks[v].push({matcher:c,hooks:[{type:"command",command:h}]})},"addGroup");g("SessionStart",".*","ironbee hook session-start --client codex"),g("UserPromptSubmit",".*","ironbee hook activity-start --client codex"),g("PreToolUse",".*","ironbee hook track-action-pre --client codex"),i&&(g("PreToolUse","^mcp__(browser|node|backend|android)[-_]devtools__.*",`ironbee hook require-verification --client codex${t}`),g("PreToolUse","^apply_patch$",`ironbee hook require-verdict --client codex${t}`),g("PostToolUse","^apply_patch$","ironbee hook clear-verdict --client codex"),r==="sub-agent"&&g("SubagentStart",".*","ironbee hook subagent-start --client codex")),g("SubagentStop",".*","ironbee hook subagent-stop --client codex"),g("PostToolUse",".*",i?"ironbee hook track-action --client codex":"ironbee hook track-action-monitor --client codex"),g("Stop",".*",o==="enforce"?"ironbee hook verify-gate --client codex":"ironbee hook activity-end --client codex"),(0,s.writeFileSync)(e,JSON.stringify(a,null,2))}removeIronBeeHooks(e){if((0,s.existsSync)(e))try{const o=(0,s.readFileSync)(e,"utf-8"),r=JSON.parse(o);if(!r.hooks)return;let i=!1;for(const t of Object.keys(r.hooks)){const a=r.hooks[t].filter(g=>!G(g));a.length!==r.hooks[t].length&&(i=!0),a.length===0?delete r.hooks[t]:r.hooks[t]=a}i&&(0,s.writeFileSync)(e,JSON.stringify(r,null,2))}catch(o){b.logger.debug(`failed to strip IronBee hooks from ${e}: ${o}`)}}maybeDeleteEmptyHooks(e){if((0,s.existsSync)(e))try{const o=JSON.parse((0,s.readFileSync)(e,"utf-8"));me(o)&&(0,s.unlinkSync)(e)}catch(o){b.logger.debug(`failed to inspect ${e} for emptiness: ${o}`)}}mergeConfigToml(e,o,r,i){(0,s.mkdirSync)((0,m.join)(e,".codex"),{recursive:!0});let t=(0,n.readCodexConfigToml)(e);if(t=(0,n.ensureFeaturesHooksTrue)(t),t=r&&(0,q.resolveRuntimeLocation)(e)==="external"?(0,n.ensureSandboxWritableRoot)(t,B):(0,n.removeSandboxWritableRoot)(t,B),t=(0,n.removeMcpServer)(t,E),t=(0,n.removeMcpServer)(t,A),t=(0,n.removeMcpServer)(t,_),t=(0,n.removeMcpServer)(t,I),r&&i==="main-agent"){t=this.upsertSessionMcpServers(t,e,o),t=(0,n.removeAgentsTable)(t,$),t=(0,n.removeAgentsTable)(t,x),t=(0,n.removeMultiAgentV2SpawnMetadata)(t),this.removeVerifierAgentToml(e),this.removeScenarioAgentToml(e),(0,n.writeCodexConfigToml)(e,t);return}if(r){const g=(0,d.getVerificationModel)(o,"codex"),p=(0,s.existsSync)((0,n.userCodexConfigTomlPath)())?(0,s.readFileSync)((0,n.userCodexConfigTomlPath)(),"utf-8"):"",u=(0,n.extractTomlTopLevelModel)(t)===null&&(0,n.extractTomlTopLevelModel)(p)===null;g===void 0&&u&&console.log(` ${l.pc.dim("\u2192")} ${y("[codex]")} ${l.pc.yellow("\u26A0 no model for the verifier")} \u2014 the ${l.pc.bold("ironbee-verifier")} sub-agent inherits the session model, but neither this project's .codex/config.toml nor ~/.codex/config.toml has a top-level ${l.pc.bold("model")}, so it may fail to spawn ("could not resolve the child model"). Fix: set ${l.pc.bold("model")} in ~/.codex/config.toml, or set ${l.pc.bold("verification.model")} in your ironbee config.`),this.writeVerifierAgentToml(e,o,g),t=(0,n.upsertAgentsTable)(t,$,[`description = ${JSON.stringify(L)}`,`config_file = ${JSON.stringify(`agents/${$}.toml`)}`]),t=(0,n.ensureMultiAgentV2SpawnMetadataExposed)(t),this.writeScenarioAgentToml(e,o,g),t=(0,n.upsertAgentsTable)(t,x,[`description = ${JSON.stringify(F)}`,`config_file = ${JSON.stringify(`agents/${x}.toml`)}`])}else t=(0,n.removeAgentsTable)(t,$),t=(0,n.removeAgentsTable)(t,x),t=(0,n.removeMultiAgentV2SpawnMetadata)(t),this.removeVerifierAgentToml(e),this.removeScenarioAgentToml(e);(0,n.writeCodexConfigToml)(e,t)}writeVerifierAgentToml(e,o,r){this.writeCustomAgentToml(e,o,r,$,L,"skill","read-only")}writeScenarioAgentToml(e,o,r){this.writeCustomAgentToml(e,o,r,x,F,"scenario","read-only")}writeCustomAgentToml(e,o,r,i,t,a,g){const p=(0,m.join)(__dirname,"agents",`${i}.md`);let u;try{u=(0,s.readFileSync)(p,"utf-8")}catch(k){b.logger.debug(`failed to read agent source ${p}: ${k}`);return}const v=P("codex");for(const k of d.ALL_CYCLES){const w=(0,d.isCycleEnabled)(o,k)?ie=>{const N=(0,m.join)(v,(0,C.fragmentFilename)(a,k,ie));return(0,s.existsSync)(N)?(0,s.readFileSync)(N,"utf-8").trimEnd():null}:null;u=(0,C.applyPlatformSection)(u,k,w,`${i}.toml`)}const c=[];c.push(`name = ${JSON.stringify(i)}`),c.push(`description = ${JSON.stringify(t)}`),c.push(`sandbox_mode = ${JSON.stringify(g)}`),r&&c.push(`model = ${JSON.stringify(r)}`),c.push("developer_instructions = '''"),c.push(u.replace(/'''/g,"```").trimEnd()),c.push("'''");const h=S((k,T,w)=>{k&&(c.push(""),c.push(`[mcp_servers.${T}]`),c.push(...K(w)),c.push(`startup_timeout_sec = ${V}`),c.push("required = true"),c.push('default_tools_approval_mode = "approve"'))},"addCycle");h((0,d.isCycleEnabled)(o,"browser"),E,(0,d.getMcpServerEntry)(e)),h((0,d.isCycleEnabled)(o,"node"),A,(0,d.getNodeDevToolsMcpEntry)(e)),h((0,d.isCycleEnabled)(o,"backend"),_,(0,d.getBackendDevToolsMcpEntry)(e)),h((0,d.isCycleEnabled)(o,"android"),I,(0,d.getAndroidDevToolsMcpEntry)(e));const R=(0,n.codexAgentTomlPath)(e,i);(0,s.mkdirSync)((0,m.dirname)(R),{recursive:!0}),(0,s.writeFileSync)(R,c.join(`
1
+ "use strict";var P=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var le=Object.prototype.hasOwnProperty;var S=(f,e)=>P(f,"name",{value:e,configurable:!0});var ce=(f,e)=>{for(var o in e)P(f,o,{get:e[o],enumerable:!0})},de=(f,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ae(e))!le.call(f,i)&&i!==o&&P(f,i,{get:()=>e[i],enumerable:!(r=se(e,i))||r.enumerable});return f};var ge=f=>de(P({},"__esModule",{value:!0}),f);var ve={};ce(ve,{CodexClient:()=>fe});module.exports=ge(ve);var s=require("fs"),m=require("path"),q=require("../../lib/gitignore"),b=require("../../lib/logger"),c=require("../../lib/output"),N=require("../../lib/fs-prune"),l=require("../../lib/config"),C=require("../../lib/platform-section"),n=require("./util"),B=require("./thread-map"),W=require("../../lib/runtime-paths"),X=require("./hooks/verify-gate"),D=require("./hooks/activity-end"),Y=require("./hooks/session-start"),z=require("./hooks/activity-start"),Q=require("./hooks/require-verification"),Z=require("./hooks/require-verdict"),j=require("./hooks/clear-verdict"),ee=require("./hooks/track-action"),oe=require("./hooks/track-action-monitor"),ne=require("./hooks/track-action-pre"),te=require("./hooks/subagent-start"),ie=require("./hooks/subagent-stop");const O="~/.ironbee/projects",w="browser-devtools",A="node-devtools",_="backend-devtools",I="android-devtools",R="terminal-devtools",me="ironbee",$="ironbee-verifier",L=30,J="Verifies recent code changes through real browser/runtime/backend tools and submits the IronBee verdict. Spawn this custom agent (by agent_type) after editing code to run the verification cycle out-of-band \u2014 it drives the devtools tools, judges the result, and records the verdict in the shared session. It does NOT edit code.",x="ironbee-scenario",F=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"],G="Manages and searches reusable IronBee verification scenarios via the devtools scenario tools. Spawn this custom agent (by agent_type) from the scenario slash commands to author/update/delete saved scenarios and find them by name/description/metadata. NOT a verification cycle (running a saved scenario to verify is done via $ironbee-verify scenario:<name>).";function H(f){return(0,m.join)(__dirname,"..",f,"platforms")}S(H,"platformsDirFor");function y(f){return c.pc.dim(f)}S(y,"codexColor");function K(f){return f.hooks.some(e=>e.command.includes(me))}S(K,"isIronBeeHookGroup");function ue(f){const e=Object.keys(f);return e.length===0?!0:e.length===1&&e[0]==="hooks"?Object.keys(f.hooks??{}).length===0:!1}S(ue,"isCodexHooksEmpty");class fe{constructor(){this.name="codex";this.supportsVerifierModel=!0}static{S(this,"CodexClient")}detect(e){return(0,s.existsSync)((0,m.join)(e,".agents","skills","ironbee-verify"))}resolveProjectDir(){return process.env.CODEX_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(e,o){const r=o??(0,l.loadConfig)(e),i=(0,l.getVerificationMode)(r),t=i!=="monitor",a=(0,l.getCodexVerifierMode)(r);this.cleanupArtifacts(e);const g=(0,n.codexHooksJsonPath)(e);if(this.mergeHooksConfig(g,i,a),this.mergeConfigToml(e,r,t,a),t&&(i==="enforce"&&this.writeAgentsMdBlock(e,r,a),this.writeSkills(e,i==="enforce",r,a),(0,C.syncPlatformSectionsToConfig)(e,H)),(0,q.ensureIronBeeGitignored)(e),console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} hooks ${c.pc.dim("\u2192")} ${c.pc.dim(g)}`),console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} config ${c.pc.dim("\u2192")} ${c.pc.dim((0,n.codexConfigTomlPath)(e))}`),t){const h=a==="main-agent"?`${c.pc.yellow("main-agent")} (the main agent drives the devtools tools directly)`:`${c.pc.bold("sub-agent")} (delegated to the ironbee-verifier custom agent)`;console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} verify ${c.pc.dim("\u2192")} ${h}`)}i==="enforce"?(console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} agents ${c.pc.dim("\u2192")} ${c.pc.dim((0,m.join)(e,"AGENTS.md"))}`),console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} skill ${c.pc.dim("\u2192")} ${c.pc.dim((0,m.join)(e,".agents","skills","ironbee-verification","SKILL.md"))}`),console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} command ${c.pc.dim("\u2192")} ${c.pc.dim((0,m.join)(e,".agents","skills","ironbee-verify","SKILL.md"))}`)):i==="assist"?(console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} ${c.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual $ironbee-verify only, no enforcement`),console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} command ${c.pc.dim("\u2192")} ${c.pc.dim((0,m.join)(e,".agents","skills","ironbee-verify","SKILL.md"))}`)):console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} ${c.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(),console.log(` ${c.pc.yellow("\u26A0")} ${c.pc.yellow("Codex requires one-time TUI setup:")}`),console.log(` ${c.pc.yellow("1.")} Run ${c.pc.bold("/hooks")} in a fresh Codex session to review and trust IronBee hooks`),console.log(` ${c.pc.yellow("2.")} Restart any open Codex sessions to pick up new hook config`)}uninstall(e){this.cleanupArtifacts(e),(0,s.existsSync)((0,n.codexHooksJsonPath)(e))||this.removeFeaturesHooksFlag(e),(0,N.pruneEmptyDirs)((0,m.join)(e,".codex"));const o=(0,B.codexThreadMapPath)(e);if((0,s.existsSync)(o))try{(0,s.unlinkSync)(o)}catch(r){b.logger.debug(`failed to remove codex thread map: ${r}`)}console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} removed hooks, MCP entries, AGENTS.md block, and skills`)}removeFeaturesHooksFlag(e){const o=(0,n.codexConfigTomlPath)(e);if((0,s.existsSync)(o))try{const r=(0,s.readFileSync)(o,"utf-8");let i=(0,n.removeFeaturesHooks)(r);i=(0,n.removeSandboxWritableRoot)(i,O),i.trim().length===0?(0,s.unlinkSync)(o):i!==r&&(0,s.writeFileSync)(o,i)}catch(r){b.logger.debug(`failed to strip [features] hooks from config.toml: ${r}`)}}cleanupArtifacts(e){this.migrateAwayFromUserLevel();const o=(0,n.codexHooksJsonPath)(e);this.removeIronBeeHooks(o),this.maybeDeleteEmptyHooks(o),this.removeIronBeeMcpServers(e),this.removeVerifierAgentToml(e),this.removeScenarioAgentToml(e);const r=(0,m.join)(e,"AGENTS.md");if((0,s.existsSync)(r))try{const t=(0,s.readFileSync)(r,"utf-8"),a=(0,n.stripAgentsMdBlock)(t);a===null?(0,s.unlinkSync)(r):a!==t&&(0,s.writeFileSync)(r,a)}catch(t){b.logger.debug(`failed to strip AGENTS.md block: ${t}`)}const i=(0,m.join)(e,".agents","skills");this.removeDir((0,m.join)(i,"ironbee-verification")),this.removeDir((0,m.join)(i,"ironbee-verify"));for(const t of F)this.removeDir((0,m.join)(i,t));this.removeDir((0,m.join)(i,"ironbee-run-scenario")),(0,N.pruneEmptyDirs)((0,m.join)(e,".agents"))}async runVerifyGate(e){await(0,X.run)(e)}async runActivityEnd(e){await(0,D.run)(e)}async runSessionStart(e){await(0,Y.run)(e)}async runActivityStart(e){await(0,z.run)(e)}async runRequireVerification(e,o){await(0,Q.run)(e,o)}async runRequireVerdict(e,o){await(0,Z.run)(e,o)}async runClearVerdict(e){await(0,j.run)(e)}async runTrackAction(e){await(0,ee.run)(e)}async runTrackActionMonitor(e){await(0,oe.run)(e)}async runTrackActionPre(e){await(0,ne.run)(e)}async runSubagentStart(e){await(0,te.run)(e)}async runSubagentStop(e){await(0,ie.run)(e)}resolveAgentSessionId(e,o){const r=process.env.CODEX_THREAD_ID;if(typeof r=="string"&&r.length>0&&o)return(0,B.lookupThreadSession)(o,r)}async runSessionEnd(e){b.logger.debug("session-end: no-op on Codex (no SessionEnd hook event)")}mergeHooksConfig(e,o,r){const i=o!=="monitor",t=o==="assist"?" --soft":"";(0,s.mkdirSync)((0,m.dirname)(e),{recursive:!0});let a={hooks:{}};if((0,s.existsSync)(e))try{a=JSON.parse((0,s.readFileSync)(e,"utf-8")),a.hooks||(a.hooks={})}catch(v){b.logger.debug(`failed to parse ${e}: ${v}`),a={hooks:{}}}for(const v of Object.keys(a.hooks)){const d=a.hooks[v].filter(p=>!K(p));d.length===0?delete a.hooks[v]:a.hooks[v]=d}const g=S((v,d,p)=>{a.hooks[v]||(a.hooks[v]=[]),a.hooks[v].push({matcher:d,hooks:[{type:"command",command:p}]})},"addGroup");g("SessionStart",".*","ironbee hook session-start --client codex"),g("UserPromptSubmit",".*","ironbee hook activity-start --client codex"),g("PreToolUse",".*","ironbee hook track-action-pre --client codex"),i&&(g("PreToolUse","^mcp__(browser|node|backend|android|terminal)[-_]devtools__.*",`ironbee hook require-verification --client codex${t}`),g("PreToolUse","^apply_patch$",`ironbee hook require-verdict --client codex${t}`),g("PostToolUse","^apply_patch$","ironbee hook clear-verdict --client codex"),r==="sub-agent"&&g("SubagentStart",".*","ironbee hook subagent-start --client codex")),g("SubagentStop",".*","ironbee hook subagent-stop --client codex"),g("PostToolUse",".*",i?"ironbee hook track-action --client codex":"ironbee hook track-action-monitor --client codex"),g("Stop",".*",o==="enforce"?"ironbee hook verify-gate --client codex":"ironbee hook activity-end --client codex"),(0,s.writeFileSync)(e,JSON.stringify(a,null,2))}removeIronBeeHooks(e){if((0,s.existsSync)(e))try{const o=(0,s.readFileSync)(e,"utf-8"),r=JSON.parse(o);if(!r.hooks)return;let i=!1;for(const t of Object.keys(r.hooks)){const a=r.hooks[t].filter(g=>!K(g));a.length!==r.hooks[t].length&&(i=!0),a.length===0?delete r.hooks[t]:r.hooks[t]=a}i&&(0,s.writeFileSync)(e,JSON.stringify(r,null,2))}catch(o){b.logger.debug(`failed to strip IronBee hooks from ${e}: ${o}`)}}maybeDeleteEmptyHooks(e){if((0,s.existsSync)(e))try{const o=JSON.parse((0,s.readFileSync)(e,"utf-8"));ue(o)&&(0,s.unlinkSync)(e)}catch(o){b.logger.debug(`failed to inspect ${e} for emptiness: ${o}`)}}mergeConfigToml(e,o,r,i){(0,s.mkdirSync)((0,m.join)(e,".codex"),{recursive:!0});let t=(0,n.readCodexConfigToml)(e);if(t=(0,n.ensureFeaturesHooksTrue)(t),t=r&&(0,W.resolveRuntimeLocation)(e)==="external"?(0,n.ensureSandboxWritableRoot)(t,O):(0,n.removeSandboxWritableRoot)(t,O),t=(0,n.removeMcpServer)(t,w),t=(0,n.removeMcpServer)(t,A),t=(0,n.removeMcpServer)(t,_),t=(0,n.removeMcpServer)(t,I),t=(0,n.removeMcpServer)(t,R),r&&i==="main-agent"){t=this.upsertSessionMcpServers(t,e,o),t=(0,n.removeAgentsTable)(t,$),t=(0,n.removeAgentsTable)(t,x),t=(0,n.removeMultiAgentV2SpawnMetadata)(t),this.removeVerifierAgentToml(e),this.removeScenarioAgentToml(e),(0,n.writeCodexConfigToml)(e,t);return}if(r){const g=(0,l.getVerificationModel)(o,"codex"),h=(0,s.existsSync)((0,n.userCodexConfigTomlPath)())?(0,s.readFileSync)((0,n.userCodexConfigTomlPath)(),"utf-8"):"",u=(0,n.extractTomlTopLevelModel)(t)===null&&(0,n.extractTomlTopLevelModel)(h)===null;g===void 0&&u&&console.log(` ${c.pc.dim("\u2192")} ${y("[codex]")} ${c.pc.yellow("\u26A0 no model for the verifier")} \u2014 the ${c.pc.bold("ironbee-verifier")} sub-agent inherits the session model, but neither this project's .codex/config.toml nor ~/.codex/config.toml has a top-level ${c.pc.bold("model")}, so it may fail to spawn ("could not resolve the child model"). Fix: set ${c.pc.bold("model")} in ~/.codex/config.toml, or set ${c.pc.bold("verification.model")} in your ironbee config.`),this.writeVerifierAgentToml(e,o,g),t=(0,n.upsertAgentsTable)(t,$,[`description = ${JSON.stringify(J)}`,`config_file = ${JSON.stringify(`agents/${$}.toml`)}`]),t=(0,n.ensureMultiAgentV2SpawnMetadataExposed)(t),this.writeScenarioAgentToml(e,o,g),t=(0,n.upsertAgentsTable)(t,x,[`description = ${JSON.stringify(G)}`,`config_file = ${JSON.stringify(`agents/${x}.toml`)}`])}else t=(0,n.removeAgentsTable)(t,$),t=(0,n.removeAgentsTable)(t,x),t=(0,n.removeMultiAgentV2SpawnMetadata)(t),this.removeVerifierAgentToml(e),this.removeScenarioAgentToml(e);(0,n.writeCodexConfigToml)(e,t)}writeVerifierAgentToml(e,o,r){this.writeCustomAgentToml(e,o,r,$,J,"skill","read-only")}writeScenarioAgentToml(e,o,r){this.writeCustomAgentToml(e,o,r,x,G,"scenario","read-only")}writeCustomAgentToml(e,o,r,i,t,a,g){const h=(0,m.join)(__dirname,"agents",`${i}.md`);let u;try{u=(0,s.readFileSync)(h,"utf-8")}catch(k){b.logger.debug(`failed to read agent source ${h}: ${k}`);return}const v=H("codex");for(const k of l.ALL_CYCLES){const E=(0,l.isCycleEnabled)(o,k)?re=>{const V=(0,m.join)(v,(0,C.fragmentFilename)(a,k,re));return(0,s.existsSync)(V)?(0,s.readFileSync)(V,"utf-8").trimEnd():null}:null;u=(0,C.applyPlatformSection)(u,k,E,`${i}.toml`)}const d=[];d.push(`name = ${JSON.stringify(i)}`),d.push(`description = ${JSON.stringify(t)}`),d.push(`sandbox_mode = ${JSON.stringify(g)}`),r&&d.push(`model = ${JSON.stringify(r)}`),d.push("developer_instructions = '''"),d.push(u.replace(/'''/g,"```").trimEnd()),d.push("'''");const p=S((k,T,E)=>{k&&(d.push(""),d.push(`[mcp_servers.${T}]`),d.push(...U(E)),d.push(`startup_timeout_sec = ${L}`),d.push("required = true"),d.push('default_tools_approval_mode = "approve"'))},"addCycle");p((0,l.isCycleEnabled)(o,"browser"),w,(0,l.getMcpServerEntry)(e)),p((0,l.isCycleEnabled)(o,"node"),A,(0,l.getNodeDevToolsMcpEntry)(e)),p((0,l.isCycleEnabled)(o,"backend"),_,(0,l.getBackendDevToolsMcpEntry)(e)),p((0,l.isCycleEnabled)(o,"android"),I,(0,l.getAndroidDevToolsMcpEntry)(e)),p((0,l.isCycleEnabled)(o,"terminal"),R,(0,l.getTerminalDevToolsMcpEntry)(e));const M=(0,n.codexAgentTomlPath)(e,i);(0,s.mkdirSync)((0,m.dirname)(M),{recursive:!0}),(0,s.writeFileSync)(M,d.join(`
2
2
  `)+`
3
- `)}upsertSessionMcpServers(e,o,r){let i=e;const t=S((a,g,p)=>{if(!a)return;const u=[...K(p),`startup_timeout_sec = ${V}`,'default_tools_approval_mode = "approve"'];i=(0,n.upsertMcpServer)(i,g,u)},"addCycle");return t((0,d.isCycleEnabled)(r,"browser"),E,(0,d.getMcpServerEntry)(o)),t((0,d.isCycleEnabled)(r,"node"),A,(0,d.getNodeDevToolsMcpEntry)(o)),t((0,d.isCycleEnabled)(r,"backend"),_,(0,d.getBackendDevToolsMcpEntry)(o)),t((0,d.isCycleEnabled)(r,"android"),I,(0,d.getAndroidDevToolsMcpEntry)(o)),i}removeVerifierAgentToml(e){const o=(0,n.codexAgentTomlPath)(e,$);if((0,s.existsSync)(o))try{(0,s.unlinkSync)(o)}catch(r){b.logger.debug(`failed to remove verifier agent toml: ${r}`)}}removeScenarioAgentToml(e){const o=(0,n.codexAgentTomlPath)(e,x);if((0,s.existsSync)(o))try{(0,s.unlinkSync)(o)}catch(r){b.logger.debug(`failed to remove scenario agent toml: ${r}`)}}removeIronBeeMcpServers(e){let o=(0,n.readCodexConfigToml)(e);o&&(o=(0,n.removeMcpServer)(o,E),o=(0,n.removeMcpServer)(o,A),o=(0,n.removeMcpServer)(o,_),o=(0,n.removeMcpServer)(o,I),o=(0,n.removeAgentsTable)(o,$),o=(0,n.removeAgentsTable)(o,x),o=(0,n.removeMultiAgentV2SpawnMetadata)(o),(0,n.writeCodexConfigToml)(e,o))}migrateAwayFromUserLevel(){const e=(0,n.userCodexHooksJsonPath)();this.removeIronBeeHooks(e),this.maybeDeleteEmptyHooks(e);const o=(0,n.userCodexConfigTomlPath)();if((0,s.existsSync)(o))try{let i=(0,s.readFileSync)(o,"utf-8");const t=i;i=(0,n.removeMcpServer)(i,E),i=(0,n.removeMcpServer)(i,A),i=(0,n.removeMcpServer)(i,_),i=(0,n.removeMcpServer)(i,I),i=(0,n.removeAgentsTable)(i,$),i=(0,n.removeMultiAgentV2SpawnMetadata)(i),i!==t&&(0,s.writeFileSync)(o,i)}catch(i){b.logger.debug(`migrate: failed to clean user-level config.toml: ${i}`)}const r=(0,n.userCodexAgentTomlPath)($);if((0,s.existsSync)(r))try{(0,s.unlinkSync)(r)}catch(i){b.logger.debug(`migrate: failed to remove user-level verifier toml: ${i}`)}}writeAgentsMdBlock(e,o,r){const i=(0,m.join)(e,"AGENTS.md"),t=r==="main-agent"?"ironbee-verification.main.md":"ironbee-verification.md",a=(0,m.join)(__dirname,"rules",t);let g;try{g=(0,s.readFileSync)(a,"utf-8")}catch(c){b.logger.debug(`failed to read rule source ${a}: ${c}`);return}const p=P("codex");for(const c of d.ALL_CYCLES){const R=(0,d.isCycleEnabled)(o,c)?k=>{const T=(0,m.join)(p,(0,C.fragmentFilename)("rule",c,k));if(!(0,s.existsSync)(T)){const w=k.length>0?`${c}:${k}`:c;return b.logger.debug(`AGENTS.md platform-section ${w}: missing fragment ${T}, using placeholder`),null}return(0,s.readFileSync)(T,"utf-8").trimEnd()}:null;g=(0,C.applyPlatformSection)(g,c,R,"AGENTS.md")}const u=(0,s.existsSync)(i)?(0,s.readFileSync)(i,"utf-8"):"",v=(0,n.upsertAgentsMdBlock)(u,g);(0,s.writeFileSync)(i,v)}writeSkills(e,o,r,i){const t=(0,m.join)(e,".agents","skills"),a=i==="main-agent";if(o){const u=(0,m.join)(t,"ironbee-verification");(0,s.mkdirSync)(u,{recursive:!0});const v=(0,m.join)(__dirname,"skills",a?"ironbee-verification.main.md":"ironbee-verification.md");try{let c=(0,s.readFileSync)(v,"utf-8");a&&(c=this.spliceCycleFragments(c,"skill",r,"ironbee-verification/SKILL.md")),(0,s.writeFileSync)((0,m.join)(u,"SKILL.md"),c)}catch(c){b.logger.debug(`failed to copy skill ${v}: ${c}`)}}const g=(0,m.join)(t,"ironbee-verify");(0,s.mkdirSync)(g,{recursive:!0});const p=(0,m.join)(__dirname,"commands","ironbee-verify",a?"SKILL.main.md":"SKILL.md");try{let u=(0,s.readFileSync)(p,"utf-8");a&&(u=this.spliceCycleFragments(u,"command-verify",r,"ironbee-verify/SKILL.md")),(0,s.writeFileSync)((0,m.join)(g,"SKILL.md"),u)}catch(u){b.logger.debug(`failed to copy verify command ${p}: ${u}`)}for(const u of J){const v=(0,m.join)(t,u);(0,s.mkdirSync)(v,{recursive:!0});const c=(0,m.join)(__dirname,"commands",u,a?"SKILL.main.md":"SKILL.md");try{let h=(0,s.readFileSync)(c,"utf-8");a&&(h=this.spliceCycleFragments(h,"scenario",r,`${u}/SKILL.md`)),(0,s.writeFileSync)((0,m.join)(v,"SKILL.md"),h)}catch(h){b.logger.debug(`failed to copy scenario command ${c}: ${h}`)}}}spliceCycleFragments(e,o,r,i){const t=P("codex");let a=e;for(const g of d.ALL_CYCLES){const u=(0,d.isCycleEnabled)(r,g)?v=>{const c=(0,m.join)(t,(0,C.fragmentFilename)(o,g,v));return(0,s.existsSync)(c)?(0,s.readFileSync)(c,"utf-8").trimEnd():null}:null;a=(0,C.applyPlatformSection)(a,g,u,i)}return a}removeDir(e){if((0,s.existsSync)(e))try{(0,s.rmSync)(e,{recursive:!0,force:!0})}catch(o){b.logger.debug(`failed to remove ${e}: ${o}`)}}}function K(f){return(0,n.tomlBodyFromRecord)(f)}S(K,"mcpEntryToTomlBody");0&&(module.exports={CodexClient});
3
+ `)}upsertSessionMcpServers(e,o,r){let i=e;const t=S((a,g,h)=>{if(!a)return;const u=[...U(h),`startup_timeout_sec = ${L}`,'default_tools_approval_mode = "approve"'];i=(0,n.upsertMcpServer)(i,g,u)},"addCycle");return t((0,l.isCycleEnabled)(r,"browser"),w,(0,l.getMcpServerEntry)(o)),t((0,l.isCycleEnabled)(r,"node"),A,(0,l.getNodeDevToolsMcpEntry)(o)),t((0,l.isCycleEnabled)(r,"backend"),_,(0,l.getBackendDevToolsMcpEntry)(o)),t((0,l.isCycleEnabled)(r,"android"),I,(0,l.getAndroidDevToolsMcpEntry)(o)),t((0,l.isCycleEnabled)(r,"terminal"),R,(0,l.getTerminalDevToolsMcpEntry)(o)),i}removeVerifierAgentToml(e){const o=(0,n.codexAgentTomlPath)(e,$);if((0,s.existsSync)(o))try{(0,s.unlinkSync)(o)}catch(r){b.logger.debug(`failed to remove verifier agent toml: ${r}`)}}removeScenarioAgentToml(e){const o=(0,n.codexAgentTomlPath)(e,x);if((0,s.existsSync)(o))try{(0,s.unlinkSync)(o)}catch(r){b.logger.debug(`failed to remove scenario agent toml: ${r}`)}}removeIronBeeMcpServers(e){let o=(0,n.readCodexConfigToml)(e);o&&(o=(0,n.removeMcpServer)(o,w),o=(0,n.removeMcpServer)(o,A),o=(0,n.removeMcpServer)(o,_),o=(0,n.removeMcpServer)(o,I),o=(0,n.removeMcpServer)(o,R),o=(0,n.removeAgentsTable)(o,$),o=(0,n.removeAgentsTable)(o,x),o=(0,n.removeMultiAgentV2SpawnMetadata)(o),(0,n.writeCodexConfigToml)(e,o))}migrateAwayFromUserLevel(){const e=(0,n.userCodexHooksJsonPath)();this.removeIronBeeHooks(e),this.maybeDeleteEmptyHooks(e);const o=(0,n.userCodexConfigTomlPath)();if((0,s.existsSync)(o))try{let i=(0,s.readFileSync)(o,"utf-8");const t=i;i=(0,n.removeMcpServer)(i,w),i=(0,n.removeMcpServer)(i,A),i=(0,n.removeMcpServer)(i,_),i=(0,n.removeMcpServer)(i,I),i=(0,n.removeMcpServer)(i,R),i=(0,n.removeAgentsTable)(i,$),i=(0,n.removeMultiAgentV2SpawnMetadata)(i),i!==t&&(0,s.writeFileSync)(o,i)}catch(i){b.logger.debug(`migrate: failed to clean user-level config.toml: ${i}`)}const r=(0,n.userCodexAgentTomlPath)($);if((0,s.existsSync)(r))try{(0,s.unlinkSync)(r)}catch(i){b.logger.debug(`migrate: failed to remove user-level verifier toml: ${i}`)}}writeAgentsMdBlock(e,o,r){const i=(0,m.join)(e,"AGENTS.md"),t=r==="main-agent"?"ironbee-verification.main.md":"ironbee-verification.md",a=(0,m.join)(__dirname,"rules",t);let g;try{g=(0,s.readFileSync)(a,"utf-8")}catch(d){b.logger.debug(`failed to read rule source ${a}: ${d}`);return}const h=H("codex");for(const d of l.ALL_CYCLES){const M=(0,l.isCycleEnabled)(o,d)?k=>{const T=(0,m.join)(h,(0,C.fragmentFilename)("rule",d,k));if(!(0,s.existsSync)(T)){const E=k.length>0?`${d}:${k}`:d;return b.logger.debug(`AGENTS.md platform-section ${E}: missing fragment ${T}, using placeholder`),null}return(0,s.readFileSync)(T,"utf-8").trimEnd()}:null;g=(0,C.applyPlatformSection)(g,d,M,"AGENTS.md")}const u=(0,s.existsSync)(i)?(0,s.readFileSync)(i,"utf-8"):"",v=(0,n.upsertAgentsMdBlock)(u,g);(0,s.writeFileSync)(i,v)}writeSkills(e,o,r,i){const t=(0,m.join)(e,".agents","skills"),a=i==="main-agent";if(o){const u=(0,m.join)(t,"ironbee-verification");(0,s.mkdirSync)(u,{recursive:!0});const v=(0,m.join)(__dirname,"skills",a?"ironbee-verification.main.md":"ironbee-verification.md");try{let d=(0,s.readFileSync)(v,"utf-8");a&&(d=this.spliceCycleFragments(d,"skill",r,"ironbee-verification/SKILL.md")),(0,s.writeFileSync)((0,m.join)(u,"SKILL.md"),d)}catch(d){b.logger.debug(`failed to copy skill ${v}: ${d}`)}}const g=(0,m.join)(t,"ironbee-verify");(0,s.mkdirSync)(g,{recursive:!0});const h=(0,m.join)(__dirname,"commands","ironbee-verify",a?"SKILL.main.md":"SKILL.md");try{let u=(0,s.readFileSync)(h,"utf-8");a&&(u=this.spliceCycleFragments(u,"command-verify",r,"ironbee-verify/SKILL.md")),(0,s.writeFileSync)((0,m.join)(g,"SKILL.md"),u)}catch(u){b.logger.debug(`failed to copy verify command ${h}: ${u}`)}for(const u of F){const v=(0,m.join)(t,u);(0,s.mkdirSync)(v,{recursive:!0});const d=(0,m.join)(__dirname,"commands",u,a?"SKILL.main.md":"SKILL.md");try{let p=(0,s.readFileSync)(d,"utf-8");a&&(p=this.spliceCycleFragments(p,"scenario",r,`${u}/SKILL.md`)),(0,s.writeFileSync)((0,m.join)(v,"SKILL.md"),p)}catch(p){b.logger.debug(`failed to copy scenario command ${d}: ${p}`)}}}spliceCycleFragments(e,o,r,i){const t=H("codex");let a=e;for(const g of l.ALL_CYCLES){const u=(0,l.isCycleEnabled)(r,g)?v=>{const d=(0,m.join)(t,(0,C.fragmentFilename)(o,g,v));return(0,s.existsSync)(d)?(0,s.readFileSync)(d,"utf-8").trimEnd():null}:null;a=(0,C.applyPlatformSection)(a,g,u,i)}return a}removeDir(e){if((0,s.existsSync)(e))try{(0,s.rmSync)(e,{recursive:!0,force:!0})}catch(o){b.logger.debug(`failed to remove ${e}: ${o}`)}}}function U(f){return(0,n.tomlBodyFromRecord)(f)}S(U,"mcpEntryToTomlBody");0&&(module.exports={CodexClient});
@@ -0,0 +1,61 @@
1
+ <!-- Terminal verification is ENABLED for this project. -->
2
+
3
+ ## Terminal Mode (when `terminal.verifyPatterns` matches an edited file)
4
+
5
+ > **Precondition: the change must have terminal-observable behavior.** If the change is a web-only UI with no command-line / REPL / TUI surface, this section does NOT apply — `tdt_*` tools spawn a program attached to a PTY. Just do browser verification.
6
+
7
+ If the project has terminal verification enabled (`ironbee terminal enable` once at setup) and your edits touch matching paths, the Stop hook also enforces a terminal cycle. The same `verification-start` covers both cycles; one platform-agnostic verdict covers both.
8
+
9
+ ### Mode behavior (terminal cycle)
10
+ - **default** (no arg or `default`): exercise only the commands / code paths your diff touched.
11
+ - **full**: exercise every terminal-reachable code path from files matching `terminal.verifyPatterns`.
12
+ - `visual` / `functional`: browser-only modes; terminal cycle behaves as `default` when they are passed.
13
+
14
+ ### Steps (run within step 3 of the Universal steps above)
15
+ 1. **Pick an evidence path** for the changed code:
16
+ - **Run-evidence** (proves a non-interactive command works): run the affected command one-shot with `mcp__terminal-devtools__tdt_pty_run` — it spawns the command attached to a PTY, runs it to completion, and returns the FULL output plus exit code. Confirm the output shows the expected result AND the exit code matches expectation. Best for CLIs, build targets, scripts, and test runs.
17
+ - **Interactive-evidence** (proves a REPL / shell / TUI change works):
18
+ - Spawn the program: `mcp__terminal-devtools__tdt_pty_start` (returns a `paneId`).
19
+ - Drive input: `mcp__terminal-devtools__tdt_interaction_send-keys` (tmux key syntax — `Enter`, `C-c`, `Up`, `Tab`, …) and `mcp__terminal-devtools__tdt_interaction_send-text` (literal text).
20
+ - Synchronize before reading: `mcp__terminal-devtools__tdt_sync_wait-for` (block until the expected output appears — prefer over delays).
21
+ - Capture output: `mcp__terminal-devtools__tdt_content_capture` — `mode: stream` for line-oriented programs (REPLs, shells; incremental `since` cursor reads only new lines), `mode: screen` for full-screen TUIs. Confirm it shows the expected result.
22
+ - Stop the pane: `mcp__terminal-devtools__tdt_pty_stop`.
23
+ - Auxiliary (NOT gate evidence): `mcp__terminal-devtools__tdt_sync_wait-for-idle`, `mcp__terminal-devtools__tdt_content_get-cursor`, `mcp__terminal-devtools__tdt_pty_resize`, `mcp__terminal-devtools__tdt_pty_signal`, `mcp__terminal-devtools__tdt_pty_list`.
24
+ 2. **Submit verdict** — platform-agnostic, just status + checks (+ issues/fixes).
25
+
26
+ ### Verdict (platform-agnostic)
27
+ ```json
28
+ {
29
+ "session_id": "...",
30
+ "status": "pass",
31
+ "checks": ["`mycli build` exits 0 with the new summary line", "REPL `:help` lists the new command"]
32
+ }
33
+ ```
34
+
35
+ For a multi-cycle pass, both browser and terminal pass criteria must hold.
36
+
37
+ ---
38
+
39
+ ## Default Mode (terminal cycle)
40
+
41
+ Focus on the commands or code paths your diff touched — not the entire program.
42
+
43
+ ### 1. Study the changes
44
+ 1. Run `git diff --name-only` and `git diff --name-only HEAD~1`
45
+ 2. **Ignore `.ironbee/`, `.claude/`, `.cursor/`** — tool config, not application code
46
+ 3. **Read the full diff** for every terminal file in scope — note new commands, changed flags, new output lines, changed exit codes, new REPL/TUI behavior
47
+ 4. Before spawning, identify: which command / subcommand / REPL command / TUI screen is affected? What input exercises it? What output / exit code proves it works?
48
+
49
+ ### 2. Verify against the running program
50
+ - **Run-evidence**: run the affected command via `tdt_pty_run`; the output must show the expected result and the exit code must match expectation
51
+ - **Interactive-evidence**: spawn the program, drive the affected input flow (send-keys / send-text), wait for the expected output (`tdt_sync_wait-for`), and capture it (`tdt_content_capture`) — the capture must show the expected state after your change
52
+
53
+ ---
54
+
55
+ ## Full Mode (`$ironbee-verify full`, terminal cycle)
56
+
57
+ Verify every terminal-reachable code path from files matching `terminal.verifyPatterns`, not just the changed files. Do NOT run `git diff` or scope to recent changes.
58
+
59
+ - Exercise every command / subcommand / REPL command / TUI screen in scope
60
+ - Drive at least one happy-path flow AND one error-path flow per command (confirm both the success output/exit `0` and the expected failure output/non-zero exit)
61
+ - Capture output (run-evidence or interactive-evidence) for each path; no unexpected crashes or stack traces
@@ -0,0 +1,31 @@
1
+ <!-- Terminal verification is ENABLED for this project. The Stop hook
2
+ enforces a terminal cycle whenever an edited file matches
3
+ `terminal.verifyPatterns`. -->
4
+
5
+ ## Terminal cycle
6
+
7
+ Terminal file changes IF the file matches `terminal.verifyPatterns` ALSO require verification through the **terminal-devtools** MCP server (prefix `tdt_`). Terminal-cycle verification means spawning the affected program attached to a PTY and confirming its behavior — either running the command one-shot and checking its output and exit code, OR driving an interactive session (REPL / shell / TUI) and capturing the rendered output.
8
+
9
+ Both cycles can be active simultaneously (e.g. you edit both a React component and a CLI command in the same task). One `verification-start` covers all active cycles; one platform-agnostic verdict covers them all; one retry counter applies globally.
10
+
11
+ ### ⚠️ `terminal-devtools` is ONLY for terminal-observable behavior
12
+
13
+ `terminal-devtools` drives CLIs, REPLs, shells, and TUIs through a PTY. It does NOT apply to web-only UI changes with no command-line surface. If the change produces no terminal-observable output (stdout / stderr / exit code / rendered TUI), do NOT call `tdt_*` tools — use the browser cycle for web-only projects.
14
+
15
+ **Misconfiguration recovery.** If you reach this state, the operator enabled the terminal cycle by mistake. The Stop hook will keep blocking with `incomplete_tools` for the terminal cycle. Don't attempt to spawn a PTY. Instead, stop and clearly report to the user: this change has no terminal-observable behavior; ask them to run `ironbee terminal disable` to unblock the gate.
16
+
17
+ ### Terminal-cycle additions to the main flow
18
+
19
+ These attach to the **Required steps** above — they don't replace any step. Numbering follows the main flow:
20
+
21
+ - **Within step 3 (run flow):** also run the terminal flow: pick ONE evidence path:
22
+ - **Run-evidence**: run the affected command one-shot (`tdt_pty_run`) and confirm its output AND exit code match expectation
23
+ - **Interactive-evidence**: spawn a pane (`tdt_pty_start`) → drive input (`tdt_interaction_send-keys` / `tdt_interaction_send-text`) → synchronize (`tdt_sync_wait-for`) → capture output (`tdt_content_capture`, `mode: stream` for REPLs/shells, `mode: screen` for TUIs) → stop the pane (`tdt_pty_stop`). Auxiliary only (NOT evidence): `tdt_sync_wait-for-idle`, `tdt_content_get-cursor`, `tdt_pty_resize` / `tdt_pty_signal` / `tdt_pty_list`.
24
+ - **Within step 6 (submit verdict):** submit one platform-agnostic verdict with `status` + `checks` (+ `issues`/`fixes` as needed). Terminal-cycle pass criteria: (command ran via `tdt_pty_run` with output + exit code confirmed) OR (pane spawned AND input driven AND output captured showing the expected result).
25
+
26
+ ### Additional BANNED for terminal cycle
27
+
28
+ - Calling `tdt_*` tools without first opening a verification cycle (`ironbee hook verification-start`).
29
+ - **Calling `tdt_*` tools when the change has NO terminal-observable behavior.** Use the browser cycle only for web-only projects.
30
+ - Claiming `status: pass` for a terminal cycle when no evidence path was exercised.
31
+ - Claiming `status: pass` on the run-evidence path without confirming the exit code, or on the interactive-evidence path without capturing output that shows the expected result.
@@ -0,0 +1,36 @@
1
+ ### terminal platform (enabled)
2
+ - **Use for**: CLI / REPL / shell / TUI scenarios driven through a PTY.
3
+ - **Server**: `terminal-devtools` · **scenario tools**: the `tdt_scenario-*` tools
4
+ (`tdt_scenario-add` / `-update` / `-delete` / `-list` / `-search` / `-run`).
5
+ - **Store**: project → `.ironbee/scenarios/tdt`, global → `~/.ironbee/scenarios/tdt` (the
6
+ server's `SCENARIOS_DIR`; you pass `scope`, the server resolves the path).
7
+ - Scenario **scripts** call this platform's tools via `callTool('<bare-tool>', {...})` — discover
8
+ the available `tdt_*` tool names from your connected MCP tool schemas; don't guess.
9
+
10
+ **What to test & how — capture the SAME evidence the verifier would** (a scenario runs FOR
11
+ verification, so its script must collect what the terminal cycle collects). In the script, pick an
12
+ **evidence path** for the changed code area:
13
+ 1. **Run-evidence path** — run the affected command one-shot with `tdt_pty_run` (with
14
+ `returnOutput: true`): it spawns the command attached to a PTY, runs it to completion, and returns
15
+ the FULL output plus exit code. Put the returned output AND exit code in your result; the verifier
16
+ reads them to judge whether the change behaved correctly. Best for non-interactive CLIs, build
17
+ targets, scripts, and test runs.
18
+ 2. **Interactive-evidence path** — drive a live session:
19
+ - Spawn the program: `tdt_pty_start` (returns a `paneId` you reference for the rest of the script).
20
+ - Drive input: `tdt_interaction_send-keys` (tmux key syntax — `Enter`, `C-c`, `Up`, `Tab`, …) and
21
+ `tdt_interaction_send-text` (literal text).
22
+ - **Synchronize before reading** — `tdt_sync_wait-for` to block until the expected output appears
23
+ (prefer over fixed delays).
24
+ - Capture output: `tdt_content_capture` (with `returnOutput: true`) — `mode: stream` for
25
+ line-oriented programs (REPLs, shells; incremental `since` cursor reads only new lines),
26
+ `mode: screen` for full-screen TUIs. Its captured text is what the verifier reads.
27
+ - Stop the pane: `tdt_pty_stop`.
28
+ - Optional helpers (NOT evidence): `tdt_sync_wait-for-idle` (wait until output settles),
29
+ `tdt_content_get-cursor` (read the stream cursor), `tdt_pty_resize` / `tdt_pty_signal` /
30
+ `tdt_pty_list`.
31
+
32
+ `return` the evidence — the captured output text, the exit code (run-evidence) — **plus explicit
33
+ pass/fail assertions**. That returned result is what `$ironbee-verify scenario:<name>` reads to judge
34
+ functional correctness (from the output text and exit code). **`terminal-devtools` has no
35
+ screenshots / video** — there is no visual artifact to capture; the captured text and exit code ARE
36
+ the evidence. **`terminal-devtools` is for terminal-observable behavior only.**
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **Recording (only when `recording.enable` is on in config):** the gate blocks every other browser tool until you first call `mcp__browser-devtools__bdt_content_start-recording`, and `submit-verdict` rejects with `"recording is still active"` unless you call `mcp__browser-devtools__bdt_content_stop-recording` after the steps below. **Treat start/stop as bookends around steps 1-5.** The same is enforced as step 6 of the Universal flow.
8
8
 
9
- 1. **Navigate**: `mcp__browser-devtools__bdt_navigation_go-to` — go to the affected page(s)
9
+ 1. **Navigate**: `mcp__browser-devtools__bdt_navigation_go-to` — go to the affected page(s) **AND any downstream page that renders or consumes what the change produces** — verify the change's effect where it's observed, not only the page the edited file owns
10
10
  2. **Interact**: actually exercise what changed — click buttons, fill forms, submit data, trigger workflows. Don't just look at the page.
11
11
  3. **Screenshot**: `mcp__browser-devtools__bdt_content_take-screenshot` — capture the final visual state
12
12
  4. **Accessibility**: `mcp__browser-devtools__bdt_a11y_take-aria-snapshot` — verify page structure
@@ -0,0 +1,57 @@
1
+ <!-- Terminal verification is ENABLED for this project. The Stop hook
2
+ enforces a terminal cycle whenever an edited file matches
3
+ `terminal.verifyPatterns`. -->
4
+
5
+ ## ⚠️ CRITICAL: when NOT to use terminal-devtools
6
+
7
+ **`terminal-devtools` is ONLY for programs with terminal-observable behavior** — CLIs, REPLs, shells, and full-screen TUIs that you can drive by spawning them attached to a PTY. Do **NOT** call `tdt_*` tools for changes that produce no terminal-observable behavior.
8
+
9
+ **How to tell whether the change has terminal-observable behavior:**
10
+ - A CLI entrypoint (`bin/` script, `package.json` `"bin"` field, a `main()` that parses argv, a `cobra`/`commander`/`click`/`clap` command tree)
11
+ - A REPL / interactive prompt the user types into
12
+ - A full-screen TUI (`blessed`, `ink`, `ratatui`, `bubbletea`, `ncurses`, …)
13
+ - A script / build target / shell command whose stdout, stderr, or exit code changed
14
+
15
+ If the change is a web-only UI with no command-line or terminal surface — that's the **browser cycle**, not this one. Do NOT call any `tdt_*` tools.
16
+
17
+ **Misconfiguration recovery.** If this cycle activated but the change has no terminal-observable behavior, the operator enabled the terminal cycle by mistake. The Stop hook will keep blocking with `incomplete_tools` until `maxRetries` is exhausted. Don't attempt to spawn a PTY. Stop and tell the user clearly: this change has no terminal-observable behavior; ask them to run `ironbee terminal disable` to unblock the gate.
18
+
19
+ ## Terminal flow
20
+
21
+ The terminal cycle drives CLIs / REPLs / TUIs by spawning them attached to a PTY (like tmux). Pick the evidence path that fits the changed code area:
22
+
23
+ 1. **Pick an evidence path** per changed code area:
24
+ - **Run-evidence path** (one-shot command confirms the change works):
25
+ - Run the affected command end-to-end: `mcp__terminal-devtools__tdt_pty_run` — it spawns the command attached to a PTY, runs it to completion, and returns the FULL output plus the exit code in one call. Best for non-interactive CLIs, build targets, scripts, and test runs.
26
+ - Confirm the returned output shows the expected result AND the exit code matches expectation (`0` for success; the expected non-zero code when failure is the change under test).
27
+ - **Interactive-evidence path** (driving a live session confirms an interactive change):
28
+ - Spawn the program attached to a PTY: `mcp__terminal-devtools__tdt_pty_start` — returns a `paneId` you reference for the rest of the flow. Best for REPLs, shells, and full-screen TUIs.
29
+ - Drive input to exercise the changed code: `mcp__terminal-devtools__tdt_interaction_send-keys` (tmux key syntax — `Enter`, `C-c`, `Up`, `Tab`, …) for control keys / navigation, `mcp__terminal-devtools__tdt_interaction_send-text` for literal text.
30
+ - **Synchronize before reading** — block until the expected output appears with `mcp__terminal-devtools__tdt_sync_wait-for` (prefer this over fixed delays).
31
+ - Capture the output: `mcp__terminal-devtools__tdt_content_capture` — use `mode: stream` for line-oriented programs (REPLs, shells; supports an incremental `since` cursor so you read only new lines), and `mode: screen` for full-screen TUIs (snapshots the rendered screen).
32
+ - Confirm the capture shows the expected result for the change.
33
+ - Stop the pane when done: `mcp__terminal-devtools__tdt_pty_stop`.
34
+ - **Auxiliary (NOT evidence — synchronization / housekeeping only):** `mcp__terminal-devtools__tdt_sync_wait-for-idle` (block until output settles), `mcp__terminal-devtools__tdt_content_get-cursor` (read the stream cursor position), `mcp__terminal-devtools__tdt_pty_resize` (resize the PTY), `mcp__terminal-devtools__tdt_pty_signal` (send a signal), `mcp__terminal-devtools__tdt_pty_list` (list active panes). None of these count toward the gate — they help you drive the session, they don't inspect the change.
35
+
36
+ **Batch (speed):** on the run-evidence path, a single `mcp__terminal-devtools__tdt_pty_run` already does the whole thing (spawn + run + capture + exit code), so there's nothing to batch. On the interactive-evidence path, `tdt_pty_start` runs standalone first (prerequisite); then batch a coherent sequence of `tdt_interaction_send-*` + `tdt_sync_wait-for` + `tdt_content_capture` into one `mcp__terminal-devtools__tdt_execute` — the capture reads the state after the batched input, so to assert an intermediate state add a `tdt_sync_wait-for` + capture at that point too.
37
+
38
+ ### Verdict fields
39
+ The verdict is platform-agnostic — submit only semantic judgment:
40
+
41
+ ```json
42
+ {
43
+ "session_id": "<sid>",
44
+ "status": "pass",
45
+ "checks": ["`mycli build` exits 0 and prints the new summary line", "REPL `:help` lists the new command"]
46
+ }
47
+ ```
48
+
49
+ On fail, include `issues`. On pass after a previous fail, include `fixes`.
50
+
51
+ Terminal-cycle pass criteria:
52
+ - **Run-evidence**: the affected command was run via `tdt_pty_run` AND its output AND exit code confirm the change behaved correctly.
53
+ - **Interactive-evidence**: a pane was spawned (`tdt_pty_start`) AND input was driven (`tdt_interaction_send-keys` / `tdt_interaction_send-text`) AND output was captured (`tdt_content_capture`) AND it shows the expected result.
54
+
55
+ ## Multi-cycle (browser + terminal simultaneously)
56
+
57
+ Both cycles can be active simultaneously. One `verification-start` covers all active cycles; one platform-agnostic verdict covers them all; one retry counter applies globally.
@@ -31,6 +31,9 @@ Every `apply_patch` clears the verdict, requiring re-verification. The Stop gate
31
31
  <!--IRONBEE:PLATFORM:android-->
32
32
  <!--/IRONBEE:PLATFORM:android-->
33
33
 
34
+ <!--IRONBEE:PLATFORM:terminal-->
35
+ <!--/IRONBEE:PLATFORM:terminal-->
36
+
34
37
  ## BANNED
35
38
 
36
39
  - Reporting a task complete without verifying your changes through the real tools.
@@ -98,6 +98,9 @@ the dominant cost. Drive the tools in as few turns as you can:
98
98
  <!--IRONBEE:PLATFORM:android-->
99
99
  <!--/IRONBEE:PLATFORM:android-->
100
100
 
101
+ <!--IRONBEE:PLATFORM:terminal-->
102
+ <!--/IRONBEE:PLATFORM:terminal-->
103
+
101
104
  ## Important
102
105
  - **Always submit a verdict after every verification attempt** — both pass AND fail.
103
106
  - Submit verdicts via `ironbee hook submit-verdict`, never write `verdict.json` directly.
@@ -1,4 +1,4 @@
1
- "use strict";var $=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var o=(n,t)=>$(n,"name",{value:t,configurable:!0});var J=(n,t)=>{for(var e in t)$(n,e,{get:t[e],enumerable:!0})},P=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of W(t))!M.call(n,r)&&r!==e&&$(n,r,{get:()=>t[r],enumerable:!(s=L(t,r))||s.enumerable});return n};var B=n=>P($({},"__esModule",{value:!0}),n);var hn={};J(hn,{AGENTS_MD_END_MARKER:()=>y,AGENTS_MD_START_MARKER:()=>v,canonicalizeCodexServerName:()=>C,canonicalizeCodexToolName:()=>R,classifyCodexTool:()=>D,codexAgentTomlPath:()=>ln,codexConfigTomlPath:()=>O,codexHooksJsonPath:()=>pn,decodeJwtPayload:()=>E,ensureFeaturesHooksTrue:()=>Z,ensureMultiAgentV2SpawnMetadataExposed:()=>Y,ensureSandboxWritableRoot:()=>sn,extractBashBinary:()=>A,extractCodexMcpServer:()=>S,extractCodexToolInput:()=>K,extractTomlTopLevelModel:()=>un,findTomlSection:()=>p,normalizeCodexToolName:()=>j,parseCodexHookStdin:()=>z,readCodexConfigToml:()=>gn,removeAgentsTable:()=>rn,removeFeaturesHooks:()=>q,removeMcpServer:()=>tn,removeMultiAgentV2SpawnMetadata:()=>N,removeSandboxWritableRoot:()=>on,resolveCodexUsage:()=>X,stripAgentsMdBlock:()=>cn,tomlBodyFromRecord:()=>an,upsertAgentsMdBlock:()=>dn,upsertAgentsTable:()=>en,upsertMcpServer:()=>nn,userCodexAgentTomlPath:()=>_n,userCodexConfigTomlPath:()=>mn,userCodexHooksJsonPath:()=>xn,writeCodexConfigToml:()=>fn});module.exports=B(hn);var x=require("fs"),I=require("os"),m=require("path"),b=require("../../lib/logger");function z(n){try{return JSON.parse(n)}catch(t){return b.logger.debug(`failed to parse Codex hook stdin: ${t}`),{}}}o(z,"parseCodexHookStdin");const h="mcp__",H={browser_devtools:"browser-devtools",node_devtools:"node-devtools",backend_devtools:"backend-devtools",android_devtools:"android-devtools"},F=["bdt_","ndt_","bedt_","adt_"];function C(n){return H[n]??n}o(C,"canonicalizeCodexServerName");function R(n){if(!F.some(e=>n.startsWith(e)))return n;const t=n.split("_");return t.length>=3&&t[1]==="scenario"?`${t[0]}_scenario-${t.slice(2).join("-")}`:t.length<=3?n:`${t[0]}_${t[1]}_${t.slice(2).join("-")}`}o(R,"canonicalizeCodexToolName");const V=[["bdt_","browser-devtools"],["ndt_","node-devtools"],["bedt_","backend-devtools"],["adt_","android-devtools"]];function S(n){if(!n)return null;if(n.startsWith(h)){const t=n.slice(h.length),e=t.indexOf("__");return e<0?null:C(t.slice(0,e))}for(const[t,e]of V)if(n.startsWith(t))return e;return null}o(S,"extractCodexMcpServer");function j(n){return n==="exec_command"?"Bash":n==="apply_patch"?"Edit":n==="update_plan"?"TodoWrite":n==="read_file"?"Read":n==="web_search"?"WebSearch":n==="web_fetch"?"WebFetch":n}o(j,"normalizeCodexToolName");function D(n){if(!n)return{tool_type:null,tool_name:"",mcp_server:null};if(n.startsWith(h)){const s=n.slice(h.length),r=s.indexOf("__");if(r>=0){const i=s.slice(0,r),u=C(i),l=s.slice(r+2);return{tool_type:"mcp",tool_name:R(l),mcp_server:u}}}const t=S(n);if(t!==null&&!n.startsWith(h))return{tool_type:"mcp",tool_name:R(n),mcp_server:t};const e=j(n);return n==="spawn_agent"||n==="wait_agent"||n==="close_agent"?{tool_type:"sub_agent",tool_name:e,mcp_server:null}:{tool_type:null,tool_name:e,mcp_server:null}}o(D,"classifyCodexTool");function K(n,t){if(!n||t===void 0)return;if(n==="apply_patch"){if(typeof t=="string")return{input_size:t.length};if(typeof t=="object"&&t!==null){const r=t,i=r.command??r.input;if(typeof i=="string")return{input_size:i.length}}return{input_size:void 0}}if(typeof t!="object"||t===null)return;const e=t;if(j(n)==="Bash"){const r=e.cmd??e.command,i=typeof r=="string"?A(r):void 0;return{workdir:e.workdir,binary:i}}if(n==="update_plan"){const r=e.explanation,i=e.plan;return{explanation:typeof r=="string"?r:void 0,plan_step_count:Array.isArray(i)?i.length:void 0}}if(n==="spawn_agent"){const r=e.agent_type,i=e.message,u=e.fork_context;return{agent_type:typeof r=="string"?r:void 0,message_size:typeof i=="string"?i.length:void 0,fork_context:typeof u=="boolean"?u:void 0}}if(n==="wait_agent"){const r=e.targets,i=e.timeout_ms;return{target_count:Array.isArray(r)?r.length:void 0,timeout_ms:typeof i=="number"?i:void 0}}if(n==="close_agent"){const r=e.target;return{target:typeof r=="string"?r:void 0}}if(n==="view_image"){const r=e.path,i=e.detail;return{path:typeof r=="string"?r:void 0,detail:typeof i=="string"?i:void 0}}if(n==="write_stdin"){const r=e.session_id,i=e.chars,u=e.yield_time_ms,l=e.max_output_tokens;return{session_id:typeof r=="number"?r:void 0,chars_size:typeof i=="string"?i.length:void 0,yield_time_ms:typeof u=="number"?u:void 0,max_output_tokens:typeof l=="number"?l:void 0}}if(n.startsWith(h)||S(n)!==null){if("_metadata"in e){const{_metadata:r,...i}=e;return i}return e}}o(K,"extractCodexToolInput");function A(n){const t=n.trim();if(!t)return;const e=t.split(/\s+/);for(const s of e)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(s)&&s.length>0)return s.split(/[\\/]/).pop()??s}o(A,"extractBashBinary");function E(n){const t=n.split(".");if(t.length!==3)return null;try{const e=Buffer.from(t[1],"base64url").toString("utf-8"),s=JSON.parse(e);return typeof s!="object"||s===null?null:s}catch{return null}}o(E,"decodeJwtPayload");function U(n){if(typeof n=="string"){const t=E(n);return t?{email:t.email,planType:t["https://api.openai.com/auth"]?.chatgpt_plan_type}:{}}if(typeof n=="object"&&n!==null){const t=n;return{email:t.email,planType:t.chatgpt_plan_type}}return{}}o(U,"extractIdTokenFields");function X(n){const t=n??(0,m.join)((0,I.homedir)(),".codex","auth.json");if(!(0,x.existsSync)(t))return{};try{const e=JSON.parse((0,x.readFileSync)(t,"utf-8")),s=e.auth_mode==="chatgpt"||e.auth_mode==="swic"?"subscription":e.auth_mode==="api"?"api":void 0,{email:r,planType:i}=U(e.tokens?.id_token);return{usageType:s,usagePlan:i?.toLowerCase(),userEmail:r}}catch(e){return b.logger.debug(`failed to parse ${t}: ${e}`),{}}}o(X,"resolveCodexUsage");function G(n,t){return n.trim()===`[${t}]`}o(G,"tableHeaderLineExact");function Q(n){const t=n.trim();return/^\[\[?[^\]]+\]\]?$/.test(t)}o(Q,"isAnyTableHeader");function T(n){const e=n.trim().match(/^\[([^[\]]+)\]$/);return e===null?null:e[1]}o(T,"tableHeaderName");function p(n,t){let e=-1;for(let r=0;r<n.length;r+=1)if(G(n[r],t)){e=r;break}if(e<0)return null;let s=n.length;for(let r=e+1;r<n.length;r+=1)if(Q(n[r])){s=r;break}return{startIdx:e,endIdx:s}}o(p,"findTomlSection");function k(n){const t=[...n];for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return t}o(k,"trimTrailingBlanks");function w(n,t){return n.length===0?t.join(`
1
+ "use strict";var $=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var o=(n,t)=>$(n,"name",{value:t,configurable:!0});var J=(n,t)=>{for(var e in t)$(n,e,{get:t[e],enumerable:!0})},P=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of W(t))!M.call(n,r)&&r!==e&&$(n,r,{get:()=>t[r],enumerable:!(s=L(t,r))||s.enumerable});return n};var B=n=>P($({},"__esModule",{value:!0}),n);var hn={};J(hn,{AGENTS_MD_END_MARKER:()=>y,AGENTS_MD_START_MARKER:()=>v,canonicalizeCodexServerName:()=>C,canonicalizeCodexToolName:()=>R,classifyCodexTool:()=>D,codexAgentTomlPath:()=>ln,codexConfigTomlPath:()=>O,codexHooksJsonPath:()=>pn,decodeJwtPayload:()=>E,ensureFeaturesHooksTrue:()=>Z,ensureMultiAgentV2SpawnMetadataExposed:()=>Y,ensureSandboxWritableRoot:()=>sn,extractBashBinary:()=>A,extractCodexMcpServer:()=>S,extractCodexToolInput:()=>K,extractTomlTopLevelModel:()=>un,findTomlSection:()=>p,normalizeCodexToolName:()=>j,parseCodexHookStdin:()=>z,readCodexConfigToml:()=>gn,removeAgentsTable:()=>rn,removeFeaturesHooks:()=>q,removeMcpServer:()=>tn,removeMultiAgentV2SpawnMetadata:()=>N,removeSandboxWritableRoot:()=>on,resolveCodexUsage:()=>X,stripAgentsMdBlock:()=>cn,tomlBodyFromRecord:()=>an,upsertAgentsMdBlock:()=>dn,upsertAgentsTable:()=>en,upsertMcpServer:()=>nn,userCodexAgentTomlPath:()=>_n,userCodexConfigTomlPath:()=>mn,userCodexHooksJsonPath:()=>xn,writeCodexConfigToml:()=>fn});module.exports=B(hn);var x=require("fs"),I=require("os"),m=require("path"),b=require("../../lib/logger");function z(n){try{return JSON.parse(n)}catch(t){return b.logger.debug(`failed to parse Codex hook stdin: ${t}`),{}}}o(z,"parseCodexHookStdin");const h="mcp__",H={browser_devtools:"browser-devtools",node_devtools:"node-devtools",backend_devtools:"backend-devtools",android_devtools:"android-devtools",terminal_devtools:"terminal-devtools"},F=["bdt_","ndt_","bedt_","adt_","tdt_"];function C(n){return H[n]??n}o(C,"canonicalizeCodexServerName");function R(n){if(!F.some(e=>n.startsWith(e)))return n;const t=n.split("_");return t.length>=3&&t[1]==="scenario"?`${t[0]}_scenario-${t.slice(2).join("-")}`:t.length<=3?n:`${t[0]}_${t[1]}_${t.slice(2).join("-")}`}o(R,"canonicalizeCodexToolName");const V=[["bdt_","browser-devtools"],["ndt_","node-devtools"],["bedt_","backend-devtools"],["adt_","android-devtools"],["tdt_","terminal-devtools"]];function S(n){if(!n)return null;if(n.startsWith(h)){const t=n.slice(h.length),e=t.indexOf("__");return e<0?null:C(t.slice(0,e))}for(const[t,e]of V)if(n.startsWith(t))return e;return null}o(S,"extractCodexMcpServer");function j(n){return n==="exec_command"?"Bash":n==="apply_patch"?"Edit":n==="update_plan"?"TodoWrite":n==="read_file"?"Read":n==="web_search"?"WebSearch":n==="web_fetch"?"WebFetch":n}o(j,"normalizeCodexToolName");function D(n){if(!n)return{tool_type:null,tool_name:"",mcp_server:null};if(n.startsWith(h)){const s=n.slice(h.length),r=s.indexOf("__");if(r>=0){const i=s.slice(0,r),u=C(i),l=s.slice(r+2);return{tool_type:"mcp",tool_name:R(l),mcp_server:u}}}const t=S(n);if(t!==null&&!n.startsWith(h))return{tool_type:"mcp",tool_name:R(n),mcp_server:t};const e=j(n);return n==="spawn_agent"||n==="wait_agent"||n==="close_agent"?{tool_type:"sub_agent",tool_name:e,mcp_server:null}:{tool_type:null,tool_name:e,mcp_server:null}}o(D,"classifyCodexTool");function K(n,t){if(!n||t===void 0)return;if(n==="apply_patch"){if(typeof t=="string")return{input_size:t.length};if(typeof t=="object"&&t!==null){const r=t,i=r.command??r.input;if(typeof i=="string")return{input_size:i.length}}return{input_size:void 0}}if(typeof t!="object"||t===null)return;const e=t;if(j(n)==="Bash"){const r=e.cmd??e.command,i=typeof r=="string"?A(r):void 0;return{workdir:e.workdir,binary:i}}if(n==="update_plan"){const r=e.explanation,i=e.plan;return{explanation:typeof r=="string"?r:void 0,plan_step_count:Array.isArray(i)?i.length:void 0}}if(n==="spawn_agent"){const r=e.agent_type,i=e.message,u=e.fork_context;return{agent_type:typeof r=="string"?r:void 0,message_size:typeof i=="string"?i.length:void 0,fork_context:typeof u=="boolean"?u:void 0}}if(n==="wait_agent"){const r=e.targets,i=e.timeout_ms;return{target_count:Array.isArray(r)?r.length:void 0,timeout_ms:typeof i=="number"?i:void 0}}if(n==="close_agent"){const r=e.target;return{target:typeof r=="string"?r:void 0}}if(n==="view_image"){const r=e.path,i=e.detail;return{path:typeof r=="string"?r:void 0,detail:typeof i=="string"?i:void 0}}if(n==="write_stdin"){const r=e.session_id,i=e.chars,u=e.yield_time_ms,l=e.max_output_tokens;return{session_id:typeof r=="number"?r:void 0,chars_size:typeof i=="string"?i.length:void 0,yield_time_ms:typeof u=="number"?u:void 0,max_output_tokens:typeof l=="number"?l:void 0}}if(n.startsWith(h)||S(n)!==null){if("_metadata"in e){const{_metadata:r,...i}=e;return i}return e}}o(K,"extractCodexToolInput");function A(n){const t=n.trim();if(!t)return;const e=t.split(/\s+/);for(const s of e)if(!/^[A-Za-z_][A-Za-z0-9_]*=/.test(s)&&s.length>0)return s.split(/[\\/]/).pop()??s}o(A,"extractBashBinary");function E(n){const t=n.split(".");if(t.length!==3)return null;try{const e=Buffer.from(t[1],"base64url").toString("utf-8"),s=JSON.parse(e);return typeof s!="object"||s===null?null:s}catch{return null}}o(E,"decodeJwtPayload");function U(n){if(typeof n=="string"){const t=E(n);return t?{email:t.email,planType:t["https://api.openai.com/auth"]?.chatgpt_plan_type}:{}}if(typeof n=="object"&&n!==null){const t=n;return{email:t.email,planType:t.chatgpt_plan_type}}return{}}o(U,"extractIdTokenFields");function X(n){const t=n??(0,m.join)((0,I.homedir)(),".codex","auth.json");if(!(0,x.existsSync)(t))return{};try{const e=JSON.parse((0,x.readFileSync)(t,"utf-8")),s=e.auth_mode==="chatgpt"||e.auth_mode==="swic"?"subscription":e.auth_mode==="api"?"api":void 0,{email:r,planType:i}=U(e.tokens?.id_token);return{usageType:s,usagePlan:i?.toLowerCase(),userEmail:r}}catch(e){return b.logger.debug(`failed to parse ${t}: ${e}`),{}}}o(X,"resolveCodexUsage");function G(n,t){return n.trim()===`[${t}]`}o(G,"tableHeaderLineExact");function Q(n){const t=n.trim();return/^\[\[?[^\]]+\]\]?$/.test(t)}o(Q,"isAnyTableHeader");function T(n){const e=n.trim().match(/^\[([^[\]]+)\]$/);return e===null?null:e[1]}o(T,"tableHeaderName");function p(n,t){let e=-1;for(let r=0;r<n.length;r+=1)if(G(n[r],t)){e=r;break}if(e<0)return null;let s=n.length;for(let r=e+1;r<n.length;r+=1)if(Q(n[r])){s=r;break}return{startIdx:e,endIdx:s}}o(p,"findTomlSection");function k(n){const t=[...n];for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return t}o(k,"trimTrailingBlanks");function w(n,t){return n.length===0?t.join(`
2
2
  `)+`
3
3
  `:n.replace(/\n+$/,"")+`
4
4
 
@@ -98,3 +98,6 @@ The platform sections below list each enabled cycle's server, tool prefix, and s
98
98
 
99
99
  <!--IRONBEE:PLATFORM:android-->
100
100
  <!--/IRONBEE:PLATFORM:android-->
101
+
102
+ <!--IRONBEE:PLATFORM:terminal-->
103
+ <!--/IRONBEE:PLATFORM:terminal-->
@@ -32,3 +32,6 @@ The platform sections below list each enabled cycle's server, tool prefix, and s
32
32
 
33
33
  <!--IRONBEE:PLATFORM:android-->
34
34
  <!--/IRONBEE:PLATFORM:android-->
35
+
36
+ <!--IRONBEE:PLATFORM:terminal-->
37
+ <!--/IRONBEE:PLATFORM:terminal-->
@@ -52,3 +52,6 @@ running anything, use `ironbee scenario status`.)
52
52
 
53
53
  <!--IRONBEE:PLATFORM:android-->
54
54
  <!--/IRONBEE:PLATFORM:android-->
55
+
56
+ <!--IRONBEE:PLATFORM:terminal-->
57
+ <!--/IRONBEE:PLATFORM:terminal-->
@@ -61,6 +61,9 @@ Whatever the scenario directs, the gate is unchanged — you must still call eve
61
61
  <!--IRONBEE:PLATFORM:android-->
62
62
  <!--/IRONBEE:PLATFORM:android-->
63
63
 
64
+ <!--IRONBEE:PLATFORM:terminal-->
65
+ <!--/IRONBEE:PLATFORM:terminal-->
66
+
64
67
  ---
65
68
 
66
69
  ## When to FAIL
@@ -1,6 +1,6 @@
1
- "use strict";var l=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var h=(o,t)=>l(o,"name",{value:t,configurable:!0});var k=(o,t)=>{for(var i in t)l(o,i,{get:t[i],enumerable:!0})},I=(o,t,i,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!x.call(o,s)&&s!==i&&l(o,s,{get:()=>t[s],enumerable:!(e=P(t,s))||e.enumerable});return o};var E=o=>I(l({},"__esModule",{value:!0}),o);var $={};k($,{run:()=>F});module.exports=E($);var c=require("fs"),b=require("../../../hooks/core/actions"),v=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),d=require("../../../lib/config"),n=require("../../../lib/logger"),S=require("../../../lib/stdin"),p=require("../../../lib/runtime-paths");async function F(o,t){const i=t?.soft===!0;let e;try{e=JSON.parse((0,S.readStdin)())}catch(r){n.logger.debug(`failed to parse stdin: ${r}`);const u={permission:"allow"};process.stdout.write(JSON.stringify(u)),process.exit(0);return}const s=e.conversation_id??"default";(0,n.setLogFile)((0,p.sessionLogFile)(o,s));const f=(0,p.sessionDir)(o,s),g=`${f}/actions.jsonl`;if(!i&&(0,b.hasToolCallsSinceLastVerdict)(g)){const r={permission:"deny",agent_message:`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools / android-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
1
+ "use strict";var u=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var h=(o,t)=>u(o,"name",{value:t,configurable:!0});var k=(o,t)=>{for(var i in t)u(o,i,{get:t[i],enumerable:!0})},I=(o,t,i,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!x.call(o,s)&&s!==i&&u(o,s,{get:()=>t[s],enumerable:!(e=P(t,s))||e.enumerable});return o};var E=o=>I(u({},"__esModule",{value:!0}),o);var $={};k($,{run:()=>F});module.exports=E($);var c=require("fs"),b=require("../../../hooks/core/actions"),v=require("../../../hooks/core/activity"),C=require("../../../hooks/core/tool-use-stash"),d=require("../../../lib/config"),n=require("../../../lib/logger"),S=require("../../../lib/stdin"),p=require("../../../lib/runtime-paths");async function F(o,t){const i=t?.soft===!0;let e;try{e=JSON.parse((0,S.readStdin)())}catch(r){n.logger.debug(`failed to parse stdin: ${r}`);const l={permission:"allow"};process.stdout.write(JSON.stringify(l)),process.exit(0);return}const s=e.conversation_id??"default";(0,n.setLogFile)((0,p.sessionLogFile)(o,s));const f=(0,p.sessionDir)(o,s),g=`${f}/actions.jsonl`;if(!i&&(0,b.hasToolCallsSinceLastVerdict)(g)){const r={permission:"deny",agent_message:`BLOCKED: You used verification tools (browser-devtools / node-devtools / backend-devtools / android-devtools / terminal-devtools) but did not submit a verdict. You MUST submit a verdict (pass or fail) before editing code.
2
2
 
3
3
  Submit your verdict first:
4
4
  echo '{"session_id":"${s}","status":"fail","checks":["..."],"issues":["describe what failed"]}' | ironbee hook submit-verdict
5
5
 
6
- Then you can edit code to fix the issues.`};process.stdout.write(JSON.stringify(r)),process.exit(2);return}const a=e.tool_input?.file_path??e.tool_input?.path;if(a&&e.tool_use_id){const r=(0,d.loadConfig)(o),u=(0,d.getCaptureFileChangeset)(r),m=(0,c.existsSync)(a),w=e.tool_name==="Write",T=e.tool_name==="StrReplace"||e.tool_name==="Delete";if(w||T&&u){const _={file_existed:m};if(u&&m)try{_.prior_content=(0,c.readFileSync)(a,"utf-8")}catch(O){n.logger.debug(`failed to pre-read ${a} for changeset capture: ${O}`)}(0,C.stashToolUseData)(s,e.tool_use_id,_)}}await(0,v.startActivity)({sessionDir:f,actionsFile:g,source:"pre_tool_use"});const y={permission:"allow"};process.stdout.write(JSON.stringify(y)),process.exit(0)}h(F,"run");0&&(module.exports={run});
6
+ Then you can edit code to fix the issues.`};process.stdout.write(JSON.stringify(r)),process.exit(2);return}const a=e.tool_input?.file_path??e.tool_input?.path;if(a&&e.tool_use_id){const r=(0,d.loadConfig)(o),l=(0,d.getCaptureFileChangeset)(r),m=(0,c.existsSync)(a),w=e.tool_name==="Write",T=e.tool_name==="StrReplace"||e.tool_name==="Delete";if(w||T&&l){const _={file_existed:m};if(l&&m)try{_.prior_content=(0,c.readFileSync)(a,"utf-8")}catch(O){n.logger.debug(`failed to pre-read ${a} for changeset capture: ${O}`)}(0,C.stashToolUseData)(s,e.tool_use_id,_)}}await(0,v.startActivity)({sessionDir:f,actionsFile:g,source:"pre_tool_use"});const y={permission:"allow"};process.stdout.write(JSON.stringify(y)),process.exit(0)}h(F,"run");0&&(module.exports={run});
@@ -1,9 +1,9 @@
1
- "use strict";var f=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var R=(o,t)=>f(o,"name",{value:t,configurable:!0});var J=(o,t)=>{for(var c in t)f(o,c,{get:t[c],enumerable:!0})},L=(o,t,c,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!j.call(o,r)&&r!==c&&f(o,r,{get:()=>t[r],enumerable:!(i=F(t,r))||i.enumerable});return o};var D=o=>L(f({},"__esModule",{value:!0}),o);var Y={};J(Y,{run:()=>q});module.exports=D(Y);var S=require("crypto"),e=require("../../../hooks/core/session-state"),I=require("../../../hooks/core/actions"),k=require("../../../hooks/core/activity"),O=require("../../../hooks/core/verification-lifecycle"),U=require("../../../lib/config"),E=require("../../../lib/recording-tools"),M=require("../../../hooks/core/scenario-tools"),p=require("../../../lib/logger"),A=require("../../../lib/stdin"),N=require("../../../lib/runtime-paths");const h={"MCP:bdt_":"browser-devtools","MCP:ndt_":"node-devtools","MCP:bedt_":"backend-devtools","MCP:adt_":"android-devtools"},W="browser-devtools";async function q(o,t){const c=t?.soft===!0;let i;try{i=JSON.parse((0,A.readStdin)())}catch(n){p.logger.debug(`failed to parse stdin: ${n}`);const B={permission:"allow"};process.stdout.write(JSON.stringify(B)),process.exit(0);return}const r=i.conversation_id??"default",s=(0,N.sessionDir)(o,r);(0,p.setLogFile)(`${s}/session.log`);const P=`${s}/actions.jsonl`,g=(0,M.isScenarioTool)(i.tool_name),b=(0,e.getActiveVerificationId)(s);if(!b&&!c&&!g){const n={permission:"deny",agent_message:`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-devtools).
1
+ "use strict";var f=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var R=(o,t)=>f(o,"name",{value:t,configurable:!0});var J=(o,t)=>{for(var c in t)f(o,c,{get:t[c],enumerable:!0})},L=(o,t,c,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!j.call(o,r)&&r!==c&&f(o,r,{get:()=>t[r],enumerable:!(i=F(t,r))||i.enumerable});return o};var D=o=>L(f({},"__esModule",{value:!0}),o);var Y={};J(Y,{run:()=>q});module.exports=D(Y);var S=require("crypto"),e=require("../../../hooks/core/session-state"),I=require("../../../hooks/core/actions"),k=require("../../../hooks/core/activity"),M=require("../../../hooks/core/verification-lifecycle"),O=require("../../../lib/config"),U=require("../../../lib/recording-tools"),E=require("../../../hooks/core/scenario-tools"),p=require("../../../lib/logger"),A=require("../../../lib/stdin"),N=require("../../../lib/runtime-paths");const h={"MCP:bdt_":"browser-devtools","MCP:ndt_":"node-devtools","MCP:bedt_":"backend-devtools","MCP:adt_":"android-devtools","MCP:tdt_":"terminal-devtools"},W="browser-devtools";async function q(o,t){const c=t?.soft===!0;let i;try{i=JSON.parse((0,A.readStdin)())}catch(n){p.logger.debug(`failed to parse stdin: ${n}`);const B={permission:"allow"};process.stdout.write(JSON.stringify(B)),process.exit(0);return}const r=i.conversation_id??"default",s=(0,N.sessionDir)(o,r);(0,p.setLogFile)(`${s}/session.log`);const P=`${s}/actions.jsonl`,g=(0,E.isScenarioTool)(i.tool_name),b=(0,e.getActiveVerificationId)(s);if(!b&&!c&&!g){const n={permission:"deny",agent_message:`BLOCKED: You must start a verification cycle before using devtools tools (browser-devtools / node-devtools / backend-devtools / android-devtools / terminal-devtools).
2
2
 
3
3
  Start verification first:
4
4
  echo '{"session_id":"${r}"}' | ironbee hook verification-start
5
5
 
6
- Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for browser, MCP:ndt_* for node, MCP:bedt_* for backend, MCP:adt_* for android.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}const m=i.tool_name??"",v=m.startsWith("MCP:")?m.slice(4):"",u=v?(0,E.recordingToolsForBareTool)(v):null;if(!c&&!g&&u!==null&&(0,e.isRecordingRequired)(s)&&!(0,e.isRecordingActive)(s)&&v!==u.startTool){const n={permission:"deny",agent_message:`BLOCKED: Recording is required but not started.
6
+ Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for browser, MCP:ndt_* for node, MCP:bedt_* for backend, MCP:adt_* for android, MCP:tdt_* for terminal.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}const m=i.tool_name??"",v=m.startsWith("MCP:")?m.slice(4):"",u=v?(0,U.recordingToolsForBareTool)(v):null;if(!c&&!g&&u!==null&&(0,e.isRecordingRequired)(s)&&!(0,e.isRecordingActive)(s)&&v!==u.startTool){const n={permission:"deny",agent_message:`BLOCKED: Recording is required but not started.
7
7
 
8
8
  1. Start recording NOW:
9
9
  Use MCP:${u.startTool}
@@ -12,4 +12,4 @@ Then use the verification tools for the active cycle(s) \u2014 MCP:bdt_* for bro
12
12
 
13
13
  3. **Stop recording BEFORE submitting verdict:**
14
14
  Use MCP:${u.stopTool}
15
- submit-verdict will reject with "recording is still active" if you skip this.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}await(0,k.startActivity)({sessionDir:s,actionsFile:P,source:"pre_tool_use"});let d=b;c&&!d&&!g&&(d=(await(0,O.startVerification)({sessionId:r,sessionDir:s,actionsFile:P,recordingEnabled:!1})).verificationId);const $=(0,e.getActiveTraceId)(s),_=(0,e.getActiveActivityId)(s),y=(0,I.resolveProjectName)(o),C=[`prj:${y}`,`sid:${r}`];_&&C.push(`aid:${_}`),d&&C.push(`vid:${d}`);const V=`ironbee=${C.join(";")}`,l=(0,U.loadConfig)(o),T={...i.tool_input??{}},a={projectName:y,sessionId:r,activityId:_,verificationId:d,traceId:$,traceState:V,toolCallId:(0,S.randomUUID)()};i.tool_use_id&&(a.toolUseId=i.tool_use_id),a.mcpServer=(()=>{for(const n of Object.keys(h))if(m.startsWith(n))return h[n];return W})();const w=(0,e.getUserEmail)(s);w&&(a.userEmail=w),l.collector?.url&&(a.collectorUrl=l.collector.url),l.collector?.oauthToken?a.collectorOAuthToken=l.collector.oauthToken:l.collector?.apiKey&&(a.collectorApiKey=l.collector.apiKey),T._metadata=a;const x={permission:"allow",updated_input:T};process.stdout.write(JSON.stringify(x)),process.exit(0)}R(q,"run");0&&(module.exports={run});
15
+ submit-verdict will reject with "recording is still active" if you skip this.`};process.stdout.write(JSON.stringify(n)),process.exit(2);return}await(0,k.startActivity)({sessionDir:s,actionsFile:P,source:"pre_tool_use"});let d=b;c&&!d&&!g&&(d=(await(0,M.startVerification)({sessionId:r,sessionDir:s,actionsFile:P,recordingEnabled:!1})).verificationId);const $=(0,e.getActiveTraceId)(s),_=(0,e.getActiveActivityId)(s),y=(0,I.resolveProjectName)(o),C=[`prj:${y}`,`sid:${r}`];_&&C.push(`aid:${_}`),d&&C.push(`vid:${d}`);const V=`ironbee=${C.join(";")}`,a=(0,O.loadConfig)(o),T={...i.tool_input??{}},l={projectName:y,sessionId:r,activityId:_,verificationId:d,traceId:$,traceState:V,toolCallId:(0,S.randomUUID)()};i.tool_use_id&&(l.toolUseId=i.tool_use_id),l.mcpServer=(()=>{for(const n of Object.keys(h))if(m.startsWith(n))return h[n];return W})();const w=(0,e.getUserEmail)(s);w&&(l.userEmail=w),a.collector?.url&&(l.collectorUrl=a.collector.url),a.collector?.oauthToken?l.collectorOAuthToken=a.collector.oauthToken:a.collector?.apiKey&&(l.collectorApiKey=a.collector.apiKey),T._metadata=l;const x={permission:"allow",updated_input:T};process.stdout.write(JSON.stringify(x)),process.exit(0)}R(q,"run");0&&(module.exports={run});
@@ -1 +1 @@
1
- "use strict";var d=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var p=(t,o)=>d(t,"name",{value:o,configurable:!0});var C=(t,o)=>{for(var r in o)d(t,r,{get:o[r],enumerable:!0})},S=(t,o,r,i)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of v(o))!I.call(t,n)&&n!==r&&d(t,n,{get:()=>o[n],enumerable:!(i=h(o,n))||i.enumerable});return t};var P=t=>S(d({},"__esModule",{value:!0}),t);var x={};C(x,{run:()=>L});module.exports=P(x);var b=require("../../../hooks/core/actions"),T=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),E=require("../../../lib/config"),e=require("../../../lib/logger"),c=require("../../../lib/output"),w=require("../../../lib/stdin"),l=require("../../../queue"),f=require("../util"),O=require("../../../lib/runtime-paths");const D="bdt_",J="ndt_",F="bedt_";async function L(t){let o;try{o=JSON.parse((0,w.readStdin)())}catch(u){e.logger.debug(`failed to parse stdin: ${u}`),(0,c.writeAndExit)(JSON.stringify({}),0);return}const r=o.conversation_id??"default",i=(0,O.sessionDir)(t,r),n=`${i}/actions.jsonl`;(0,e.setLogFile)(`${i}/session.log`),(0,m.getActiveActivityId)(i)===void 0&&await(0,T.startActivity)({sessionDir:i,actionsFile:n,source:"pre_tool_use"});const _=o.tool_name??"unknown",k=Date.now(),A=o.tool_input&&typeof o.tool_input=="object"&&!Array.isArray(o.tool_input)?{...o.tool_input,_metadata:void 0}:o.tool_input,N=(0,m.getActiveActivityId)(i),s=(0,f.classifyTool)(_,o.tool_input);if(s.tool_type==="mcp"&&(s.tool_name.startsWith(D)||s.tool_name.startsWith(J)||s.tool_name.startsWith(F))){e.logger.debug(`track-action-monitor: skipped devtools tool ${_}`),(0,c.writeAndExit)(JSON.stringify({}),0);return}const g=typeof o.error_message=="string"&&o.error_message.length>0?o.error_message:void 0;let a;if(g){const u=[];o.failure_type&&u.push(o.failure_type),o.is_interrupt&&u.push("interrupted"),a=`${u.length>0?`${u.join(",")}: `:""}${g}`}const $={...(0,b.baseFields)(n),type:"tool_call",timestamp:k,tool_name:s.tool_name,tool_type:s.tool_type,tool_use_id:o.tool_use_id,tool_input:(0,f.extractCursorToolInput)(_,A),tool_input_size:y(o.tool_input),tool_response:a?void 0:o.tool_output,tool_response_size:y(a?void 0:o.tool_output),activity_id:N,duration:typeof o.duration=="number"?o.duration:null,mcp_server:s.mcp_server,error:a};R(t,r,$),e.logger.debug(`track-action-monitor: ${_}${a?" (failed)":""}`),(0,c.writeAndExit)(JSON.stringify({}),0)}p(L,"run");function R(t,o,r){if(!(0,E.isJobQueueEnabled)(t))return;const i={...r};delete i.tool_response;try{(0,l.submit)(t,o,l.SEND_EVENT_TYPE,i)}catch(n){if(n instanceof l.JobTooLargeError){e.logger.debug(`track-action-monitor: wire event too large for ${r.tool_name}; dropping`);return}e.logger.debug(`track-action-monitor: failed to submit ${r.tool_name}: ${n instanceof Error?n.message:n}`)}}p(R,"submitEvent");function y(t){if(t==null)return 0;try{const o=typeof t=="string"?t:JSON.stringify(t);return o===void 0?0:Buffer.byteLength(o,"utf-8")}catch{return 0}}p(y,"byteSize");0&&(module.exports={run});
1
+ "use strict";var d=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var c=(o,t)=>d(o,"name",{value:t,configurable:!0});var C=(o,t)=>{for(var r in t)d(o,r,{get:t[r],enumerable:!0})},P=(o,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $(t))!v.call(o,n)&&n!==r&&d(o,n,{get:()=>t[n],enumerable:!(i=k(t,n))||i.enumerable});return o};var R=o=>P(d({},"__esModule",{value:!0}),o);var W={};C(W,{run:()=>M});module.exports=R(W);var T=require("../../../hooks/core/actions"),b=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),E=require("../../../lib/config"),s=require("../../../lib/logger"),p=require("../../../lib/output"),O=require("../../../lib/stdin"),a=require("../../../queue"),f=require("../util"),A=require("../../../lib/runtime-paths");const D="bdt_",L="ndt_",S="bedt_",F="adt_",J="tdt_";async function M(o){let t;try{t=JSON.parse((0,O.readStdin)())}catch(u){s.logger.debug(`failed to parse stdin: ${u}`),(0,p.writeAndExit)(JSON.stringify({}),0);return}const r=t.conversation_id??"default",i=(0,A.sessionDir)(o,r),n=`${i}/actions.jsonl`;(0,s.setLogFile)(`${i}/session.log`),(0,m.getActiveActivityId)(i)===void 0&&await(0,b.startActivity)({sessionDir:i,actionsFile:n,source:"pre_tool_use"});const _=t.tool_name??"unknown",N=Date.now(),w=t.tool_input&&typeof t.tool_input=="object"&&!Array.isArray(t.tool_input)?{...t.tool_input,_metadata:void 0}:t.tool_input,I=(0,m.getActiveActivityId)(i),e=(0,f.classifyTool)(_,t.tool_input);if(e.tool_type==="mcp"&&(e.tool_name.startsWith(D)||e.tool_name.startsWith(L)||e.tool_name.startsWith(S)||e.tool_name.startsWith(F)||e.tool_name.startsWith(J))){s.logger.debug(`track-action-monitor: skipped devtools tool ${_}`),(0,p.writeAndExit)(JSON.stringify({}),0);return}const g=typeof t.error_message=="string"&&t.error_message.length>0?t.error_message:void 0;let l;if(g){const u=[];t.failure_type&&u.push(t.failure_type),t.is_interrupt&&u.push("interrupted"),l=`${u.length>0?`${u.join(",")}: `:""}${g}`}const h={...(0,T.baseFields)(n),type:"tool_call",timestamp:N,tool_name:e.tool_name,tool_type:e.tool_type,tool_use_id:t.tool_use_id,tool_input:(0,f.extractCursorToolInput)(_,w),tool_input_size:y(t.tool_input),tool_response:l?void 0:t.tool_output,tool_response_size:y(l?void 0:t.tool_output),activity_id:I,duration:typeof t.duration=="number"?t.duration:null,mcp_server:e.mcp_server,error:l};x(o,r,h),s.logger.debug(`track-action-monitor: ${_}${l?" (failed)":""}`),(0,p.writeAndExit)(JSON.stringify({}),0)}c(M,"run");function x(o,t,r){if(!(0,E.isJobQueueEnabled)(o))return;const i={...r};delete i.tool_response;try{(0,a.submit)(o,t,a.SEND_EVENT_TYPE,i)}catch(n){if(n instanceof a.JobTooLargeError){s.logger.debug(`track-action-monitor: wire event too large for ${r.tool_name}; dropping`);return}s.logger.debug(`track-action-monitor: failed to submit ${r.tool_name}: ${n instanceof Error?n.message:n}`)}}c(x,"submitEvent");function y(o){if(o==null)return 0;try{const t=typeof o=="string"?o:JSON.stringify(o);return t===void 0?0:Buffer.byteLength(t,"utf-8")}catch{return 0}}c(y,"byteSize");0&&(module.exports={run});
@@ -1 +1 @@
1
- "use strict";var y=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var g=(o,t)=>y(o,"name",{value:t,configurable:!0});var Y=(o,t)=>{for(var l in t)y(o,l,{get:t[l],enumerable:!0})},q=(o,t,l,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of j(t))!Q.call(o,r)&&r!==l&&y(o,r,{get:()=>t[r],enumerable:!(i=K(t,r))||i.enumerable});return o};var G=o=>q(y({},"__esModule",{value:!0}),o);var rt={};Y(rt,{run:()=>et});module.exports=G(rt);var p=require("../../../hooks/core/actions"),T=require("../../../hooks/core/nested-tools"),a=require("../../../hooks/core/session-state"),L=require("../../../hooks/core/verification-context"),E=require("../../../lib/config"),s=require("../../../lib/logger"),k=require("../../../lib/recording-tools"),F=require("../../../lib/output"),P=require("../../../lib/stdin"),f=require("../../../queue"),b=require("../util"),W=require("../../../lib/runtime-paths");const N="bdt_",$="ndt_",x="bedt_",D="adt_",H="browser-devtools",Z="node-devtools",tt="backend-devtools",ot="android-devtools";function nt(o){return o.startsWith(x)?tt:o.startsWith(D)?ot:o.startsWith(N)?H:o.startsWith($)?Z:null}g(nt,"resolveServerByPrefix");async function et(o){let t;try{t=JSON.parse((0,P.readStdin)())}catch(n){s.logger.debug(`failed to parse stdin: ${n}`),process.stdout.write(JSON.stringify({})),process.exit(0);return}const l=t.conversation_id??"default",i=(0,W.sessionDir)(o,l),r=`${i}/actions.jsonl`;(0,s.setLogFile)(`${i}/session.log`);const v=t.tool_name??"unknown",R=Date.now(),w=t.tool_input&&typeof t.tool_input=="object"&&!Array.isArray(t.tool_input)?{...t.tool_input,_metadata:void 0}:t.tool_input,A=(0,a.getActiveActivityId)(i),O=(0,a.getActiveVerificationId)(i),C=(0,a.getActiveTraceId)(i),e=(0,b.classifyTool)(v,t.tool_input),B=e.tool_type==="mcp"&&e.tool_name.startsWith(N),J=e.tool_type==="mcp"&&e.tool_name.startsWith($),M=e.tool_type==="mcp"&&e.tool_name.startsWith(x),z=e.tool_type==="mcp"&&e.tool_name.startsWith(D),m=B||J||M||z,c=e.tool_type==="mcp"?nt(e.tool_name)??e.mcp_server:e.mcp_server,X=m?w:(0,b.extractCursorToolInput)(v,w),I=typeof t.error_message=="string"&&t.error_message.length>0?t.error_message:void 0;let u;if(I){const n=[];t.failure_type&&n.push(t.failure_type),t.is_interrupt&&n.push("interrupted"),u=`${n.length>0?`${n.join(",")}: `:""}${I}`}const S={...(0,p.baseFields)(r),type:"tool_call",timestamp:R,tool_name:e.tool_name,tool_type:e.tool_type,tool_use_id:t.tool_use_id,tool_input:X,tool_input_size:V(t.tool_input),tool_response:u?void 0:t.tool_output,tool_response_size:V(u?void 0:t.tool_output),activity_id:A,verification_id:O,trace_id:C,duration:typeof t.duration=="number"?t.duration:null,mcp_server:c,error:u};if(m){await(0,p.appendAction)(r,S);const n=(0,k.recordingToolsForServer)(c);n!==null&&(e.tool_name===n.startTool?((0,a.setRecordingActive)(i,!0),s.logger.debug(`track-action: recording started (${n.cycle})`)):e.tool_name===n.stopTool&&((0,a.setRecordingActive)(i,!1),s.logger.debug(`track-action: recording stopped (${n.cycle})`)))}else it(o,l,S);if(s.logger.debug(`track-action: ${v}${u?" (failed)":""}`),m&&c!==null&&e.tool_name===(0,T.executeToolBareName)(c)&&!u){const n=(0,T.extractNestedToolCalls)(t.tool_input,c),_=(0,k.recordingToolsForServer)(c);for(const d of n){_!==null&&(d.name===_.startTool?((0,a.setRecordingActive)(i,!0),s.logger.debug(`track-action (nested): recording started (${_.cycle})`)):d.name===_.stopTool&&((0,a.setRecordingActive)(i,!1),s.logger.debug(`track-action (nested): recording stopped (${_.cycle})`)));const U={...(0,p.baseFields)(r),type:"tool_call",timestamp:d.startTime??R,tool_name:d.name,tool_type:"mcp",tool_input:d.args,activity_id:A,verification_id:O,trace_id:C,duration:d.duration??null,mcp_server:c,nested:!0,parent_tool_use_id:t.tool_use_id};await(0,p.appendAction)(r,U),s.logger.debug(`track-action (nested): ${d.name}`)}}const h={};if(m)try{const n=(0,L.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:l,sessionDir:i,activeVerificationId:O,config:(0,E.loadConfig)(o)});n.length>0&&(h.additional_context=n)}catch(n){s.logger.debug(`track-action: verification-context injection skipped: ${n instanceof Error?n.message:n}`)}(0,F.writeAndExit)(JSON.stringify(h),0)}g(et,"run");function it(o,t,l){if(!(0,E.isJobQueueEnabled)(o))return;const i={...l};delete i.tool_response;try{(0,f.submit)(o,t,f.SEND_EVENT_TYPE,i)}catch(r){if(r instanceof f.JobTooLargeError){s.logger.debug(`track-action: wire event too large for ${l.tool_name}; dropping`);return}s.logger.debug(`track-action: failed to submit ${l.tool_name}: ${r instanceof Error?r.message:r}`)}}g(it,"submitEvent");function V(o){if(o==null)return 0;try{const t=typeof o=="string"?o:JSON.stringify(o);return t===void 0?0:Buffer.byteLength(t,"utf-8")}catch{return 0}}g(V,"byteSize");0&&(module.exports={run});
1
+ "use strict";var T=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var g=(o,t)=>T(o,"name",{value:t,configurable:!0});var G=(o,t)=>{for(var l in t)T(o,l,{get:t[l],enumerable:!0})},H=(o,t,l,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Y(t))!q.call(o,r)&&r!==l&&T(o,r,{get:()=>t[r],enumerable:!(i=Q(t,r))||i.enumerable});return o};var Z=o=>H(T({},"__esModule",{value:!0}),o);var at={};G(at,{run:()=>st});module.exports=Z(at);var p=require("../../../hooks/core/actions"),y=require("../../../hooks/core/nested-tools"),a=require("../../../hooks/core/session-state"),F=require("../../../hooks/core/verification-context"),E=require("../../../lib/config"),s=require("../../../lib/logger"),R=require("../../../lib/recording-tools"),P=require("../../../lib/output"),W=require("../../../lib/stdin"),f=require("../../../queue"),b=require("../util"),M=require("../../../lib/runtime-paths");const N="bdt_",$="ndt_",D="bedt_",L="adt_",x="tdt_",tt="browser-devtools",ot="node-devtools",nt="backend-devtools",et="android-devtools",it="terminal-devtools";function rt(o){return o.startsWith(D)?nt:o.startsWith(L)?et:o.startsWith(x)?it:o.startsWith(N)?tt:o.startsWith($)?ot:null}g(rt,"resolveServerByPrefix");async function st(o){let t;try{t=JSON.parse((0,W.readStdin)())}catch(n){s.logger.debug(`failed to parse stdin: ${n}`),process.stdout.write(JSON.stringify({})),process.exit(0);return}const l=t.conversation_id??"default",i=(0,M.sessionDir)(o,l),r=`${i}/actions.jsonl`;(0,s.setLogFile)(`${i}/session.log`);const v=t.tool_name??"unknown",A=Date.now(),k=t.tool_input&&typeof t.tool_input=="object"&&!Array.isArray(t.tool_input)?{...t.tool_input,_metadata:void 0}:t.tool_input,w=(0,a.getActiveActivityId)(i),O=(0,a.getActiveVerificationId)(i),I=(0,a.getActiveTraceId)(i),e=(0,b.classifyTool)(v,t.tool_input),B=e.tool_type==="mcp"&&e.tool_name.startsWith(N),J=e.tool_type==="mcp"&&e.tool_name.startsWith($),X=e.tool_type==="mcp"&&e.tool_name.startsWith(D),z=e.tool_type==="mcp"&&e.tool_name.startsWith(L),U=e.tool_type==="mcp"&&e.tool_name.startsWith(x),m=B||J||X||z||U,c=e.tool_type==="mcp"?rt(e.tool_name)??e.mcp_server:e.mcp_server,K=m?k:(0,b.extractCursorToolInput)(v,k),C=typeof t.error_message=="string"&&t.error_message.length>0?t.error_message:void 0;let u;if(C){const n=[];t.failure_type&&n.push(t.failure_type),t.is_interrupt&&n.push("interrupted"),u=`${n.length>0?`${n.join(",")}: `:""}${C}`}const S={...(0,p.baseFields)(r),type:"tool_call",timestamp:A,tool_name:e.tool_name,tool_type:e.tool_type,tool_use_id:t.tool_use_id,tool_input:K,tool_input_size:V(t.tool_input),tool_response:u?void 0:t.tool_output,tool_response_size:V(u?void 0:t.tool_output),activity_id:w,verification_id:O,trace_id:I,duration:typeof t.duration=="number"?t.duration:null,mcp_server:c,error:u};if(m){await(0,p.appendAction)(r,S);const n=(0,R.recordingToolsForServer)(c);n!==null&&(e.tool_name===n.startTool?((0,a.setRecordingActive)(i,!0),s.logger.debug(`track-action: recording started (${n.cycle})`)):e.tool_name===n.stopTool&&((0,a.setRecordingActive)(i,!1),s.logger.debug(`track-action: recording stopped (${n.cycle})`)))}else lt(o,l,S);if(s.logger.debug(`track-action: ${v}${u?" (failed)":""}`),m&&c!==null&&e.tool_name===(0,y.executeToolBareName)(c)&&!u){const n=(0,y.extractNestedToolCalls)(t.tool_input,c),_=(0,R.recordingToolsForServer)(c);for(const d of n){_!==null&&(d.name===_.startTool?((0,a.setRecordingActive)(i,!0),s.logger.debug(`track-action (nested): recording started (${_.cycle})`)):d.name===_.stopTool&&((0,a.setRecordingActive)(i,!1),s.logger.debug(`track-action (nested): recording stopped (${_.cycle})`)));const j={...(0,p.baseFields)(r),type:"tool_call",timestamp:d.startTime??A,tool_name:d.name,tool_type:"mcp",tool_input:d.args,activity_id:w,verification_id:O,trace_id:I,duration:d.duration??null,mcp_server:c,nested:!0,parent_tool_use_id:t.tool_use_id};await(0,p.appendAction)(r,j),s.logger.debug(`track-action (nested): ${d.name}`)}}const h={};if(m)try{const n=(0,F.buildVerificationContextOnceForCycle)({projectDir:o,sessionId:l,sessionDir:i,activeVerificationId:O,config:(0,E.loadConfig)(o)});n.length>0&&(h.additional_context=n)}catch(n){s.logger.debug(`track-action: verification-context injection skipped: ${n instanceof Error?n.message:n}`)}(0,P.writeAndExit)(JSON.stringify(h),0)}g(st,"run");function lt(o,t,l){if(!(0,E.isJobQueueEnabled)(o))return;const i={...l};delete i.tool_response;try{(0,f.submit)(o,t,f.SEND_EVENT_TYPE,i)}catch(r){if(r instanceof f.JobTooLargeError){s.logger.debug(`track-action: wire event too large for ${l.tool_name}; dropping`);return}s.logger.debug(`track-action: failed to submit ${l.tool_name}: ${r instanceof Error?r.message:r}`)}}g(lt,"submitEvent");function V(o){if(o==null)return 0;try{const t=typeof o=="string"?o:JSON.stringify(o);return t===void 0?0:Buffer.byteLength(t,"utf-8")}catch{return 0}}g(V,"byteSize");0&&(module.exports={run});
@@ -1 +1 @@
1
- "use strict";var $=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var v=(c,o)=>$(c,"name",{value:o,configurable:!0});var X=(c,o)=>{for(var e in o)$(c,e,{get:o[e],enumerable:!0})},Y=(c,o,e,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of z(o))!Q.call(c,t)&&t!==e&&$(c,t,{get:()=>o[t],enumerable:!(s=x(o,t))||s.enumerable});return c};var Z=c=>Y($({},"__esModule",{value:!0}),c);var no={};X(no,{CursorClient:()=>io});module.exports=Z(no);var i=require("fs"),l=require("path"),p=require("../../lib/logger"),n=require("../../lib/output"),T=require("../../lib/fs-prune"),N=require("./hooks/verify-gate"),P=require("./hooks/clear-verdict"),L=require("./hooks/track-action"),B=require("./hooks/track-action-monitor"),V=require("./hooks/session-start"),J=require("./hooks/require-verdict"),D=require("./hooks/require-verification"),F=require("./hooks/activity-start"),U=require("./hooks/activity-end"),K=require("./hooks/session-end"),a=require("../../lib/config"),q=require("../../lib/platform-section"),j=require("../../lib/gitignore");const y="browser-devtools",b="node-devtools",h="backend-devtools",S="android-devtools",oo="ironbee",O=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"];function eo(c){return(0,l.join)(__dirname,"..",c,"platforms")}v(eo,"platformsDirFor");function _(c){const o=Object.keys(c);if(o.length===0)return!0;if(o.length===1&&o[0]==="mcpServers"){const e=c.mcpServers;return e===void 0||Object.keys(e).length===0}return!1}v(_,"isMcpConfigEmpty");const E="ironbee",I=[`${y}:*`,`${b}:*`,`${h}:*`,`${S}:*`];function ro(c){const o=new Set(["mcpAllowlist","terminalAllowlist"]);for(const t of Object.keys(c))if(!o.has(t))return!1;const e=c.mcpAllowlist??[],s=c.terminalAllowlist??[];return e.length===0&&s.length===0}v(ro,"isPermissionsEmpty");function so(c){const o=new Set(["version","hooks"]);for(const e of Object.keys(c))if(!o.has(e))return!1;return Object.keys(c.hooks??{}).length===0}v(so,"isCursorHooksEmpty");function d(c){return n.pc.dim(c)}v(d,"cursorColor");class io{constructor(){this.name="cursor"}static{v(this,"CursorClient")}detect(o){return(0,i.existsSync)((0,l.join)(o,".cursor"))}resolveProjectDir(){return process.env.CURSOR_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(o,e){const s=e??(0,a.loadConfig)(o),t=(0,a.getVerificationMode)(s),r=t!=="monitor";this.cleanupArtifacts(o),(0,j.ensureIronBeeGitignored)(o);const u=(0,l.join)(o,".cursor"),g=(0,l.join)(u,"rules"),f=(0,l.join)(u,"skills");(0,i.mkdirSync)(g,{recursive:!0}),(0,i.mkdirSync)(f,{recursive:!0});const m=(0,l.join)(u,"hooks.json");if(this.mergeHooksConfig(m,t),r){if(t==="enforce"){const C=(0,l.join)(f,"ironbee-verification.md"),w=(0,i.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,i.writeFileSync)(C,w);const H=(0,l.join)(g,"ironbee-verification.mdc"),W=(0,i.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.mdc"),"utf-8");(0,i.writeFileSync)(H,W),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} skill ${n.pc.dim("\u2192")} ${n.pc.dim(C)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} rule ${n.pc.dim("\u2192")} ${n.pc.dim(H)}`)}const k=(0,l.join)(f,"ironbee-verify");(0,i.mkdirSync)(k,{recursive:!0});const A=(0,l.join)(k,"SKILL.md"),G=(0,i.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify","SKILL.md"),"utf-8");(0,i.writeFileSync)(A,G);for(const C of O){const w=(0,l.join)(f,C);(0,i.mkdirSync)(w,{recursive:!0}),(0,i.writeFileSync)((0,l.join)(w,"SKILL.md"),(0,i.readFileSync)((0,l.join)(__dirname,"commands",C,"SKILL.md"),"utf-8"))}const M=(0,l.join)(u,"mcp.json");this.writeMcpConfig(M,o);const R=(0,l.join)(u,"permissions.json");this.writePermissionsConfig(R,o),(0,q.syncPlatformSectionsToConfig)(o,eo),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),t==="assist"&&console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} command ${n.pc.dim("\u2192")} ${n.pc.dim(A)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} mcp ${n.pc.dim("\u2192")} ${n.pc.dim(M)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} perms ${n.pc.dim("\u2192")} ${n.pc.dim(R)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} ${n.pc.yellow("Cursor requires manual steps:")}`),console.log(` ${n.pc.yellow("1.")} Restart Cursor to load the new hooks and MCP config`),console.log(` ${n.pc.yellow("2.")} Go to ${n.pc.bold("Settings \u2192 Tools & MCP")} and verify ${n.pc.bold("browser-devtools")} is enabled`),console.log(` ${n.pc.yellow("3.")} If the server shows as enabled but tools are unavailable, toggle it off and on`)}else console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to load the new hook config`)}uninstall(o){this.cleanupArtifacts(o),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} removed hooks, skill, rule, command, and MCP`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to apply changes`)}cleanupArtifacts(o){const e=(0,l.join)(o,".cursor"),s=(0,l.join)(e,"skills","ironbee-verification.md"),t=(0,l.join)(e,"rules","ironbee-verification.mdc"),r=(0,l.join)(e,"skills","ironbee-analyze","SKILL.md"),u=(0,l.join)(e,"skills","ironbee-verify","SKILL.md");this.removeFile(s),this.removeFile(t),this.removeFile(r),this.removeFile(u);for(const k of O)this.removeFile((0,l.join)(e,"skills",k,"SKILL.md"));this.removeFile((0,l.join)(e,"skills","ironbee-run-scenario","SKILL.md"));const g=(0,l.join)(e,"hooks.json");this.removeIronBeeHooks(g),this.maybeDeleteEmptyHooks(g);const f=(0,l.join)(e,"mcp.json");this.removeMcpServer(f);const m=(0,l.join)(e,"permissions.json");this.removePermissionsEntries(m),(0,T.pruneEmptyDirs)(e)}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));so(e)&&(0,i.unlinkSync)(o)}catch(e){p.logger.debug(`failed to inspect ${o} for emptiness: ${e}`)}}async runVerifyGate(o){await(0,N.run)(o)}async runClearVerdict(o){await(0,P.run)(o)}async runTrackAction(o){await(0,L.run)(o)}async runSessionStart(o){await(0,V.run)(o)}async runRequireVerdict(o,e){await(0,J.run)(o,e)}async runRequireVerification(o,e){await(0,D.run)(o,e)}async runActivityStart(o){await(0,F.run)(o)}async runActivityEnd(o){await(0,U.run)(o)}async runTrackActionMonitor(o){await(0,B.run)(o)}async runSessionEnd(o){await(0,K.run)(o)}async runTrackActionPre(o){}isIronBeeHook(o){return o.command.includes(oo)}mergeHooksConfig(o,e){const s=e!=="monitor",t=e==="assist"?" --soft":"";let r={version:1,hooks:{}};if((0,i.existsSync)(o))try{r=JSON.parse((0,i.readFileSync)(o,"utf-8")),r.hooks||(r.hooks={})}catch(f){p.logger.debug(`failed to parse ${o}: ${f}`),r={version:1,hooks:{}}}for(const f of Object.keys(r.hooks)){const m=r.hooks[f].filter(k=>!this.isIronBeeHook(k));m.length===0?delete r.hooks[f]:r.hooks[f]=m}r.hooks.sessionStart||(r.hooks.sessionStart=[]),r.hooks.sessionStart.push({command:"ironbee hook session-start --client cursor"}),r.hooks.beforeSubmitPrompt||(r.hooks.beforeSubmitPrompt=[]),r.hooks.beforeSubmitPrompt.push({command:"ironbee hook activity-start --client cursor"}),s&&(r.hooks.preToolUse||(r.hooks.preToolUse=[]),r.hooks.preToolUse.push({command:`ironbee hook require-verification --client cursor${t}`,matcher:"MCP:(bdt|ndt|bedt|adt)_.*",failClosed:e==="enforce"}),r.hooks.preToolUse.push({command:`ironbee hook require-verdict --client cursor${t}`,matcher:"Write|StrReplace|Delete",failClosed:e==="enforce"}),r.hooks.postToolUse||(r.hooks.postToolUse=[]),r.hooks.postToolUse.push({command:"ironbee hook clear-verdict --client cursor",matcher:"Write|StrReplace|Delete"})),r.hooks.postToolUse||(r.hooks.postToolUse=[]);const u=s?"ironbee hook track-action --client cursor":"ironbee hook track-action-monitor --client cursor";r.hooks.postToolUse.push({command:u}),r.hooks.postToolUseFailure||(r.hooks.postToolUseFailure=[]),r.hooks.postToolUseFailure.push({command:u}),r.hooks.stop||(r.hooks.stop=[]);const g=e==="enforce"?"ironbee hook verify-gate --client cursor":"ironbee hook activity-end --client cursor";r.hooks.stop.push({command:g}),r.hooks.sessionEnd||(r.hooks.sessionEnd=[]),r.hooks.sessionEnd.push({command:"ironbee hook session-end --client cursor"}),r.version=1,(0,i.writeFileSync)(o,JSON.stringify(r,null,2))}removeIronBeeHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));if(!e.hooks)return;for(const s of Object.keys(e.hooks)){const t=e.hooks[s].filter(r=>!this.isIronBeeHook(r));t.length===0?delete e.hooks[s]:e.hooks[s]=t}(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove hooks from ${o}: ${e}`)}}removeMcpServer(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));let s=!1;e.mcpServers&&e.mcpServers[y]&&(delete e.mcpServers[y],s=!0),e.mcpServers&&e.mcpServers[b]&&(delete e.mcpServers[b],s=!0),e.mcpServers&&e.mcpServers[h]&&(delete e.mcpServers[h],s=!0),e.mcpServers&&e.mcpServers[S]&&(delete e.mcpServers[S],s=!0),_(e)?(0,i.unlinkSync)(o):s&&(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove MCP server from ${o}: ${e}`)}}removeFile(o){(0,i.existsSync)(o)&&(0,i.unlinkSync)(o)}writeMcpConfig(o,e){let s={mcpServers:{}};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8")),s.mcpServers||(s.mcpServers={})}catch(r){p.logger.debug(`failed to parse ${o}: ${r}`),s={mcpServers:{}}}const t=(0,a.loadConfig)(e);if((0,a.isCycleEnabled)(t,"browser")?s.mcpServers[y]=(0,a.getMcpServerEntry)(e):delete s.mcpServers[y],(0,a.isCycleEnabled)(t,"node")?s.mcpServers[b]=(0,a.getNodeDevToolsMcpEntry)(e):delete s.mcpServers[b],(0,a.isCycleEnabled)(t,"backend")?s.mcpServers[h]=(0,a.getBackendDevToolsMcpEntry)(e):delete s.mcpServers[h],(0,a.isCycleEnabled)(t,"android")?s.mcpServers[S]=(0,a.getAndroidDevToolsMcpEntry)(e):delete s.mcpServers[S],_(s)){try{(0,i.rmSync)(o,{force:!0})}catch(r){p.logger.debug(`failed to remove empty ${o}: ${r}`)}return}(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}writePermissionsConfig(o,e){let s={};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8"))}catch(m){p.logger.debug(`failed to parse ${o}: ${m}`),s={}}const t=(0,a.loadConfig)(e),r=[];(0,a.isCycleEnabled)(t,"browser")&&r.push(`${y}:*`),(0,a.isCycleEnabled)(t,"node")&&r.push(`${b}:*`),(0,a.isCycleEnabled)(t,"backend")&&r.push(`${h}:*`),(0,a.isCycleEnabled)(t,"android")&&r.push(`${S}:*`);const u=new Set(I),g=(s.mcpAllowlist??[]).filter(m=>!u.has(m));for(const m of r)g.includes(m)||g.push(m);g.length>0?s.mcpAllowlist=g:delete s.mcpAllowlist;const f=(s.terminalAllowlist??[]).filter(m=>m!==E);f.push(E),s.terminalAllowlist=f,(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}removePermissionsEntries(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8")),s=new Set(I);Array.isArray(e.mcpAllowlist)&&(e.mcpAllowlist=e.mcpAllowlist.filter(t=>!s.has(t)),e.mcpAllowlist.length===0&&delete e.mcpAllowlist),Array.isArray(e.terminalAllowlist)&&(e.terminalAllowlist=e.terminalAllowlist.filter(t=>t!==E),e.terminalAllowlist.length===0&&delete e.terminalAllowlist),ro(e)?(0,i.unlinkSync)(o):(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove permissions from ${o}: ${e}`)}}}0&&(module.exports={CursorClient});
1
+ "use strict";var E=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var v=(a,o)=>E(a,"name",{value:o,configurable:!0});var Y=(a,o)=>{for(var e in o)E(a,e,{get:o[e],enumerable:!0})},Z=(a,o,e,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of Q(o))!X.call(a,t)&&t!==e&&E(a,t,{get:()=>o[t],enumerable:!(s=z(o,t))||s.enumerable});return a};var oo=a=>Z(E({},"__esModule",{value:!0}),a);var to={};Y(to,{CursorClient:()=>no});module.exports=oo(to);var i=require("fs"),l=require("path"),p=require("../../lib/logger"),n=require("../../lib/output"),N=require("../../lib/fs-prune"),P=require("./hooks/verify-gate"),L=require("./hooks/clear-verdict"),B=require("./hooks/track-action"),V=require("./hooks/track-action-monitor"),J=require("./hooks/session-start"),D=require("./hooks/require-verdict"),F=require("./hooks/require-verification"),U=require("./hooks/activity-start"),K=require("./hooks/activity-end"),q=require("./hooks/session-end"),c=require("../../lib/config"),j=require("../../lib/platform-section"),G=require("../../lib/gitignore");const y="browser-devtools",h="node-devtools",S="backend-devtools",b="android-devtools",C="terminal-devtools",eo="ironbee",O=["ironbee-manage-scenario","ironbee-search-scenario","ironbee-sync-scenario"];function ro(a){return(0,l.join)(__dirname,"..",a,"platforms")}v(ro,"platformsDirFor");function I(a){const o=Object.keys(a);if(o.length===0)return!0;if(o.length===1&&o[0]==="mcpServers"){const e=a.mcpServers;return e===void 0||Object.keys(e).length===0}return!1}v(I,"isMcpConfigEmpty");const A="ironbee",T=[`${y}:*`,`${h}:*`,`${S}:*`,`${b}:*`,`${C}:*`];function so(a){const o=new Set(["mcpAllowlist","terminalAllowlist"]);for(const t of Object.keys(a))if(!o.has(t))return!1;const e=a.mcpAllowlist??[],s=a.terminalAllowlist??[];return e.length===0&&s.length===0}v(so,"isPermissionsEmpty");function io(a){const o=new Set(["version","hooks"]);for(const e of Object.keys(a))if(!o.has(e))return!1;return Object.keys(a.hooks??{}).length===0}v(io,"isCursorHooksEmpty");function d(a){return n.pc.dim(a)}v(d,"cursorColor");class no{constructor(){this.name="cursor"}static{v(this,"CursorClient")}detect(o){return(0,i.existsSync)((0,l.join)(o,".cursor"))}resolveProjectDir(){return process.env.CURSOR_PROJECT_DIR??process.env.IRONBEE_PROJECT_DIR??process.cwd()}install(o,e){const s=e??(0,c.loadConfig)(o),t=(0,c.getVerificationMode)(s),r=t!=="monitor";this.cleanupArtifacts(o),(0,G.ensureIronBeeGitignored)(o);const u=(0,l.join)(o,".cursor"),g=(0,l.join)(u,"rules"),f=(0,l.join)(u,"skills");(0,i.mkdirSync)(g,{recursive:!0}),(0,i.mkdirSync)(f,{recursive:!0});const m=(0,l.join)(u,"hooks.json");if(this.mergeHooksConfig(m,t),r){if(t==="enforce"){const w=(0,l.join)(f,"ironbee-verification.md"),$=(0,i.readFileSync)((0,l.join)(__dirname,"skills","ironbee-verification.md"),"utf-8");(0,i.writeFileSync)(w,$);const H=(0,l.join)(g,"ironbee-verification.mdc"),x=(0,i.readFileSync)((0,l.join)(__dirname,"rules","ironbee-verification.mdc"),"utf-8");(0,i.writeFileSync)(H,x),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} skill ${n.pc.dim("\u2192")} ${n.pc.dim(w)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} rule ${n.pc.dim("\u2192")} ${n.pc.dim(H)}`)}const k=(0,l.join)(f,"ironbee-verify");(0,i.mkdirSync)(k,{recursive:!0});const M=(0,l.join)(k,"SKILL.md"),W=(0,i.readFileSync)((0,l.join)(__dirname,"commands","ironbee-verify","SKILL.md"),"utf-8");(0,i.writeFileSync)(M,W);for(const w of O){const $=(0,l.join)(f,w);(0,i.mkdirSync)($,{recursive:!0}),(0,i.writeFileSync)((0,l.join)($,"SKILL.md"),(0,i.readFileSync)((0,l.join)(__dirname,"commands",w,"SKILL.md"),"utf-8"))}const R=(0,l.join)(u,"mcp.json");this.writeMcpConfig(R,o);const _=(0,l.join)(u,"permissions.json");this.writePermissionsConfig(_,o),(0,j.syncPlatformSectionsToConfig)(o,ro),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),t==="assist"&&console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("assist mode")} (verification.auto: false) \u2014 manual /ironbee-verify only, no enforcement`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} command ${n.pc.dim("\u2192")} ${n.pc.dim(M)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} mcp ${n.pc.dim("\u2192")} ${n.pc.dim(R)}`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} perms ${n.pc.dim("\u2192")} ${n.pc.dim(_)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} ${n.pc.yellow("Cursor requires manual steps:")}`),console.log(` ${n.pc.yellow("1.")} Restart Cursor to load the new hooks and MCP config`),console.log(` ${n.pc.yellow("2.")} Go to ${n.pc.bold("Settings \u2192 Tools & MCP")} and verify ${n.pc.bold("browser-devtools")} is enabled`),console.log(` ${n.pc.yellow("3.")} If the server shows as enabled but tools are unavailable, toggle it off and on`)}else console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} ${n.pc.yellow("monitoring-only mode")} (verification.enable: false)`),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} hooks ${n.pc.dim("\u2192")} ${n.pc.dim(m)}`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to load the new hook config`)}uninstall(o){this.cleanupArtifacts(o),console.log(` ${n.pc.dim("\u2192")} ${d("[cursor]")} removed hooks, skill, rule, command, and MCP`),console.log(),console.log(` ${n.pc.yellow("\u26A0")} Restart Cursor to apply changes`)}cleanupArtifacts(o){const e=(0,l.join)(o,".cursor"),s=(0,l.join)(e,"skills","ironbee-verification.md"),t=(0,l.join)(e,"rules","ironbee-verification.mdc"),r=(0,l.join)(e,"skills","ironbee-analyze","SKILL.md"),u=(0,l.join)(e,"skills","ironbee-verify","SKILL.md");this.removeFile(s),this.removeFile(t),this.removeFile(r),this.removeFile(u);for(const k of O)this.removeFile((0,l.join)(e,"skills",k,"SKILL.md"));this.removeFile((0,l.join)(e,"skills","ironbee-run-scenario","SKILL.md"));const g=(0,l.join)(e,"hooks.json");this.removeIronBeeHooks(g),this.maybeDeleteEmptyHooks(g);const f=(0,l.join)(e,"mcp.json");this.removeMcpServer(f);const m=(0,l.join)(e,"permissions.json");this.removePermissionsEntries(m),(0,N.pruneEmptyDirs)(e)}maybeDeleteEmptyHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));io(e)&&(0,i.unlinkSync)(o)}catch(e){p.logger.debug(`failed to inspect ${o} for emptiness: ${e}`)}}async runVerifyGate(o){await(0,P.run)(o)}async runClearVerdict(o){await(0,L.run)(o)}async runTrackAction(o){await(0,B.run)(o)}async runSessionStart(o){await(0,J.run)(o)}async runRequireVerdict(o,e){await(0,D.run)(o,e)}async runRequireVerification(o,e){await(0,F.run)(o,e)}async runActivityStart(o){await(0,U.run)(o)}async runActivityEnd(o){await(0,K.run)(o)}async runTrackActionMonitor(o){await(0,V.run)(o)}async runSessionEnd(o){await(0,q.run)(o)}async runTrackActionPre(o){}isIronBeeHook(o){return o.command.includes(eo)}mergeHooksConfig(o,e){const s=e!=="monitor",t=e==="assist"?" --soft":"";let r={version:1,hooks:{}};if((0,i.existsSync)(o))try{r=JSON.parse((0,i.readFileSync)(o,"utf-8")),r.hooks||(r.hooks={})}catch(f){p.logger.debug(`failed to parse ${o}: ${f}`),r={version:1,hooks:{}}}for(const f of Object.keys(r.hooks)){const m=r.hooks[f].filter(k=>!this.isIronBeeHook(k));m.length===0?delete r.hooks[f]:r.hooks[f]=m}r.hooks.sessionStart||(r.hooks.sessionStart=[]),r.hooks.sessionStart.push({command:"ironbee hook session-start --client cursor"}),r.hooks.beforeSubmitPrompt||(r.hooks.beforeSubmitPrompt=[]),r.hooks.beforeSubmitPrompt.push({command:"ironbee hook activity-start --client cursor"}),s&&(r.hooks.preToolUse||(r.hooks.preToolUse=[]),r.hooks.preToolUse.push({command:`ironbee hook require-verification --client cursor${t}`,matcher:"MCP:(bdt|ndt|bedt|adt|tdt)_.*",failClosed:e==="enforce"}),r.hooks.preToolUse.push({command:`ironbee hook require-verdict --client cursor${t}`,matcher:"Write|StrReplace|Delete",failClosed:e==="enforce"}),r.hooks.postToolUse||(r.hooks.postToolUse=[]),r.hooks.postToolUse.push({command:"ironbee hook clear-verdict --client cursor",matcher:"Write|StrReplace|Delete"})),r.hooks.postToolUse||(r.hooks.postToolUse=[]);const u=s?"ironbee hook track-action --client cursor":"ironbee hook track-action-monitor --client cursor";r.hooks.postToolUse.push({command:u}),r.hooks.postToolUseFailure||(r.hooks.postToolUseFailure=[]),r.hooks.postToolUseFailure.push({command:u}),r.hooks.stop||(r.hooks.stop=[]);const g=e==="enforce"?"ironbee hook verify-gate --client cursor":"ironbee hook activity-end --client cursor";r.hooks.stop.push({command:g}),r.hooks.sessionEnd||(r.hooks.sessionEnd=[]),r.hooks.sessionEnd.push({command:"ironbee hook session-end --client cursor"}),r.version=1,(0,i.writeFileSync)(o,JSON.stringify(r,null,2))}removeIronBeeHooks(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));if(!e.hooks)return;for(const s of Object.keys(e.hooks)){const t=e.hooks[s].filter(r=>!this.isIronBeeHook(r));t.length===0?delete e.hooks[s]:e.hooks[s]=t}(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove hooks from ${o}: ${e}`)}}removeMcpServer(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8"));let s=!1;e.mcpServers&&e.mcpServers[y]&&(delete e.mcpServers[y],s=!0),e.mcpServers&&e.mcpServers[h]&&(delete e.mcpServers[h],s=!0),e.mcpServers&&e.mcpServers[S]&&(delete e.mcpServers[S],s=!0),e.mcpServers&&e.mcpServers[b]&&(delete e.mcpServers[b],s=!0),e.mcpServers&&e.mcpServers[C]&&(delete e.mcpServers[C],s=!0),I(e)?(0,i.unlinkSync)(o):s&&(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove MCP server from ${o}: ${e}`)}}removeFile(o){(0,i.existsSync)(o)&&(0,i.unlinkSync)(o)}writeMcpConfig(o,e){let s={mcpServers:{}};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8")),s.mcpServers||(s.mcpServers={})}catch(r){p.logger.debug(`failed to parse ${o}: ${r}`),s={mcpServers:{}}}const t=(0,c.loadConfig)(e);if((0,c.isCycleEnabled)(t,"browser")?s.mcpServers[y]=(0,c.getMcpServerEntry)(e):delete s.mcpServers[y],(0,c.isCycleEnabled)(t,"node")?s.mcpServers[h]=(0,c.getNodeDevToolsMcpEntry)(e):delete s.mcpServers[h],(0,c.isCycleEnabled)(t,"backend")?s.mcpServers[S]=(0,c.getBackendDevToolsMcpEntry)(e):delete s.mcpServers[S],(0,c.isCycleEnabled)(t,"android")?s.mcpServers[b]=(0,c.getAndroidDevToolsMcpEntry)(e):delete s.mcpServers[b],(0,c.isCycleEnabled)(t,"terminal")?s.mcpServers[C]=(0,c.getTerminalDevToolsMcpEntry)(e):delete s.mcpServers[C],I(s)){try{(0,i.rmSync)(o,{force:!0})}catch(r){p.logger.debug(`failed to remove empty ${o}: ${r}`)}return}(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}writePermissionsConfig(o,e){let s={};if((0,i.existsSync)(o))try{s=JSON.parse((0,i.readFileSync)(o,"utf-8"))}catch(m){p.logger.debug(`failed to parse ${o}: ${m}`),s={}}const t=(0,c.loadConfig)(e),r=[];(0,c.isCycleEnabled)(t,"browser")&&r.push(`${y}:*`),(0,c.isCycleEnabled)(t,"node")&&r.push(`${h}:*`),(0,c.isCycleEnabled)(t,"backend")&&r.push(`${S}:*`),(0,c.isCycleEnabled)(t,"android")&&r.push(`${b}:*`),(0,c.isCycleEnabled)(t,"terminal")&&r.push(`${C}:*`);const u=new Set(T),g=(s.mcpAllowlist??[]).filter(m=>!u.has(m));for(const m of r)g.includes(m)||g.push(m);g.length>0?s.mcpAllowlist=g:delete s.mcpAllowlist;const f=(s.terminalAllowlist??[]).filter(m=>m!==A);f.push(A),s.terminalAllowlist=f,(0,i.writeFileSync)(o,JSON.stringify(s,null,2))}removePermissionsEntries(o){if((0,i.existsSync)(o))try{const e=JSON.parse((0,i.readFileSync)(o,"utf-8")),s=new Set(T);Array.isArray(e.mcpAllowlist)&&(e.mcpAllowlist=e.mcpAllowlist.filter(t=>!s.has(t)),e.mcpAllowlist.length===0&&delete e.mcpAllowlist),Array.isArray(e.terminalAllowlist)&&(e.terminalAllowlist=e.terminalAllowlist.filter(t=>t!==A),e.terminalAllowlist.length===0&&delete e.terminalAllowlist),so(e)?(0,i.unlinkSync)(o):(0,i.writeFileSync)(o,JSON.stringify(e,null,2))}catch(e){p.logger.debug(`failed to remove permissions from ${o}: ${e}`)}}}0&&(module.exports={CursorClient});