@x-otto/claude-compat 0.1.0-alpha.39 → 0.1.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/package.json +9 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -119,5 +119,4 @@ declare function createClaudeHookBridgeHooks(deps: ClaudeHookBridgeDeps): HookSp
|
|
|
119
119
|
*/
|
|
120
120
|
declare function resolveClaudeMcpServers(workspaceDir: string): Record<string, McpServerConfig>;
|
|
121
121
|
//#endregion
|
|
122
|
-
export { type ClaudeExecutableSurface, type ClaudeHookBridgeDeps, type ClaudeHookCommand, type ClaudeHookGroup, type ClaudeHooksConfig, type ClaudeTrustVerdict, type ExpandOptions, type FileCommand, type HookRunResult, type HookRunner, type PreToolUseVerdict, type ProjectConfigGating, type ProjectConfigTrustContext, type WorkspaceTrustAction, type WorkspaceTrustPromptDecision, claudeTrustStorePath, createClaudeHookBridgeHooks, defaultHookRunner, detectClaudeExecutableSurface, detectProjectConfigGating, discoverFileCommands, evaluateClaudeTrust, evaluateWorkspaceTrustPrompt, expandCommandBody, interpretPreToolUse, isProjectTrusted, isStrictProjectConfigEnabled, loadClaudeHooksConfig, matchesHookMatcher, parseClaudeHooks, parseDotMcpJson, resolveClaudeMcpServers, resolveProjectConfigTrusted, trustProject, untrustProject };
|
|
123
|
-
//# sourceMappingURL=index.d.ts.map
|
|
122
|
+
export { type ClaudeExecutableSurface, type ClaudeHookBridgeDeps, type ClaudeHookCommand, type ClaudeHookGroup, type ClaudeHooksConfig, type ClaudeTrustVerdict, type ExpandOptions, type FileCommand, type HookRunResult, type HookRunner, type PreToolUseVerdict, type ProjectConfigGating, type ProjectConfigTrustContext, type WorkspaceTrustAction, type WorkspaceTrustPromptDecision, claudeTrustStorePath, createClaudeHookBridgeHooks, defaultHookRunner, detectClaudeExecutableSurface, detectProjectConfigGating, discoverFileCommands, evaluateClaudeTrust, evaluateWorkspaceTrustPrompt, expandCommandBody, interpretPreToolUse, isProjectTrusted, isStrictProjectConfigEnabled, loadClaudeHooksConfig, matchesHookMatcher, parseClaudeHooks, parseDotMcpJson, resolveClaudeMcpServers, resolveProjectConfigTrusted, trustProject, untrustProject };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t,readdirSync as n}from"node:fs";import{join as r,relative as i,resolve as a,sep as o}from"node:path";import{CLAUDE_HOME as s,findWorkspaceRoot as c,isClaudeCompatEnabled as l,isClaudeHooksCompatEnabled as u}from"@x-otto/env";import{claudeTrustStorePath as d,claudeTrustStorePath as f,detectProjectConfigGating as p,evaluateWorkspaceTrustPrompt as m,isProjectTrusted as h,isProjectTrusted as g,isStrictProjectConfigEnabled as _,resolveProjectConfigTrusted as v,trustProject as y,untrustProject as ee}from"@x-otto/setting";import{execFile as b,spawn as x}from"node:child_process";import{readFile as S}from"node:fs/promises";import{homedir as C}from"node:os";import{promisify as w}from"node:util";import{buildAllowedEnv as T,checkShellCommand as E,createLogger as D,mapClaudeAgentToolNames as O,ottoToClaudeToolName as k,parseFrontmatterList as A,parseJsonc as te,splitFrontmatter as ne}from"@x-otto/shared";import{discoverPlugins as re,evaluatePluginTrust as j}from"@x-otto/plugin";import{defineHook as M}from"@x-otto/hooks";import{globalProcessRuntime as N}from"@x-otto/runtime";import{parseDotMcpJson as P,parseDotMcpJson as F}from"@x-otto/mcp";function I(e){try{return n(e).length>0}catch{return!1}}function L(t){let n=a(t),i=e(r(n,`.mcp.json`)),o=I(r(n,`.claude`,`commands`)),s=I(r(n,`.claude`,`agents`)),c=I(r(n,`.claude`,`skills`));return{mcpJson:i,commands:o,agents:s,skills:c,any:i||o||s||c}}function R(e,t=f()){let n=L(e),r=g(e,t);return{surface:n,trusted:r,gated:l()&&n.any&&!r}}const z=D(`@x-otto/claude-compat:claude-commands`);function B(e){let t=[],a=o=>{let s;try{s=n(o,{withFileTypes:!0})}catch{return}for(let n of s){let s=r(o,n.name);if(n.isDirectory())a(s);else if(n.isFile()&&n.name.endsWith(`.md`)){let n=i(e,s).slice(0,-3);t.push([n,s])}}};return a(e),t}function V(e,n,r){let i;try{i=t(n,`utf-8`)}catch{return null}let a=ne(i),s=a?.fields,c=a?a.body:i.trim();if(!c)return null;let l=A(s?.get(`allowed-tools`)??s?.get(`allowedtools`));return{name:e.split(o).join(`:`),description:s?.get(`description`),argumentHint:s?.get(`argument-hint`)??s?.get(`argumenthint`),allowedTools:r.source===`claude`&&l?O(l):l,model:s?.get(`model`),body:c,source:r.source,scope:r.scope,pluginId:r.pluginId,filePath:n}}function H(t,n=re({cwd:a(t),homedir:C()})){let i=a(t),o=C(),s=l(),u=s&&!R(i).gated,d=n.filter(e=>e.scope!==`builtin`).filter(e=>e.manifest.contributes?.commands!==!1).filter(e=>!j(e).gated).map(e=>({dir:r(e.dir,`commands`),source:`plugin`,scope:e.scope,pluginId:e.id})),f=c(i),p=f&&a(f)!==i?{dir:r(a(f),`.otto`,`commands`),source:`otto`,scope:`repository`}:void 0,m=[...d,...s?[{dir:r(o,`.claude`,`commands`),source:`claude`,scope:`user`}]:[],{dir:r(o,`.otto`,`commands`),source:`otto`,scope:`user`},...p?[p]:[],...u?[{dir:r(i,`.claude`,`commands`),source:`claude`,scope:`project`}]:[],{dir:r(i,`.otto`,`commands`),source:`otto`,scope:`project`}],h=new Map;for(let t of m)if(e(t.dir))for(let[e,n]of B(t.dir)){let r=V(e,n,t);r&&h.set(r.name,r)}let g=[...h.values()];return g.length>0&&z.info(`Discovered %d file command(s): %s`,g.length,g.map(e=>e.name).join(`, `)),g}const U=w(b);async function W(e,t){let n=E(e);if(!n.safe)return z.warn({command:e,reason:n.reason},`file command shell blocked by safety check`),`[command blocked: ${n.reason}]`;try{let{stdout:n}=await U(`sh`,[`-c`,e],{cwd:t,env:T(),timeout:1e4,maxBuffer:1024*1024});return n.trimEnd()}catch(e){return`[command failed: ${e.message}]`}}async function G(e,t){try{return await S(a(t,e),`utf-8`)}catch{return`[file unavailable: ${e}]`}}async function K(e,t){let n=t.args,r=t.runShell??W,i=t.readFileText??G,a=e.replace(/\$ARGUMENTS\b/g,()=>n.join(` `));a=a.replace(/\$([1-9])\b/g,(e,t)=>n[Number(t)-1]??``);let o=[...a.matchAll(/(^|\s)@([^\s`]+)/g)];for(let e of o.reverse()){let n=e[2],r=await i(n,t.workspaceDir),o=`${e[1]}\n\`\`\`\n${r}\n\`\`\``;a=a.slice(0,e.index)+o+a.slice(e.index+e[0].length)}let s=[...a.matchAll(/!`([^`]+)`/g)];for(let e of s.reverse()){let n=await r(e[1],t.workspaceDir);a=a.slice(0,e.index)+n+a.slice(e.index+e[0].length)}return a}const q=D(`@x-otto/claude-compat:claude-hooks`);function J(e){let t=e?.hooks;if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t)){if(!Array.isArray(r))continue;let t=[];for(let e of r){if(!e||typeof e!=`object`)continue;let n=e,r=typeof n.matcher==`string`?n.matcher:void 0,i=Array.isArray(n.hooks)?n.hooks:[],a=[];for(let e of i)if(e&&typeof e==`object`){let t=e;typeof t.command==`string`&&a.push({type:typeof t.type==`string`?t.type:void 0,command:t.command,timeout:typeof t.timeout==`number`?t.timeout:void 0})}a.length>0&&t.push({matcher:r,hooks:a})}t.length>0&&(n[e]=t)}return n}function Y(n){if(!u()||n&&R(n).gated)return{};let i=[r(s,`settings.json`),...n?[r(a(n),`.claude`,`settings.json`),r(a(n),`.claude`,`settings.local.json`)]:[]],o={};for(let n of i)if(e(n))try{let e=J(te(t(n,`utf-8`)));for(let[t,n]of Object.entries(e))o[t]=[...o[t]??[],...n]}catch(e){q.warn({file:n,err:e},`Failed to parse .claude hooks, skipped`)}return o}function X(e,t){if(!e||e===`*`)return!0;let n=k(t),r;try{r=new RegExp(e)}catch{return e===n||e===t}return r.test(n)||r.test(t)}const Z=(e,t,{cwd:n,timeoutMs:r})=>new Promise(i=>{let a=!1,o=(e,t)=>{a||(a=!0,i({stdout:t,exitCode:e}))},s=E(e);if(!s.safe)return q.warn({command:e,reason:s.reason},`Claude hook command blocked by safety check`),o(126,``);let c=``,l;try{l=x(`sh`,[`-c`,e],{cwd:n,env:T()}),N.registerChild({command:`sh`,args:[`-c`,e],owner:{type:`agent-tool`,id:`claude-hook`},category:`tool`,lifecycle:`evictable`,cwd:n},l)}catch{return o(1,``)}let u=setTimeout(()=>{l.kill(`SIGKILL`),o(124,c)},r);l.stdout?.on(`data`,e=>c+=String(e)),l.on(`error`,()=>{clearTimeout(u),o(1,c)}),l.on(`close`,e=>{clearTimeout(u),o(e??0,c)});try{l.stdin?.write(t),l.stdin?.end()}catch{}});function Q(e,t){if(t===2)return{block:!0,reason:e.trim()||`hook exit 2`};let n=e.trim();if(!n)return{};let r;try{r=JSON.parse(n)}catch{return{}}if(r?.decision===`block`||r?.continue===!1)return{block:!0,reason:r.reason??r.stopReason};let i=r?.hookSpecificOutput;return i?.permissionDecision?{decision:i.permissionDecision,reason:i.permissionDecisionReason}:{}}const ie={PostToolUse:`tool.execute.after`,UserPromptSubmit:`chat.message.before`,PreCompact:`compaction.before`,SessionStart:`session.created`,SessionEnd:`session.deleted`,Stop:`stream.end`,SubagentStop:`task.completed`,Notification:`notification`};function $(e,t,n,r){return JSON.stringify({session_id:n,transcript_path:``,cwd:t,hook_event_name:e,...r})}function ae(e){let t=e.config??Y(e.workspaceDir);if(Object.keys(t).length===0)return[];let n=e.workspaceDir?a(e.workspaceDir):process.cwd(),r=e.runner??Z,i=(e,t)=>r(e.command,t,{cwd:n,timeoutMs:(e.timeout??10)*1e3}),o=[];if(t.PreToolUse){let e=t.PreToolUse;o.push(M({name:`claude-compat:PreToolUse`,timing:`tool.execute.before`,priority:40,handle:async(t,r)=>{for(let a of e)if(X(a.matcher,t.toolName))for(let e of a.hooks){let{stdout:a,exitCode:o}=await i(e,$(`PreToolUse`,n,t.sessionId,{tool_name:k(t.toolName),tool_input:t.args})),s=Q(a,o);if(s.block||s.decision===`deny`){r.cancelled=!0,r.cancelReason=s.reason??`blocked by .claude hook`,r.decision=`deny`,r.decisionReason=r.cancelReason;return}s.decision===`ask`&&(r.decision=`ask`,r.decisionReason=s.reason)}}}))}for(let[r,a]of Object.entries(ie)){let s=t[r];s&&o.push(M({name:`claude-compat:${r}`,timing:a,priority:40,handle:async t=>{let a=t,o=typeof a.toolName==`string`?a.toolName:void 0,c=typeof a.notification_type==`string`?a.notification_type:void 0;for(let t of s){if(r===`Notification`){if(!X(t.matcher,c??``))continue}else if(o&&!X(t.matcher,o))continue;let s=typeof a.sessionId==`string`?a.sessionId:``,l=$(r,n,e.sessionId??s,{...o?{tool_name:k(o)}:{},...r===`Notification`?{message:a.message,...a.title?{title:a.title}:{},notification_type:c}:{}});for(let e of t.hooks)try{await i(e,l)}catch(e){q.warn({event:r,err:e},`claude hook command failed`)}}}}))}return o}function oe(e){return!l()||R(e).gated?{}:F(e)}export{d as claudeTrustStorePath,ae as createClaudeHookBridgeHooks,Z as defaultHookRunner,L as detectClaudeExecutableSurface,p as detectProjectConfigGating,H as discoverFileCommands,R as evaluateClaudeTrust,m as evaluateWorkspaceTrustPrompt,K as expandCommandBody,Q as interpretPreToolUse,h as isProjectTrusted,_ as isStrictProjectConfigEnabled,Y as loadClaudeHooksConfig,X as matchesHookMatcher,J as parseClaudeHooks,P as parseDotMcpJson,oe as resolveClaudeMcpServers,v as resolveProjectConfigTrusted,y as trustProject,ee as untrustProject};
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import{existsSync as e,readFileSync as t,readdirSync as n}from"node:fs";import{join as r,relative as i,resolve as a,sep as o}from"node:path";import{CLAUDE_HOME as s,findWorkspaceRoot as c,isClaudeCompatEnabled as l,isClaudeHooksCompatEnabled as u}from"@x-otto/env";import{claudeTrustStorePath as d,claudeTrustStorePath as f,detectProjectConfigGating as p,evaluateWorkspaceTrustPrompt as m,isProjectTrusted as h,isProjectTrusted as g,isStrictProjectConfigEnabled as _,resolveProjectConfigTrusted as v,trustProject as y,untrustProject as ee}from"@x-otto/setting";import{execFile as b,spawn as x}from"node:child_process";import{readFile as S}from"node:fs/promises";import{homedir as C}from"node:os";import{promisify as w}from"node:util";import{buildAllowedEnv as T,checkShellCommand as E,createLogger as D,mapClaudeAgentToolNames as O,ottoToClaudeToolName as k,parseFrontmatterList as A,parseJsonc as te,splitFrontmatter as ne}from"@x-otto/shared";import{discoverPlugins as re,evaluatePluginTrust as j}from"@x-otto/plugin";import{defineHook as M}from"@x-otto/hooks";import{globalProcessRuntime as N}from"@x-otto/runtime";import{parseDotMcpJson as P,parseDotMcpJson as F}from"@x-otto/mcp";function I(e){try{return n(e).length>0}catch{return!1}}function L(t){let n=a(t),i=e(r(n,`.mcp.json`)),o=I(r(n,`.claude`,`commands`)),s=I(r(n,`.claude`,`agents`)),c=I(r(n,`.claude`,`skills`));return{mcpJson:i,commands:o,agents:s,skills:c,any:i||o||s||c}}function R(e,t=f()){let n=L(e),r=g(e,t);return{surface:n,trusted:r,gated:l()&&n.any&&!r}}const z=D(`@x-otto/claude-compat:claude-commands`);function B(e){let t=[],a=o=>{let s;try{s=n(o,{withFileTypes:!0})}catch{return}for(let n of s){let s=r(o,n.name);if(n.isDirectory())a(s);else if(n.isFile()&&n.name.endsWith(`.md`)){let n=i(e,s).slice(0,-3);t.push([n,s])}}};return a(e),t}function V(e,n,r){let i;try{i=t(n,`utf-8`)}catch{return null}let a=ne(i),s=a?.fields,c=a?a.body:i.trim();if(!c)return null;let l=A(s?.get(`allowed-tools`)??s?.get(`allowedtools`));return{name:e.split(o).join(`:`),description:s?.get(`description`),argumentHint:s?.get(`argument-hint`)??s?.get(`argumenthint`),allowedTools:r.source===`claude`&&l?O(l):l,model:s?.get(`model`),body:c,source:r.source,scope:r.scope,pluginId:r.pluginId,filePath:n}}function H(t,n=re({cwd:a(t),homedir:C()})){let i=a(t),o=C(),s=l(),u=s&&!R(i).gated,d=n.filter(e=>e.scope!==`builtin`).filter(e=>e.manifest.contributes?.commands!==!1).filter(e=>!j(e).gated).map(e=>({dir:r(e.dir,`commands`),source:`plugin`,scope:e.scope,pluginId:e.id})),f=c(i),p=f&&a(f)!==i?{dir:r(a(f),`.otto`,`commands`),source:`otto`,scope:`repository`}:void 0,m=[...d,...s?[{dir:r(o,`.claude`,`commands`),source:`claude`,scope:`user`}]:[],{dir:r(o,`.otto`,`commands`),source:`otto`,scope:`user`},...p?[p]:[],...u?[{dir:r(i,`.claude`,`commands`),source:`claude`,scope:`project`}]:[],{dir:r(i,`.otto`,`commands`),source:`otto`,scope:`project`}],h=new Map;for(let t of m)if(e(t.dir))for(let[e,n]of B(t.dir)){let r=V(e,n,t);r&&h.set(r.name,r)}let g=[...h.values()];return g.length>0&&z.info(`Discovered %d file command(s): %s`,g.length,g.map(e=>e.name).join(`, `)),g}const U=w(b);async function W(e,t){let n=E(e);if(!n.safe)return z.warn({command:e,reason:n.reason},`file command shell blocked by safety check`),`[command blocked: ${n.reason}]`;try{let{stdout:n}=await U(`sh`,[`-c`,e],{cwd:t,env:T(),timeout:1e4,maxBuffer:1024*1024});return n.trimEnd()}catch(e){return`[command failed: ${e.message}]`}}async function G(e,t){try{return await S(a(t,e),`utf-8`)}catch{return`[file unavailable: ${e}]`}}async function K(e,t){let n=t.args,r=t.runShell??W,i=t.readFileText??G,a=e.replace(/\$ARGUMENTS\b/g,()=>n.join(` `));a=a.replace(/\$([1-9])\b/g,(e,t)=>n[Number(t)-1]??``);let o=[...a.matchAll(/(^|\s)@([^\s`]+)/g)];for(let e of o.reverse()){let n=e[2],r=await i(n,t.workspaceDir),o=`${e[1]}\n\`\`\`\n${r}\n\`\`\``;a=a.slice(0,e.index)+o+a.slice(e.index+e[0].length)}let s=[...a.matchAll(/!`([^`]+)`/g)];for(let e of s.reverse()){let n=await r(e[1],t.workspaceDir);a=a.slice(0,e.index)+n+a.slice(e.index+e[0].length)}return a}const q=D(`@x-otto/claude-compat:claude-hooks`);function J(e){let t=e?.hooks;if(!t||typeof t!=`object`)return{};let n={};for(let[e,r]of Object.entries(t)){if(!Array.isArray(r))continue;let t=[];for(let e of r){if(!e||typeof e!=`object`)continue;let n=e,r=typeof n.matcher==`string`?n.matcher:void 0,i=Array.isArray(n.hooks)?n.hooks:[],a=[];for(let e of i)if(e&&typeof e==`object`){let t=e;typeof t.command==`string`&&a.push({type:typeof t.type==`string`?t.type:void 0,command:t.command,timeout:typeof t.timeout==`number`?t.timeout:void 0})}a.length>0&&t.push({matcher:r,hooks:a})}t.length>0&&(n[e]=t)}return n}function Y(n){if(!u()||n&&R(n).gated)return{};let i=[r(s,`settings.json`),...n?[r(a(n),`.claude`,`settings.json`),r(a(n),`.claude`,`settings.local.json`)]:[]],o={};for(let n of i)if(e(n))try{let e=J(te(t(n,`utf-8`)));for(let[t,n]of Object.entries(e))o[t]=[...o[t]??[],...n]}catch(e){q.warn({file:n,err:e},`Failed to parse .claude hooks, skipped`)}return o}function X(e,t){if(!e||e===`*`)return!0;let n=k(t),r;try{r=new RegExp(e)}catch{return e===n||e===t}return r.test(n)||r.test(t)}const Z=(e,t,{cwd:n,timeoutMs:r})=>new Promise(i=>{let a=!1,o=(e,t)=>{a||(a=!0,i({stdout:t,exitCode:e}))},s=E(e);if(!s.safe)return q.warn({command:e,reason:s.reason},`Claude hook command blocked by safety check`),o(126,``);let c=``,l;try{l=x(`sh`,[`-c`,e],{cwd:n,env:T()}),N.registerChild({command:`sh`,args:[`-c`,e],owner:{type:`agent-tool`,id:`claude-hook`},category:`tool`,lifecycle:`evictable`,cwd:n},l)}catch{return o(1,``)}let u=setTimeout(()=>{l.kill(`SIGKILL`),o(124,c)},r);l.stdout?.on(`data`,e=>c+=String(e)),l.on(`error`,()=>{clearTimeout(u),o(1,c)}),l.on(`close`,e=>{clearTimeout(u),o(e??0,c)});try{l.stdin?.write(t),l.stdin?.end()}catch{}});function Q(e,t){if(t===2)return{block:!0,reason:e.trim()||`hook exit 2`};let n=e.trim();if(!n)return{};let r;try{r=JSON.parse(n)}catch{return{}}if(r?.decision===`block`||r?.continue===!1)return{block:!0,reason:r.reason??r.stopReason};let i=r?.hookSpecificOutput;return i?.permissionDecision?{decision:i.permissionDecision,reason:i.permissionDecisionReason}:{}}const ie={PostToolUse:`tool.execute.after`,UserPromptSubmit:`chat.message.before`,PreCompact:`compaction.before`,SessionStart:`session.created`,SessionEnd:`session.deleted`,Stop:`stream.end`,SubagentStop:`task.completed`,Notification:`notification`};function $(e,t,n,r){return JSON.stringify({session_id:n,transcript_path:``,cwd:t,hook_event_name:e,...r})}function ae(e){let t=e.config??Y(e.workspaceDir);if(Object.keys(t).length===0)return[];let n=e.workspaceDir?a(e.workspaceDir):process.cwd(),r=e.runner??Z,i=(e,t)=>r(e.command,t,{cwd:n,timeoutMs:(e.timeout??10)*1e3}),o=[];if(t.PreToolUse){let e=t.PreToolUse;o.push(M({name:`claude-compat:PreToolUse`,timing:`tool.execute.before`,priority:40,handle:async(t,r)=>{for(let a of e)if(X(a.matcher,t.toolName))for(let e of a.hooks){let{stdout:a,exitCode:o}=await i(e,$(`PreToolUse`,n,t.sessionId,{tool_name:k(t.toolName),tool_input:t.args})),s=Q(a,o);if(s.block||s.decision===`deny`){r.cancelled=!0,r.cancelReason=s.reason??`blocked by .claude hook`,r.decision=`deny`,r.decisionReason=r.cancelReason;return}s.decision===`ask`&&(r.decision=`ask`,r.decisionReason=s.reason)}}}))}for(let[r,a]of Object.entries(ie)){let s=t[r];s&&o.push(M({name:`claude-compat:${r}`,timing:a,priority:40,handle:async t=>{let a=t,o=typeof a.toolName==`string`?a.toolName:void 0,c=typeof a.notification_type==`string`?a.notification_type:void 0;for(let t of s){if(r===`Notification`){if(!X(t.matcher,c??``))continue}else if(o&&!X(t.matcher,o))continue;let s=typeof a.sessionId==`string`?a.sessionId:``,l=$(r,n,e.sessionId??s,{...o?{tool_name:k(o)}:{},...r===`Notification`?{message:a.message,...a.title?{title:a.title}:{},notification_type:c}:{}});for(let e of t.hooks)try{await i(e,l)}catch(e){q.warn({event:r,err:e},`claude hook command failed`)}}}}))}return o}function oe(e){return!l()||R(e).gated?{}:F(e)}export{d as claudeTrustStorePath,ae as createClaudeHookBridgeHooks,Z as defaultHookRunner,L as detectClaudeExecutableSurface,p as detectProjectConfigGating,H as discoverFileCommands,R as evaluateClaudeTrust,m as evaluateWorkspaceTrustPrompt,K as expandCommandBody,Q as interpretPreToolUse,h as isProjectTrusted,_ as isStrictProjectConfigEnabled,Y as loadClaudeHooksConfig,X as matchesHookMatcher,J as parseClaudeHooks,P as parseDotMcpJson,oe as resolveClaudeMcpServers,v as resolveProjectConfigTrusted,y as trustProject,ee as untrustProject};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-otto/claude-compat",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.41",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@x-otto/
|
|
18
|
-
"@x-otto/
|
|
19
|
-
"@x-otto/
|
|
20
|
-
"@x-otto/
|
|
21
|
-
"@x-otto/
|
|
22
|
-
"@x-otto/
|
|
23
|
-
"@x-otto/
|
|
24
|
-
"@x-otto/shared": "0.1.0-alpha.
|
|
17
|
+
"@x-otto/env": "0.1.0-alpha.41",
|
|
18
|
+
"@x-otto/hooks": "0.1.0-alpha.41",
|
|
19
|
+
"@x-otto/interchange": "0.1.0-alpha.41",
|
|
20
|
+
"@x-otto/setting": "0.1.0-alpha.41",
|
|
21
|
+
"@x-otto/mcp": "0.1.0-alpha.41",
|
|
22
|
+
"@x-otto/plugin": "0.1.0-alpha.41",
|
|
23
|
+
"@x-otto/runtime": "0.1.0-alpha.41",
|
|
24
|
+
"@x-otto/shared": "0.1.0-alpha.41"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"typescript": "6.0.2",
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/claude-trust.ts","../src/claude-commands.ts","../src/claude-hooks.ts","../src/dot-mcp-json.ts"],"mappings":";;;;;;;;UA2BiB,uBAAA;;EAEf,OAAA;;EAEA,QAAA;EAJe;EAMf,MAAA;;EAEA,MAAA;EANA;EAQA,GAAA;AAAA;;;;;AAgBF;iBAAgB,6BAAA,CAA8B,YAAA,WAAuB,uBAAA;;UAUpD,kBAAA;EACf,OAAA,EAAS,uBAAA;EACT,OAAA;EAFiC;EAIjC,KAAA;AAAA;;;;;iBAOc,mBAAA,CACd,YAAA,UACA,SAAA,YACC,kBAAA;;;;;;;AAlDH;;;;;iBC+EgB,oBAAA,CACd,YAAA,UACA,OAAA,GAAS,gBAAA,KACR,WAAA;AAAA,UAiGc,aAAA;EACf,IAAA;EACA,YAAA;ED3KG;EC6KH,QAAA,IAAY,GAAA,UAAa,GAAA,aAAgB,OAAA;ED7J3B;EC+Jd,YAAA,IAAgB,OAAA,UAAiB,GAAA,aAAgB,OAAA;AAAA;;;ADrJnD;;iBCoKsB,iBAAA,CAAkB,IAAA,UAAc,IAAA,EAAM,aAAA,GAAgB,OAAA;;;UC3M3D,iBAAA;EACf,IAAA;EACA,OAAA;EACA,OAAA;AAAA;AAAA,UAEe,eAAA;EACf,OAAA;EACA,KAAA,EAAO,iBAAA;AAAA;;KAGG,iBAAA,GAAoB,MAAA,SAAe,eAAA;;iBAG/B,gBAAA,CAAiB,GAAA,YAAe,iBAAA;;iBAiChC,qBAAA,CAAsB,YAAA,YAAwB,iBAAA;;iBA4B9C,kBAAA,CAAmB,OAAA,sBAA6B,YAAA;AAAA,UAY/C,aAAA;EACf,MAAA;EACA,QAAA;AAAA;AAAA,KAEU,UAAA,IACV,OAAA,UACA,KAAA,UACA,IAAA;EAAQ,GAAA;EAAa,SAAA;AAAA,MAClB,OAAA,CAAQ,aAAA;;;;cAKA,iBAAA,EAAmB,UAAA;AAAA,UA6Cf,iBAAA;EACf,QAAA;EACA,KAAA;EACA,MAAA;AAAA;;iBAIc,mBAAA,CAAoB,MAAA,UAAgB,QAAA,WAAmB,iBAAA;AAAA,UAsCtD,oBAAA;EACf,YAAA;EACA,SAAA;EACA,MAAA,GAAS,UAAA;EF3IU;EE6InB,MAAA,GAAS,iBAAA;AAAA;;ADhHX;;;iBCsIgB,2BAAA,CAA4B,IAAA,EAAM,oBAAA,GAAuB,QAAA;;;;;;;iBC7NzD,uBAAA,CAAwB,YAAA,WAAuB,MAAA,SAAe,eAAA"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["claudeTrustStorePath","isProjectTrusted","logger","readFileAsync","parseDotMcpJson"],"sources":["../src/claude-trust.ts","../src/claude-commands.ts","../src/claude-hooks.ts","../src/dot-mcp-json.ts"],"sourcesContent":["/**\n * claude-trust.ts — RFC-421 D3:Claude `.claude/` 目录信任面(信任判定已迁入 @x-otto/setting).\n *\n * 信任判定(isProjectTrusted/trustProject/untrustProject/claudeTrustStorePath)与严格项目配置门\n * 决策已迁至 `@x-otto/setting`(与执行端 sanitizeUntrustedProjectLayer 同域,消除核心安全依赖\n * 兼容层的倒置)。本文件只保留 **Claude 目录方言信任面**:\n *\n * - `detectClaudeExecutableSurface`:探测 `.claude/` + `.mcp.json` 可执行面/内容面\n * - `evaluateClaudeTrust`:综合判定(兼容开 + 有面 + 未信任 → gated)\n *\n * 底层信任白名单从 setting 取。`isClaudeCompatEnabled` 仍是 compat 自己的开关\n * (Claude 兼容是可选特性)。\n */\nimport { existsSync, readdirSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { isClaudeCompatEnabled } from '@x-otto/env'\nimport { isProjectTrusted, claudeTrustStorePath } from '@x-otto/setting'\n\n// re-export 信任判定函数(过渡兼容,消费方直连 setting 后删除)\nexport {\n claudeTrustStorePath,\n isProjectTrusted,\n trustProject,\n untrustProject,\n} from '@x-otto/setting'\n\n/** 项目级 `.claude/` 受信任门面探测结果(可执行面 ① + 内容面 ②,RFC-081 O1)。 */\nexport interface ClaudeExecutableSurface {\n /** 项目根存在 `.mcp.json`(stdio server 启动即跑二进制)。 */\n mcpJson: boolean\n /** 项目 `.claude/commands/` 非空(`!cmd` 展开即 shell)。 */\n commands: boolean\n /** 项目 `.claude/agents/` 非空(攻击者可控 agent profile,默认继承全工具;O1 内容面)。 */\n agents: boolean\n /** 项目 `.claude/skills/` 非空(攻击者可控 prompt 内容;O1 内容面)。 */\n skills: boolean\n /** 任一受信任门面存在。 */\n any: boolean\n}\n\nfunction dirHasEntries(dir: string): boolean {\n try {\n return readdirSync(dir).length > 0\n } catch {\n return false\n }\n}\n\n/**\n * 探测项目根 `.claude/` 的受信任门面(不读内容,只看存在性)。\n * 含可执行面(.mcp.json/commands)+ 内容面(agents/skills,RFC-081 O1)——\n * 后者是攻击者可控 prompt/profile,与可执行面同属克隆敌仓的注入向量(对齐 Claude Code)。\n */\nexport function detectClaudeExecutableSurface(workspaceDir: string): ClaudeExecutableSurface {\n const root = resolve(workspaceDir)\n const mcpJson = existsSync(join(root, '.mcp.json'))\n const commands = dirHasEntries(join(root, '.claude', 'commands'))\n const agents = dirHasEntries(join(root, '.claude', 'agents'))\n const skills = dirHasEntries(join(root, '.claude', 'skills'))\n return { mcpJson, commands, agents, skills, any: mcpJson || commands || agents || skills }\n}\n\n/** 信任门综合判定。 */\nexport interface ClaudeTrustVerdict {\n surface: ClaudeExecutableSurface\n trusted: boolean\n /** true = 存在可执行面且未信任(调用方应门控:弹窗或跳过);false = 可直接加载。 */\n gated: boolean\n}\n\n/**\n * 评估项目 `.claude/` 是否需要门控。\n * `gated` 为 true 仅当:兼容开 且 存在可执行面 且 该项目未被信任。\n */\nexport function evaluateClaudeTrust(\n workspaceDir: string,\n storePath = claudeTrustStorePath(),\n): ClaudeTrustVerdict {\n const surface = detectClaudeExecutableSurface(workspaceDir)\n const trusted = isProjectTrusted(workspaceDir, storePath)\n const gated = isClaudeCompatEnabled() && surface.any && !trusted\n return { surface, trusted, gated }\n}\n","/**\n * claude-commands.ts — RFC-032 D2:文件式 slash 命令(`.claude/commands` + `.otto/commands`).\n *\n * Claude Code 的 `.claude/commands/**\\/*.md`:frontmatter(description/argument-hint/allowed-tools/\n * model)+ 正文模板。子目录 → namespace(`frontend/component.md` → `frontend:component`)。\n * 正文展开:`$ARGUMENTS`、`$1..$9`、`@file`(注入文件内容)、`` !`cmd` ``(执行 shell 注入 stdout)。\n *\n * 安全(§1.5):项目 `.claude/commands` 是可执行面(`!cmd` = shell)。发现层按信任门过滤——\n * 未信任项目不加载其 `.claude/commands`。`.otto/commands`(用户自有)始终加载。\n * 展开器的 `!cmd`/`@file` 执行器可注入(测试隔离),默认实现带超时防挂死。\n */\nimport { execFile } from 'node:child_process'\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { readFile as readFileAsync } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join, relative, resolve, sep } from 'node:path'\nimport { promisify } from 'node:util'\n// RFC-161 M1(R8 重路由):findWorkspaceRoot 此前经 coding/src/plugin/install-plugin.ts\n// 间接引用,该文件本身也只是对 @x-otto/env 的 re-export(RFC-130 终局审核后收敛的单一实现)\n// ——迁移后直连真源,不再经过 coding 内部模块。\nimport { isClaudeCompatEnabled, findWorkspaceRoot } from '@x-otto/env'\nimport { createLogger, splitFrontmatter, parseFrontmatterList, mapClaudeAgentToolNames, checkShellCommand, buildAllowedEnv } from '@x-otto/shared'\nimport { evaluateClaudeTrust } from './claude-trust'\nimport { evaluatePluginTrust } from '@x-otto/plugin'\nimport { discoverPlugins, type DiscoveredPlugin } from '@x-otto/plugin'\n// RFC-421 D1:FileCommand 协议类型上移到 @x-otto/interchange(零依赖协议域),消除\n// \"宿主核心类型真源在兼容层\"的依赖倒置。本包从 interchange 取类型,再经 index.ts re-export。\nimport type { FileCommand } from '@x-otto/interchange'\n// re-export 供本包 index.ts 过渡期继续对外暴露(RFC-421 规则 4:re-export 过渡策略)\nexport type { FileCommand }\n\nconst logger = createLogger('@x-otto/claude-compat:claude-commands')\n\n// FileCommand 类型从 @x-otto/interchange import(RFC-421 D1),此处不再定义。\n\ninterface ScanLayer {\n dir: string\n source: 'claude' | 'otto' | 'plugin'\n scope: 'project' | 'repository' | 'user'\n /** plugin 层的来源 id(source==='plugin')。 */\n pluginId?: string\n}\n\n/** 递归收集目录下的 *.md,返回 [相对路径(无扩展名), 绝对路径]。 */\nfunction walkMarkdown(root: string): [string, string][] {\n const out: [string, string][] = []\n const walk = (dir: string): void => {\n let entries: import('node:fs').Dirent[]\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const e of entries) {\n const full = join(dir, e.name)\n if (e.isDirectory()) walk(full)\n else if (e.isFile() && e.name.endsWith('.md')) {\n const rel = relative(root, full).slice(0, -'.md'.length)\n out.push([rel, full])\n }\n }\n }\n walk(root)\n return out\n}\n\nfunction parseCommandFile(relName: string, filePath: string, layer: ScanLayer): FileCommand | null {\n let content: string\n try {\n content = readFileSync(filePath, 'utf-8')\n } catch {\n return null\n }\n const parsed = splitFrontmatter(content)\n const fields = parsed?.fields\n const body = parsed ? parsed.body : content.trim()\n if (!body) return null\n\n const allowedRaw = parseFrontmatterList(\n fields?.get('allowed-tools') ?? fields?.get('allowedtools'),\n )\n const name = relName.split(sep).join(':')\n return {\n name,\n description: fields?.get('description'),\n argumentHint: fields?.get('argument-hint') ?? fields?.get('argumenthint'),\n allowedTools:\n layer.source === 'claude' && allowedRaw ? mapClaudeAgentToolNames(allowedRaw) : allowedRaw,\n model: fields?.get('model'),\n body,\n source: layer.source,\n scope: layer.scope,\n pluginId: layer.pluginId,\n filePath,\n }\n}\n\n/**\n * 发现文件式命令。优先级(低→高,同名 last-wins):\n * plugin[*] < user/claude < user/otto < project/claude < project/otto。\n * 插件命令最低(R7:otto/claude 永远覆盖插件同名命令)。\n * `.claude/commands` 仅在兼容开且项目已信任(未 gated)时纳入;\n * 项目级插件命令是可执行面(`!cmd`),未信任(gated)则跳过(RFC-044 R4)。\n *\n * `plugins` 缺省自动发现 `<.otto>/plugins`(项目+用户);可注入供测试。\n */\nexport function discoverFileCommands(\n workspaceDir: string,\n plugins: DiscoveredPlugin[] = discoverPlugins({ cwd: resolve(workspaceDir), homedir: homedir() }),\n): FileCommand[] {\n const ws = resolve(workspaceDir)\n const home = homedir()\n const compat = isClaudeCompatEnabled()\n const claudeAllowed = compat && !evaluateClaudeTrust(ws).gated\n\n const pluginLayers: ScanLayer[] = plugins\n // builtin scope 插件无真实磁盘目录(合成占位 dir,见 discovery.ts BUILTIN_PLUGINS 注释),\n // 天然没有 `commands/` 子目录可扫描。显式类型谓词(而非普通 boolean filter)让 TS 把窄化后的\n // scope 类型带入后续 .map()(数组方法链上 TS 不会自动跨调用传播窄化,需要类型谓词兜底)。\n .filter((p): p is DiscoveredPlugin & { scope: 'project' | 'repository' | 'user' } => p.scope !== 'builtin')\n .filter((p) => p.manifest.contributes?.commands !== false)\n .filter((p) => !evaluatePluginTrust(p).gated)\n .map((p) => ({\n dir: join(p.dir, 'commands'),\n source: 'plugin' as const,\n scope: p.scope,\n pluginId: p.id,\n }))\n\n // 仓库级 .otto/commands(RFC-130):当 cwd 在 monorepo 子目录时,额外扫描 workspace root 的命令。\n const workspaceRoot = findWorkspaceRoot(ws)\n const repoLayer: ScanLayer | undefined =\n workspaceRoot && resolve(workspaceRoot) !== ws\n ? { dir: join(resolve(workspaceRoot), '.otto', 'commands'), source: 'otto', scope: 'repository' }\n : undefined\n\n const layers: ScanLayer[] = [\n ...pluginLayers,\n ...(compat\n ? [\n {\n dir: join(home, '.claude', 'commands'),\n source: 'claude' as const,\n scope: 'user' as const,\n },\n ]\n : []),\n { dir: join(home, '.otto', 'commands'), source: 'otto', scope: 'user' },\n ...(repoLayer ? [repoLayer] : []),\n ...(claudeAllowed\n ? [\n {\n dir: join(ws, '.claude', 'commands'),\n source: 'claude' as const,\n scope: 'project' as const,\n },\n ]\n : []),\n { dir: join(ws, '.otto', 'commands'), source: 'otto', scope: 'project' },\n ]\n\n const byName = new Map<string, FileCommand>()\n for (const layer of layers) {\n if (!existsSync(layer.dir)) continue\n for (const [rel, full] of walkMarkdown(layer.dir)) {\n const cmd = parseCommandFile(rel, full, layer)\n if (cmd) byName.set(cmd.name, cmd)\n }\n }\n const commands = [...byName.values()]\n if (commands.length > 0) {\n logger.info(\n 'Discovered %d file command(s): %s',\n commands.length,\n commands.map((c) => c.name).join(', '),\n )\n }\n return commands\n}\n\nconst execFileAsync = promisify(execFile)\n\n/** 默认 shell 执行器:经 `sh -c`,带超时与输出上限,防挂死。\n * 防御纵深(RFC-421 P1-8 对齐 hooks defaultHookRunner):checkShellCommand 拦已知危险\n * 模式 + buildAllowedEnv 白名单过滤环境变量(不泄漏 API key/token 给外部命令)。\n * 注意:expandCommandBody 的 !cmd 来自已信任的 .claude/commands 或 .otto/commands\n * (信任门已覆盖),此处安全纵深是第二层防线,与 hooks 同基线。 */\nasync function defaultRunShell(cmd: string, cwd: string): Promise<string> {\n const safety = checkShellCommand(cmd)\n if (!safety.safe) {\n logger.warn({ command: cmd, reason: safety.reason }, 'file command shell blocked by safety check')\n return `[command blocked: ${safety.reason}]`\n }\n try {\n const { stdout } = await execFileAsync('sh', ['-c', cmd], {\n cwd,\n env: buildAllowedEnv(),\n timeout: 10_000,\n maxBuffer: 1024 * 1024,\n })\n return stdout.trimEnd()\n } catch (err) {\n return `[command failed: ${(err as Error).message}]`\n }\n}\n\nexport interface ExpandOptions {\n args: string[]\n workspaceDir: string\n /** `` !`cmd` `` 执行器(默认 sh -c + 超时)。 */\n runShell?: (cmd: string, cwd: string) => Promise<string>\n /** `@file` 读取器(默认相对 workspaceDir 读 utf-8)。 */\n readFileText?: (relPath: string, cwd: string) => Promise<string>\n}\n\nasync function defaultReadFile(relPath: string, cwd: string): Promise<string> {\n try {\n return await readFileAsync(resolve(cwd, relPath), 'utf-8')\n } catch (err) {\n return `[file unavailable: ${relPath}]`\n }\n}\n\n/**\n * 展开命令正文。顺序:先 `$ARGUMENTS`/`$N`(位置参数),再 `@file`,最后 `` !`cmd` ``\n * (故 cmd/file 引用里可含已替换的参数)。\n */\nexport async function expandCommandBody(body: string, opts: ExpandOptions): Promise<string> {\n const args = opts.args\n const runShell = opts.runShell ?? defaultRunShell\n const readText = opts.readFileText ?? defaultReadFile\n\n // 全用函数式 replace:替换串(args/文件内容/命令输出)含 $&/$1/$$ 等不会被当特殊替换模式解析(注入防御)。\n let out = body.replace(/\\$ARGUMENTS\\b/g, () => args.join(' '))\n out = out.replace(/\\$([1-9])\\b/g, (_m, d: string) => args[Number(d) - 1] ?? '')\n\n // 用 index-based 切片替换(而非 replace/replaceAll),防止已展开内容中碰巧含\n // 其他文件的 @ref 或 !`cmd` 引用时误替换(跨文件注入)。逆序遍历,前面的 index 始终有效。\n const fileRefs = [...out.matchAll(/(^|\\s)@([^\\s`]+)/g)]\n for (const m of fileRefs.reverse()) {\n const relPath = m[2]!\n const content = await readText(relPath, opts.workspaceDir)\n const replacement = `${m[1]}\\n\\`\\`\\`\\n${content}\\n\\`\\`\\``\n out = out.slice(0, m.index!) + replacement + out.slice(m.index! + m[0].length)\n }\n\n const cmdRefs = [...out.matchAll(/!`([^`]+)`/g)]\n for (const m of cmdRefs.reverse()) {\n const stdout = await runShell(m[1]!, opts.workspaceDir)\n out = out.slice(0, m.index!) + stdout + out.slice(m.index! + m[0].length)\n }\n\n return out\n}\n","/**\n * claude-hooks.ts — RFC-032 D5:Claude Code hooks → otto hook 命令桥(最高风险=任意命令执行).\n *\n * Claude hooks 是「事件 → matcher → shell command + JSON 协议」;otto hooks 是进程内 TS。\n * 本桥把 Claude 事件映射到 otto timing,命中 matcher 时按 Claude 协议 fork command。\n *\n * 实测纠正(v2 review):otto `HookSpec.timing` 单数,故**每 timing 一个 HookSpec**共享逻辑;\n * `tool.execute.before` 有三态 `decision`(allow/deny/ask)+ `cancelled`,PreToolUse 可干净映射;\n * `Stop`→`stream.end`、`SubagentStop`→`task.completed`、`Notification`→`notification`(observer,按 notification_type 匹配 matcher)。\n *\n * 安全:**默认关**(`isClaudeHooksCompatEnabled`)+ 项目信任门(未信任不桥)+ 命令带超时\n * (默认 10s,复刻 Claude `timeout`)+ spawn 串 kill 防挂死([[subagent-hang-blocking-vectors]])。\n */\nimport { spawn } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { isClaudeHooksCompatEnabled, CLAUDE_HOME } from '@x-otto/env'\nimport { buildAllowedEnv, checkShellCommand, createLogger, parseJsonc, ottoToClaudeToolName } from '@x-otto/shared'\nimport { defineHook, type HookSpec } from '@x-otto/hooks'\nimport { evaluateClaudeTrust } from './claude-trust'\nimport { globalProcessRuntime } from '@x-otto/runtime'\n\nconst logger = createLogger('@x-otto/claude-compat:claude-hooks')\n\nexport interface ClaudeHookCommand {\n type?: string\n command: string\n timeout?: number\n}\nexport interface ClaudeHookGroup {\n matcher?: string\n hooks: ClaudeHookCommand[]\n}\n/** event 名 → matcher 组列表。 */\nexport type ClaudeHooksConfig = Record<string, ClaudeHookGroup[]>\n\n/** 从 settings.json 原始对象解析 hooks 段(容错;非法项跳过)。 */\nexport function parseClaudeHooks(raw: unknown): ClaudeHooksConfig {\n const hooks = (raw as { hooks?: unknown })?.hooks\n if (!hooks || typeof hooks !== 'object') return {}\n const out: ClaudeHooksConfig = {}\n for (const [event, groups] of Object.entries(hooks as Record<string, unknown>)) {\n if (!Array.isArray(groups)) continue\n const parsed: ClaudeHookGroup[] = []\n for (const g of groups) {\n if (!g || typeof g !== 'object') continue\n const grp = g as Record<string, unknown>\n const matcher = typeof grp.matcher === 'string' ? grp.matcher : undefined\n const cmds = Array.isArray(grp.hooks) ? grp.hooks : []\n const commands: ClaudeHookCommand[] = []\n for (const c of cmds) {\n if (c && typeof c === 'object') {\n const cmd = c as Record<string, unknown>\n if (typeof cmd.command === 'string') {\n commands.push({\n type: typeof cmd.type === 'string' ? cmd.type : undefined,\n command: cmd.command,\n timeout: typeof cmd.timeout === 'number' ? cmd.timeout : undefined,\n })\n }\n }\n }\n if (commands.length > 0) parsed.push({ matcher, hooks: commands })\n }\n if (parsed.length > 0) out[event] = parsed\n }\n return out\n}\n\n/** 加载 `.claude` hooks(默认关 / 未信任 → {})。 */\nexport function loadClaudeHooksConfig(workspaceDir?: string): ClaudeHooksConfig {\n if (!isClaudeHooksCompatEnabled()) return {}\n if (workspaceDir && evaluateClaudeTrust(workspaceDir).gated) return {}\n const files = [\n join(CLAUDE_HOME, 'settings.json'),\n ...(workspaceDir\n ? [\n join(resolve(workspaceDir), '.claude', 'settings.json'),\n join(resolve(workspaceDir), '.claude', 'settings.local.json'),\n ]\n : []),\n ]\n const merged: ClaudeHooksConfig = {}\n for (const file of files) {\n if (!existsSync(file)) continue\n try {\n const cfg = parseClaudeHooks(parseJsonc(readFileSync(file, 'utf-8')))\n for (const [event, groups] of Object.entries(cfg)) {\n merged[event] = [...(merged[event] ?? []), ...groups]\n }\n } catch (err) {\n logger.warn({ file, err }, 'Failed to parse .claude hooks, skipped')\n }\n }\n return merged\n}\n\n/** matcher 匹配:空/`*` → 全匹配;否则按 Claude 名(+otto 名兜底)做正则。 */\nexport function matchesHookMatcher(matcher: string | undefined, ottoToolName: string): boolean {\n if (!matcher || matcher === '*') return true\n const claudeName = ottoToClaudeToolName(ottoToolName)\n let re: RegExp\n try {\n re = new RegExp(matcher)\n } catch {\n return matcher === claudeName || matcher === ottoToolName\n }\n return re.test(claudeName) || re.test(ottoToolName)\n}\n\nexport interface HookRunResult {\n stdout: string\n exitCode: number\n}\nexport type HookRunner = (\n command: string,\n stdin: string,\n opts: { cwd: string; timeoutMs: number },\n) => Promise<HookRunResult>\n\n/** 默认 runner:`sh -c command`,写 stdin、超时 kill、收 stdout(防挂死)。\n * 防御纵深(endgame review B1):checkShellCommand 拦已知危险模式 + buildAllowedEnv\n * 白名单过滤环境变量(不泄漏 API key/token 给外部 hook 命令)——与 plugin-hooks 同基线。 */\nexport const defaultHookRunner: HookRunner = (command, stdin, { cwd, timeoutMs }) =>\n new Promise<HookRunResult>((res) => {\n let done = false\n const finish = (exitCode: number, stdout: string): void => {\n if (!done) {\n done = true\n res({ stdout, exitCode })\n }\n }\n const safety = checkShellCommand(command)\n if (!safety.safe) {\n logger.warn({ command, reason: safety.reason }, 'Claude hook command blocked by safety check')\n return finish(126, '')\n }\n let stdout = ''\n let child: ReturnType<typeof spawn>\n try {\n child = spawn('sh', ['-c', command], { cwd, env: buildAllowedEnv() })\n // RFC-085 M4: register for unified kill tracking (short-lived, self-terminating)\n globalProcessRuntime.registerChild(\n { command: 'sh', args: ['-c', command], owner: { type: 'agent-tool', id: 'claude-hook' }, category: 'tool', lifecycle: 'evictable', cwd },\n child,\n )\n } catch {\n return finish(1, '')\n }\n const timer = setTimeout(() => {\n child.kill('SIGKILL')\n finish(124, stdout)\n }, timeoutMs)\n child.stdout?.on('data', (d) => (stdout += String(d)))\n child.on('error', () => {\n clearTimeout(timer)\n finish(1, stdout)\n })\n child.on('close', (code) => {\n clearTimeout(timer)\n finish(code ?? 0, stdout)\n })\n try {\n child.stdin?.write(stdin)\n child.stdin?.end()\n } catch {}\n })\n\nexport interface PreToolUseVerdict {\n decision?: 'allow' | 'deny' | 'ask'\n block?: boolean\n reason?: string\n}\n\n/** 解释 PreToolUse 钩子输出:exit 2 / decision:block / continue:false → 阻断;permissionDecision → 决策。 */\nexport function interpretPreToolUse(stdout: string, exitCode: number): PreToolUseVerdict {\n if (exitCode === 2) return { block: true, reason: stdout.trim() || 'hook exit 2' }\n const text = stdout.trim()\n if (!text) return {}\n let json: Record<string, unknown> | undefined\n try {\n json = JSON.parse(text) as Record<string, unknown>\n } catch {\n return {}\n }\n if (json?.decision === 'block' || json?.continue === false) {\n return { block: true, reason: (json.reason ?? json.stopReason) as string | undefined }\n }\n const hso = json?.hookSpecificOutput as Record<string, unknown> | undefined\n if (hso?.permissionDecision) {\n return {\n decision: hso.permissionDecision as 'allow' | 'deny' | 'ask' | undefined,\n reason: hso.permissionDecisionReason as string | undefined,\n }\n }\n return {}\n}\n\n/**\n * Claude 事件 → otto observer timing。`Stop`→`stream.end`(回合结束)、`SubagentStop`→\n * `task.completed`(子代理/任务完成)均为 fire-and-forget;`Notification`→`notification`(按 notification_type 匹配 matcher)。\n */\nconst OBSERVER_EVENTS: Record<string, string> = {\n PostToolUse: 'tool.execute.after',\n UserPromptSubmit: 'chat.message.before',\n PreCompact: 'compaction.before',\n SessionStart: 'session.created',\n SessionEnd: 'session.deleted',\n Stop: 'stream.end',\n SubagentStop: 'task.completed',\n Notification: 'notification',\n}\n\nexport interface ClaudeHookBridgeDeps {\n workspaceDir?: string\n sessionId?: string\n runner?: HookRunner\n /** 注入配置(缺省经 loadClaudeHooksConfig 读盘)。 */\n config?: ClaudeHooksConfig\n}\n\nfunction payload(\n event: string,\n cwd: string,\n sessionId: string,\n extra: Record<string, unknown>,\n): string {\n return JSON.stringify({\n session_id: sessionId,\n transcript_path: '',\n cwd,\n hook_event_name: event,\n ...extra,\n })\n}\n\n/**\n * 构造 Claude hooks 桥的 otto HookSpec 列表(默认关 / 无配置 → [])。\n * PreToolUse = 拦截器(可 deny/ask/block);其余映射事件 = 观察器(fire-and-forget)。\n */\nexport function createClaudeHookBridgeHooks(deps: ClaudeHookBridgeDeps): HookSpec[] {\n const config = deps.config ?? loadClaudeHooksConfig(deps.workspaceDir)\n if (Object.keys(config).length === 0) return []\n\n const cwd = deps.workspaceDir ? resolve(deps.workspaceDir) : process.cwd()\n const runner = deps.runner ?? defaultHookRunner\n const run = (c: ClaudeHookCommand, stdin: string): Promise<HookRunResult> =>\n runner(c.command, stdin, { cwd, timeoutMs: (c.timeout ?? 10) * 1000 })\n\n const specs: HookSpec[] = []\n\n if (config['PreToolUse']) {\n const groups = config['PreToolUse']\n specs.push(\n defineHook({\n name: 'claude-compat:PreToolUse',\n timing: 'tool.execute.before',\n priority: 40,\n handle: async (input, output) => {\n for (const g of groups) {\n if (!matchesHookMatcher(g.matcher, input.toolName)) continue\n for (const c of g.hooks) {\n const stdin = payload('PreToolUse', cwd, input.sessionId, {\n tool_name: ottoToClaudeToolName(input.toolName),\n tool_input: input.args,\n })\n const { stdout, exitCode } = await run(c, stdin)\n const v = interpretPreToolUse(stdout, exitCode)\n if (v.block || v.decision === 'deny') {\n output.cancelled = true\n output.cancelReason = v.reason ?? 'blocked by .claude hook'\n output.decision = 'deny'\n output.decisionReason = output.cancelReason\n return\n }\n if (v.decision === 'ask') {\n output.decision = 'ask'\n output.decisionReason = v.reason\n }\n }\n }\n },\n }),\n )\n }\n\n for (const [event, timing] of Object.entries(OBSERVER_EVENTS)) {\n const groups = config[event]\n if (!groups) continue\n specs.push(\n defineHook({\n name: `claude-compat:${event}`,\n timing: timing as Parameters<typeof defineHook>[0]['timing'],\n priority: 40,\n handle: async (input: unknown) => {\n const inp = input as Record<string, unknown>\n const toolName = typeof inp.toolName === 'string' ? inp.toolName : undefined\n const notifType =\n typeof inp.notification_type === 'string' ? inp.notification_type : undefined\n for (const g of groups) {\n if (event === 'Notification') {\n if (!matchesHookMatcher(g.matcher, notifType ?? '')) continue\n } else if (toolName && !matchesHookMatcher(g.matcher, toolName)) {\n continue\n }\n const sessionId = typeof inp.sessionId === 'string' ? inp.sessionId : ''\n const stdin = payload(event, cwd, deps.sessionId ?? sessionId, {\n ...(toolName ? { tool_name: ottoToClaudeToolName(toolName) } : {}),\n ...(event === 'Notification'\n ? {\n message: inp.message,\n ...(inp.title ? { title: inp.title } : {}),\n notification_type: notifType,\n }\n : {}),\n })\n for (const c of g.hooks) {\n try {\n await run(c, stdin)\n } catch (err) {\n logger.warn({ event, err }, 'claude hook command failed')\n }\n }\n }\n },\n }),\n )\n }\n\n return specs\n}\n","/**\n * dot-mcp-json.ts — RFC-421 D2:Claude `.mcp.json` 信任门(解析器已归位 @x-otto/mcp).\n *\n * `parseDotMcpJson`(文件格式解析 + env 展开)已迁至 `@x-otto/mcp`(McpServerConfig 同域包),\n * 消除 `coding/plugin/mcp.ts` 反向依赖 compat(RFC-421 P0-1)。本文件只保留 Claude 兼容的\n * 信任门逻辑:兼容关 / 项目未信任(gated)→ {};否则调用 mcp 的 `parseDotMcpJson` 返回。\n *\n * 信任门(§1.5):`.mcp.json` 的 stdio server 启动即跑任意二进制 = 可执行面。\n * otto `settings.mcp_servers` 始终覆盖同名项(调用方合并时处理)。\n */\nimport type { McpServerConfig } from '@x-otto/mcp'\nimport { parseDotMcpJson } from '@x-otto/mcp'\nimport { isClaudeCompatEnabled } from '@x-otto/env'\nimport { evaluateClaudeTrust } from './claude-trust'\n\n/**\n * 信任门后的 `.mcp.json` server:兼容关 / 未信任(gated)→ {};否则解析返回。\n * 调用方应把它作为**低优先级**与 settings.mcp_servers 合并(otto 同名覆盖)。\n */\nexport function resolveClaudeMcpServers(workspaceDir: string): Record<string, McpServerConfig> {\n if (!isClaudeCompatEnabled()) return {}\n if (evaluateClaudeTrust(workspaceDir).gated) return {}\n return parseDotMcpJson(workspaceDir)\n}\n"],"mappings":"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"}
|