alchemylab 1.4.13 → 2.0.1

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/994.js DELETED
@@ -1,126 +0,0 @@
1
- "use strict"
2
- exports.id=994,exports.ids=[994],exports.modules={5994(t,exports,e){var r=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}}
3
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.InteractiveShell=void 0
4
- const s=r(e(3785)),a=e(5317),i=e(3807),n=r(e(6928)),o=r(e(9896)),l=r(e(6895)),c=e(4139),u=e(4192),h=e(1152)
5
- exports.InteractiveShell=class{constructor(t){if(this.currentBranch="main",this.currentStatus=null,this.history=[],this.statusCache={map:new Map,ts:0},this.pathExecutableCache=null,this.projectPath=t.projectPath,this.currentSymlink=n.default.join(t.projectPath,c.ALCHEMY_DIR_NAME,"current"),this.currentDir=t.projectPath,this.logger=t.logger,this.historyFile=n.default.join(t.projectPath,c.ALCHEMY_DIR_NAME,"shell-history.txt"),this.projectId=t.projectId,t.remoteServerUrl){const e=(0,h.createMetricsCollector)(!1)
6
- this.apiClient=new u.BaseApiClient(t.remoteServerUrl,t.logger,e,t.authToken)}}async start(t){return this.shutdownCallback=t,this.currentBranch="main",this.updateCurrentSymlink(),this.loadHistory(),this.rl=s.default.createInterface({input:process.stdin,output:process.stdout,prompt:this.buildPrompt(),historySize:500,removeHistoryDuplicates:!0,completer:t=>this.completer(t)}),this.history.length>0&&(this.rl.history=[...this.history].reverse()),this.printHelp(!0),this.rl.prompt(),new Promise((t=>{this.rl.on("line",(async e=>{const r=e.trim()
7
- if(r){if(this.appendHistory(r),await this.processCommand(r))return this.rl.close(),void t()
8
- this.rl.prompt()}else this.rl.prompt()}))
9
- let e,r=!1
10
- this.rl.on("SIGINT",(()=>{r?(e&&clearTimeout(e),this.rl.close()):(r=!0,console.log(""),console.log(l.default.gray("Press Ctrl+C again to exit, or continue typing.")),this.rl.prompt(),e=setTimeout((()=>{r=!1}),2e3))})),this.rl.on("close",(async()=>{this.shutdownCallback&&await this.shutdownCallback(),t()}))}))}buildPrompt(){const t=l.default.green(this.currentBranch),e=this.currentStatus&&"main"!==this.currentBranch?` ${l.default.gray("|")} ${this.colorStatus(this.currentStatus)}`:""
11
- return`${l.default.cyan("[")}${t}${e}${l.default.cyan("]")} ${l.default.yellow("alchemy")}${l.default.gray(">")} `}colorStatus(t){switch(t){case"completed":return l.default.green(t)
12
- case"in_progress":return l.default.yellow(t)
13
- case"open":return l.default.blue(t)
14
- case"ready_for_review":return l.default.magenta("review")
15
- case"canceled":default:return l.default.gray(t)
16
- case"reverted":return l.default.red(t)}}async fetchRequestStatus(t){if(!this.apiClient||!this.projectId)return null
17
- try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/requests/${t}`),r=e?.data||e
18
- return r?.status||null}catch{return null}}async fetchBulkStatuses(){if(1e4>Date.now()-this.statusCache.ts&&this.statusCache.map.size>0)return this.statusCache.map
19
- if(!this.apiClient||!this.projectId)return new Map
20
- try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/requests?limit=100&sortBy=updated&sortDirection=desc`),e=t?.data||t||[],r=new Map
21
- for(const t of Array.isArray(e)?e:[])t.localId&&t.status&&r.set(t.localId,t.status)
22
- return this.statusCache={map:r,ts:Date.now()},r}catch{return this.statusCache.map}}formatRequestWithStatus(t,e,r){return`${r?l.default.green(t):t}${e?" "+this.colorStatus(e):""}`}updatePrompt(){this.rl.setPrompt(this.buildPrompt())}getPathExecutables(){if(this.pathExecutableCache)return this.pathExecutableCache
23
- const t=new Set,e=(process.env.PATH||"").split(n.default.delimiter)
24
- for(const r of e)try{if(!o.default.existsSync(r))continue
25
- for(const e of o.default.readdirSync(r))t.add(e)}catch{}return this.pathExecutableCache=Array.from(t).sort(),this.pathExecutableCache}completer(t){const e=["br","branch","logs","pwd","cd ","help","exit","quit"],r=t.includes(" "),s=t.indexOf(" "),a=r?t.slice(0,s).toLowerCase():""
26
- if(r&&("br"===a||"branch"===a))return setImmediate((()=>this.rl.write("\n"))),[[],""]
27
- if(!r){const r=e.filter((e=>e.startsWith(t))),s=t?this.getPathExecutables().filter((e=>e.startsWith(t))):[],a=[...new Set([...r,...s])]
28
- if(a.length>0)return[a,t]
29
- if(!t)return[e,t]}const i=t.lastIndexOf(" "),l=0>i?t:t.slice(i+1),c=0>i?"":t.slice(0,i+1)
30
- try{const e=l.includes(n.default.sep)||l.includes("/"),r=e?n.default.resolve(this.currentDir,n.default.dirname(l)):this.currentDir,s=e?n.default.basename(l):l,a=e?n.default.dirname(l)+"/":""
31
- if(!o.default.existsSync(r))return[[],t]
32
- const i=o.default.readdirSync(r,{withFileTypes:!0}).filter((t=>t.name.startsWith(s)&&!t.name.startsWith("."))).map((t=>{const e=t.isDirectory()?t.name+"/":t.name
33
- return c+a+e}))
34
- if(i.length>0)return[i,t]}catch{}return[[],t]}async detectCurrentBranch(){try{const t=(0,i.simpleGit)(this.currentDir),e=(await t.branch()).current||"main"
35
- this.currentBranch=this.extractRequestId(e)||e}catch{this.currentBranch="main"}}async processCommand(t){const e=t.split(/\s+/),r=e[0].toLowerCase(),s=e.slice(1)
36
- switch(r){case"exit":case"quit":return this.shutdownCallback&&await this.shutdownCallback(),!0
37
- case"help":return this.printHelp(!1),!1
38
- case"br":case"branch":return await this.listBranches(s.length>0?s.join(" "):void 0),!1
39
- case"logs":return await this.tailLogs(s),!1
40
- case"pwd":return console.log(this.getVirtualCwd()),!1
41
- case"cd":return this.changeDirectory(s[0]||"~"),!1
42
- default:return this.executeCommand(t),!1}}changeDirectory(t){try{let e
43
- if(e="~"===t||""===t?this.currentSymlink:n.default.isAbsolute(t)?t:n.default.resolve(this.currentDir,t),!o.default.existsSync(e)||!o.default.statSync(e).isDirectory())return void console.log(l.default.red("cd: no such directory: "+t))
44
- this.currentDir=e}catch(t){console.log(l.default.red("cd: "+t.message))}}async tailLogs(t){const r=n.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"server.log")
45
- if(!o.default.existsSync(r))return void console.log(l.default.gray("No log file found yet. Logs will appear once the server processes requests."))
46
- const s=t[0]&&/^\d+$/.test(t[0])?parseInt(t[0],10):50
47
- console.log(l.default.gray(`Tailing ${r} (last ${s} lines, Ctrl+C to stop)`))
48
- const{spawn:a}=await Promise.resolve().then(e.t.bind(e,5317,23)),i=a("tail",["-n",s+"","-f",r],{stdio:["ignore","inherit","inherit"]})
49
- return new Promise((t=>{const e=process.stdin,r=e.isRaw
50
- this.rl.pause(),e.setRawMode(!0),e.resume()
51
- const s=()=>{e.removeListener("data",a)
52
- try{i.kill("SIGTERM")}catch{}try{e.setRawMode(r??!1)}catch{}this.rl.resume()},a=t=>{3!==t[0]&&"q"!==t.toString()||i.kill("SIGTERM")}
53
- e.on("data",a),i.on("close",(()=>{s(),t()}))}))}updateCurrentSymlink(){try{let t
54
- if("main"===this.currentBranch)t=this.projectPath
55
- else{const e=n.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"worktrees",this.currentBranch)
56
- t=o.default.existsSync(e)?e:this.projectPath}try{o.default.unlinkSync(this.currentSymlink)}catch{}o.default.symlinkSync(t,this.currentSymlink),this.currentDir=this.currentSymlink}catch(t){this.logger.warn("Failed to update current symlink",{error:t.message})}}getVirtualCwd(){return this.currentDir}executeCommand(t){try{(0,a.execSync)(t,{cwd:this.currentDir,stdio:"inherit",env:{...process.env,PWD:this.currentDir,GIT_DIR:void 0,GIT_WORK_TREE:void 0}})}catch(t){void 0!==t.status&&null!==t.status||console.log(l.default.red("Error: "+t.message))}}getWorktreeRequestIds(){const t=n.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"worktrees"),e=new Set
57
- if(o.default.existsSync(t))for(const r of o.default.readdirSync(t,{withFileTypes:!0}))r.isDirectory()&&e.add(r.name)
58
- return e}extractRequestId(t){const e=t.match(/^(?:requests\/)?(req\d+)/)
59
- return e?e[1]:null}async listBranches(t){try{const e=(0,i.simpleGit)(this.projectPath),r=await e.branch(),s=this.getWorktreeRequestIds(),a=await this.fetchBulkStatuses(),n=new Set,o=[],c=[]
60
- for(const t of r.all){if(t.startsWith("remotes/"))continue
61
- if("main"===t)continue
62
- const e=this.extractRequestId(t)
63
- e&&!n.has(e)&&(n.add(e),o.push(e))}for(const t of s)n.has(t)||(n.add(t),o.push(t))
64
- o.sort(((t,e)=>t.localeCompare(e)))
65
- for(const t of o){const e=this.currentBranch===t||this.currentBranch==="requests/"+t
66
- c.push(this.formatRequestWithStatus(t,a.get(t),e))}if(o.push("main"),c.push("main"===this.currentBranch?l.default.green("main"):"main"),0===o.length)return void console.log(l.default.gray("No branches found."))
67
- if(t&&o.includes(t))return void(t!==this.currentBranch&&await this.switchBranch(t))
68
- let u=o.indexOf(this.currentBranch)
69
- 0>u&&(u=o.length-1)
70
- const h=await this.selectFromList(o,"Select branch:",c,t?0:u,!0,t)
71
- this.rl.pause(),h&&h!==this.currentBranch?await this.switchBranch(h):h||console.log(l.default.gray("Cancelled.")),this.rl.resume()}catch(t){console.log(l.default.red("Failed to list branches: "+t.message))}}async ensureWorktree(t,e){const r=n.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"worktrees"),s=n.default.join(r,t)
72
- if(o.default.existsSync(s)&&o.default.statSync(s).isDirectory())return s
73
- console.log(l.default.gray(`Creating worktree for ${t}...`)),o.default.mkdirSync(r,{recursive:!0})
74
- const a=(0,i.simpleGit)(this.projectPath)
75
- return await a.raw(["worktree","add",s,e]),console.log(l.default.gray("Worktree created at "+s)),s}async selectFromList(t,e,r,s,a,i){const n=t,o=r||t
76
- return new Promise((t=>{let r=0
77
- const c=process.stdin,u=c.isRaw
78
- let h=!1,d=i||"",f=n.map(((t,e)=>e))
79
- null!=s&&s>=0&&s<n.length&&(r=s)
80
- const p=()=>{if(!d)return void(f=n.map(((t,e)=>e)))
81
- const t=d.toLowerCase()
82
- let e=n.map(((t,e)=>({item:t,i:e}))).filter((({item:e})=>e.toLowerCase().startsWith(t)))
83
- 0===e.length&&/^\d+$/.test(t)&&(e=n.map(((t,e)=>({item:t,i:e}))).filter((({item:e})=>e.replace(/\D/g,"").endsWith(t)))),0===e.length&&(e=n.map(((t,e)=>({item:t,i:e}))).filter((({item:e})=>e.toLowerCase().includes(t)))),f=e.map((t=>t.i))},y=process.stdout.rows||24,g=Math.max(3,y-4)
84
- let m=0,w=0,S=0
85
- this.rl.pause()
86
- const C=()=>{const t=f.length,s=t>g
87
- s?m>r?m=r:m+g>r||(m=r-g+1):m=0
88
- const i=(()=>{const t=f.length
89
- if(0===t)return 1
90
- const e=t>g
91
- let r=e?Math.min(g,t-m):t
92
- return e&&m>0&&r++,e&&t>m+g&&r++,r})(),n=i+1
93
- $&&S>1&&process.stdout.write(`\r\x1b[${S-1}A\r`),n>w&&(w=n),S=w
94
- const c=w-n
95
- for(let t=0;c>t;t++)process.stdout.write("\x1b[2K\n")
96
- if(0===t)process.stdout.write(`\x1b[2K ${l.default.gray("no matches")}\n`)
97
- else{const e=s&&m>0,a=s&&t>m+g,i=s?Math.min(m+g,t):t,n=s?m:0
98
- e&&process.stdout.write(`\x1b[2K ${l.default.gray(`\u25b2 ${m} more`)}\n`)
99
- for(let t=n;i>t;t++){const e=f[t],s=t===r?l.default.cyan("\u276f "):" ",a=t===r?l.default.cyan(o[e]):o[e]
100
- process.stdout.write(`\x1b[2K${s}${a}\n`)}a&&process.stdout.write(`\x1b[2K ${l.default.gray(`\u25bc ${t-i} more`)}\n`)}a&&d?process.stdout.write(`\x1b[2K${l.default.gray(">")} ${l.default.yellow(d)}${l.default.gray("_")}`):a?process.stdout.write(`\x1b[2K${l.default.gray(">")} ${l.default.gray("type to filter...")}`):process.stdout.write("\x1b[2K"+l.default.gray(e)),$=!0}
101
- d&&(p(),r=0)
102
- let $=!1
103
- C(),c.setRawMode(!0),c.resume()
104
- const b=()=>{if(!h){h=!0,c.removeListener("data",v)
105
- try{c.setRawMode(u??!1)}catch{}$&&S>1&&process.stdout.write(`\r\x1b[${S-1}A\r`),process.stdout.write("\x1b[J"),this.rl.resume()}},v=e=>{if(!h)try{const s=e.toString()
106
- "\x1b[A"===s?f.length>0&&(r=Math.max(0,r-1),C()):"\x1b[B"===s?f.length>0&&(r=Math.min(f.length-1,r+1),C()):"\r"===s||"\n"===s?f.length>0&&(b(),t(n[f[r]])):"\x1b"===s||"\x03"===s?(b(),t(null)):a&&"\x7f"===s?d.length>0&&(d=d.slice(0,-1),p(),r=0,m=0,C()):!a||1!==s.length||" ">s||s>"~"||(d+=s,p(),r=0,m=0,C())}catch{b(),t(null)}}
107
- c.on("data",v)}))}findMatchingBranches(t,e,r){const s=new Set
108
- for(const t of e){const e=this.extractRequestId(t)
109
- e&&s.add(e)}for(const t of r)s.add(t)
110
- const a=Array.from(s)
111
- if(s.has(t))return[t]
112
- const i=a.filter((e=>e.startsWith(t)))
113
- if(i.length>0)return i.sort()
114
- if(/^\d+$/.test(t)){const e=a.filter((e=>e.replace(/\D/g,"").endsWith(t)))
115
- if(e.length>0)return e.sort()}return[]}async switchBranch(t){try{if("main"===t)return this.currentBranch="main",this.currentStatus=null,this.updateCurrentSymlink(),this.updatePrompt(),console.log(l.default.green("Switched to main")),void console.log(l.default.gray(" "+this.currentDir))
116
- const e=(0,i.simpleGit)(this.projectPath),r=await e.branch(),s=this.getWorktreeRequestIds(),a=this.findMatchingBranches(t,r.all,s)
117
- if(0===a.length)return console.log(l.default.red("No branch matching: "+t)),void console.log(l.default.gray(' Use "branches" to see available branches'))
118
- let n
119
- if(1===a.length)n=a[0]
120
- else{const e=await this.fetchBulkStatuses(),r=a.map((t=>this.formatRequestWithStatus(t,e.get(t),!1))),s=await this.selectFromList(a,`Multiple matches for "${t}" \u2014 select one:`,r,void 0,!0)
121
- if(!s)return void console.log(l.default.gray("Cancelled."))
122
- n=s}const o=r.all.includes("requests/"+n)?"requests/"+n:n
123
- await this.ensureWorktree(n,o),this.currentBranch=n,this.updateCurrentSymlink(),this.currentStatus=await this.fetchRequestStatus(n),this.updatePrompt()
124
- const c=this.currentStatus?` ${l.default.gray("|")} ${this.colorStatus(this.currentStatus)}`:""
125
- console.log(l.default.green("Switched to "+n)+c),console.log(l.default.gray(" "+this.getVirtualCwd()))}catch(t){console.log(l.default.red("Failed to switch: "+t.message))}}printHelp(t){t?(console.log(""),console.log(l.default.gray('Type "help" for available commands. Commands run in the current branch directory.')),console.log("")):(console.log(""),console.log(l.default.bold("Interactive Shell Commands:")),console.log(` ${l.default.yellow("branch")} / ${l.default.yellow("br")} Browse and switch branches`),console.log(` ${l.default.yellow("logs")} ${l.default.gray("[N]")} Tail action server log (Ctrl+C to stop, N = lines of history)`),console.log(` ${l.default.yellow("cd")} ${l.default.gray("<dir>")} Change directory within current branch`),console.log(` ${l.default.yellow("pwd")} Show current working directory`),console.log(` ${l.default.yellow("help")} Show this help`),console.log(` ${l.default.yellow("exit")} / ${l.default.yellow("quit")} Stop the server and exit`),console.log(""),console.log(l.default.gray(" Any other input is executed as a shell command in the current directory.")),console.log(""))}loadHistory(){try{if(o.default.existsSync(this.historyFile)){const t=o.default.readFileSync(this.historyFile,"utf-8")
126
- this.history=t.split("\n").filter((t=>""!==t.trim())),this.history.length>500&&(this.history=this.history.slice(-500))}}catch{}}appendHistory(t){try{this.history.push(t),o.default.appendFileSync(this.historyFile,t+"\n")}catch{}}}}}
@@ -1,96 +0,0 @@
1
- (()=>{"use strict"
2
- function e(n){var i=s[n]
3
- if(void 0!==i)return i.exports
4
- var module=s[n]={exports:{}}
5
- return t[n].call(module.exports,module,module.exports,e),module.exports}var t={1569(module){module.exports=require("zod")},2904(e,exports,t){function s(e){return{content:[{type:"text",text:e}]}}function n(e){return{content:[{type:"text",text:"Error: "+e}]}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeOperationsSchema=void 0,exports.handleCLIBridgeOperations=async function(e){const t=(0,o.getCLIBridge)()
6
- if(!t)return n("CLI Bridge not initialized. The action server may not be running.")
7
- const{operation:i}=e
8
- switch(i){case"list-sessions":{const i=await t.sendCommand("list-sessions",{includeArchived:e.includeArchived,status:e.status,agentType:e.agentType,search:e.search,limit:e.limit})
9
- return i.success?s(function(e){if(!e||0===e.length)return"No sessions found."
10
- const t=e.map((e=>{const t=[`[${e.status?.toUpperCase()||"?"}]`,e.id,e.agentType?`(${e.agentType})`:"",e.title||"(untitled)"]
11
- return e.parentId&&t.push(" parent: "+e.parentId),t.filter(Boolean).join(" ")}))
12
- return`Sessions (${e.length}):\n${t.join("\n")}`}(i.data)):n(i.error||"Failed to list sessions")}case"get-messages":{if(!e.sessionId)return n("sessionId is required for get-messages")
13
- const i=await t.sendCommand("get-messages",{sessionId:e.sessionId,limit:e.limit})
14
- return i.success?s((r=i.data)&&0!==r.length?r.map((e=>{const t=e.role?.toUpperCase()||"?",s=e.agent?` [${e.agent}]`:"",n=e.time?new Date(e.time).toLocaleTimeString():"",i=e.completedAt?"":" (streaming...)",o=(e.parts||[]).filter((e=>"text"===e.type&&e.text)).map((e=>e.text)).join("\n"),r=(e.parts||[]).filter((e=>"tool"===e.type||"patch"===e.type)).map((e=>{if("tool"===e.type){const t=e.state?.status||"unknown"
15
- return` [tool: ${e.tool} \u2192 ${t}]`}return"patch"===e.type?` [patch: ${(Array.isArray(e.files)?e.files:[]).map((e=>e.split("/").pop())).join(", ")}]`:""})).filter(Boolean).join("\n")
16
- return`--- ${t}${s} ${n}${i} ---\n${[o,r].filter(Boolean).join("\n")}`})).join("\n\n"):"No messages found."):n(i.error||"Failed to get messages")}case"send-message":{if(!e.sessionId)return n("sessionId is required for send-message")
17
- if(!e.message)return n("message is required for send-message")
18
- let i=e.agent
19
- if(!i){const s=await t.sendCommand("get-session-status",{sessionId:e.sessionId})
20
- s.success&&s.data?.agentType&&(i=s.data.agentType)}const o=await t.sendCommand("send-message",{sessionId:e.sessionId,message:e.message,agent:i,variant:e.variant,system:e.system})
21
- return o.success?s("Message sent successfully."+(o.data?.messageId?" Message ID: "+o.data.messageId:"")):n(o.error||"Failed to send message")}case"create-session":{const i=await t.sendCommand("create-session",{directory:e.directory,title:e.title,parentId:e.parentId,agent:e.agent})
22
- if(!i.success)return n(i.error||"Failed to create session")
23
- const o=i.data
24
- return s(`Session created: ${o.id}\nTitle: ${o.title||"(none)"}\nAgent: ${o.agentType||"default"}\nStatus: ${o.status}`)}case"get-session-status":{if(!e.sessionId)return n("sessionId is required for get-session-status")
25
- const i=await t.sendCommand("get-session-status",{sessionId:e.sessionId})
26
- if(!i.success)return n(i.error||"Failed to get session status")
27
- const o=i.data
28
- return s(`Session: ${o.id}\nTitle: ${o.title||"(none)"}\nStatus: ${o.status}\nAgent: ${o.agentType||"default"}`)}case"list-notifications":{const i=await t.sendCommand("list-notifications",{sessionId:e.sessionId,messageType:e.messageType,status:e.status,limit:e.limit})
29
- return i.success?s(function(e){if(!e||0===e.length)return"No notifications found."
30
- const t=e.map((e=>{const t="question"===e.messageType?"Q":"R",s="question"===e.messageType?"pending"===e.status?"PENDING":e.status?.toUpperCase():"unread"===e.readStatus?"UNREAD":e.readStatus?.toUpperCase(),n=e.content?.question||e.content?.summary||"(no summary)",i=e.createdAt?new Date(e.createdAt).toLocaleTimeString():""
31
- return`[${t}] [${s}] ${e.messageCode} ${n} (${i})`}))
32
- return`Notifications (${e.length}):\n${t.join("\n")}`}(i.data)):n(i.error||"Failed to list notifications")}case"get-notification":{if(!e.messageCode)return n("messageCode is required for get-notification")
33
- const i=await t.sendCommand("get-notification",{messageCode:e.messageCode})
34
- return i.success?s(function(e){if(!e)return"Notification not found."
35
- const t=[]
36
- return t.push("Code: "+e.messageCode),t.push("Type: "+e.messageType),t.push("Status: "+e.status),t.push("From: "+e.fromSessionId),e.toSessionId&&t.push("To: "+e.toSessionId),t.push("Created: "+e.createdAt),"question"===e.messageType?(t.push("Question: "+(e.content?.question||"(none)")),e.content?.questionType&&t.push("Question Type: "+e.content.questionType),e.content?.context&&t.push("Context: "+e.content.context),e.content?.options?.length&&(t.push("Options:"),e.content.options.forEach((e=>{t.push(` - ${e.id}: ${e.label}${e.description?" \u2014 "+e.description:""}`)})))):"report"===e.messageType&&(t.push("Read Status: "+e.readStatus),e.content?.reportStatus&&t.push("Report Status: "+e.content.reportStatus),t.push("Summary: "+(e.content?.summary||"(none)")),e.content?.details&&t.push("Details: "+e.content.details)),e.response&&(t.push("Response: "+JSON.stringify(e.response)),e.respondedBySessionId&&t.push("Responded By: "+e.respondedBySessionId),e.respondedAt&&t.push("Responded At: "+e.respondedAt)),t.join("\n")}(i.data)):n(i.error||"Failed to get notification")}case"respond-to-notification":{if(!e.messageCode)return n("messageCode is required for respond-to-notification")
37
- const i=await t.sendCommand("respond-to-notification",{messageCode:e.messageCode,responseText:e.responseText,selectedOptions:e.selectedOptions})
38
- return i.success?s(`Response sent for ${e.messageCode}.`):n(i.error||"Failed to respond to notification")}case"list-requests":{const i=await t.sendCommand("list-requests",{status:e.status,type:e.type,limit:e.limit,offset:e.offset,sortBy:e.sortBy,sortDirection:e.sortDirection})
39
- return i.success?s(function(e){if(!e||0===e.length)return"No requests found."
40
- const t=e.map((e=>{const t=e.status?.toUpperCase()||"?",s=e.type||"",n=e.updatedAt?new Date(e.updatedAt).toLocaleDateString():""
41
- return`[${t}] ${e.id} ${s} ${e.title||"(untitled)"} (${n})`}))
42
- return`Requests (${e.length}):\n${t.join("\n")}`}(i.data)):n(i.error||"Failed to list requests")}case"get-request":{if(!e.requestId)return n("requestId is required for get-request")
43
- const i=await t.sendCommand("get-request",{requestId:e.requestId})
44
- return i.success?s(function(e){if(!e)return"Request not found."
45
- const t=[]
46
- return t.push("ID: "+e.id),t.push("Title: "+(e.title||"(none)")),t.push("Type: "+(e.type||"(none)")),t.push("Status: "+e.status),e.description&&t.push("Description: "+e.description),e.acceptanceCriteria?.length&&(t.push("Acceptance Criteria:"),e.acceptanceCriteria.forEach(((e,s)=>{const n="string"==typeof e?e:e.description||JSON.stringify(e)
47
- t.push(` ${s+1}. ${n}`)}))),t.push("Created: "+e.createdAt),t.push("Updated: "+e.updatedAt),e.tasks?.length&&(t.push(""),t.push(`Tasks (${e.tasks.length}):`),e.tasks.forEach((e=>{const s=e.status?.toUpperCase()||"?"
48
- t.push(` [${s}] ${e.id} ${e.title||"(untitled)"}`)}))),t.join("\n")}(i.data)):n(i.error||"Failed to get request")}case"wait-for-idle":{if(!e.sessionId)return n("sessionId is required for wait-for-idle")
49
- const i=e.timeoutSeconds||120,o=Date.now()+1e3*i,r=2e3
50
- let a=!1
51
- const c=Date.now()+1e4
52
- for(;Date.now()<c&&Date.now()<o;){const s=await t.sendCommand("get-session-status",{sessionId:e.sessionId})
53
- if(!s.success)return n(s.error||"Failed to check session status")
54
- if("busy"===s.data.status){a=!0
55
- break}await new Promise((e=>setTimeout(e,500)))}for(;Date.now()<o;){const i=await t.sendCommand("get-session-status",{sessionId:e.sessionId})
56
- if(!i.success)return n(i.error||"Failed to check session status")
57
- const o=i.data
58
- if(("idle"===o.status||"error"===o.status)&&a)return s(`Session ${o.id} is now ${o.status}. Title: ${o.title||"(none)"}`)
59
- "busy"===o.status&&(a=!0),await new Promise((e=>setTimeout(e,r)))}return n(`Session ${e.sessionId} did not become idle within ${i}s`)}default:return n("Unknown operation: "+i)}var r}
60
- const i=t(1569),o=t(8524)
61
- exports.CLIBridgeOperationsSchema=i.z.object({operation:i.z.enum(["list-sessions","get-messages","send-message","create-session","get-session-status","wait-for-idle","list-notifications","get-notification","respond-to-notification","list-requests","get-request"]).describe("The CLI operation to perform"),sessionId:i.z.string().optional().describe("OpenCode session ID"),message:i.z.string().optional().describe("Message text to send"),agent:i.z.string().optional().describe("Agent to use for the message"),variant:i.z.string().optional().describe('Thinking variant (e.g., "high", "max")'),system:i.z.string().optional().describe("System instructions for the message"),limit:i.z.number().optional().describe("Max number of messages to return"),directory:i.z.string().optional().describe("Directory for the new session"),title:i.z.string().optional().describe("Title for the new session"),parentId:i.z.string().optional().describe("Parent session ID for child sessions"),timeoutSeconds:i.z.number().optional().describe("Max seconds to wait (default: 120)"),includeArchived:i.z.boolean().optional().describe("Include archived sessions"),status:i.z.string().optional().describe('Filter sessions by status (e.g., "idle", "busy")'),agentType:i.z.string().optional().describe("Filter sessions by agent type"),search:i.z.string().optional().describe("Search sessions by title (case-insensitive substring match)"),requestId:i.z.string().optional().describe('Request ID (e.g., "req004643")'),type:i.z.string().optional().describe('Filter requests by type (e.g., "feature", "bugfix")'),offset:i.z.number().optional().describe("Offset for paginated results"),sortBy:i.z.string().optional().describe('Sort field (e.g., "created", "updated")'),sortDirection:i.z.string().optional().describe('Sort direction ("asc" or "desc")'),messageCode:i.z.string().optional().describe('Session message code (e.g., "Q-abc-001" or "R-abc-001")'),responseText:i.z.string().optional().describe("Text answer for free-form questions"),selectedOptions:i.z.array(i.z.string()).optional().describe("Selected option IDs for alternatives questions"),messageType:i.z.string().optional().describe('Filter by message type: "question" or "report"')})},5086(module){module.exports=require("ws")},5106(module){module.exports=require("zod-to-json-schema")},5802(module){module.exports=require("@modelcontextprotocol/sdk/server/stdio.js")},6865(module){module.exports=require("@modelcontextprotocol/sdk/server/index.js")},6928(module){module.exports=require("path")},6982(module){module.exports=require("crypto")},8524(e,exports,t){var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
62
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){o=e},exports.getCLIBridge=function(){return o}
63
- const n=t(5086),i=s(t(6982))
64
- exports.CLIBridgeWebSocket=class{constructor(e,t=3e4){this.wss=null,this.uiClient=null,this.pendingCommands=new Map,this.heartbeatInterval=null,this.logger=e,this.defaultTimeout=t}attach(e){this.wss=new n.WebSocketServer({server:e,path:"/ws/cli-bridge"}),this.wss.on("connection",(e=>{this.logger.info("[CLIBridge] UI client connected"),this.uiClient&&(this.logger.warn("[CLIBridge] Replacing existing UI client connection"),this.uiClient.close(1e3,"Replaced by new connection")),this.uiClient=e,this.startHeartbeat(),e.on("message",(e=>{try{const t=JSON.parse(e.toString())
65
- this.handleResponse(t)}catch(e){this.logger.error("[CLIBridge] Failed to parse message from UI",{error:e instanceof Error?e.message:e+""})}})),e.on("close",(()=>{this.logger.info("[CLIBridge] UI client disconnected"),this.uiClient===e&&(this.uiClient=null),this.stopHeartbeat()
66
- for(const[e,t]of this.pendingCommands)clearTimeout(t.timer),t.resolve({id:e,success:!1,error:"UI client disconnected during command. The command may have been partially executed."}),this.pendingCommands.delete(e)})),e.on("error",(e=>{this.logger.error("[CLIBridge] WebSocket error",{error:e.message})}))})),this.logger.info("[CLIBridge] WebSocket server attached at /ws/cli-bridge")}isUIConnected(){return null!==this.uiClient&&this.uiClient.readyState===n.WebSocket.OPEN}async sendCommand(e,t={},s){if(!this.isUIConnected())return{id:"",success:!1,error:"alchemy-ui is not connected. Please open alchemy-ui in your browser first."}
67
- const n=i.default.randomUUID(),o={id:n,action:e,params:t}
68
- return new Promise(((t,i)=>{const r=setTimeout((()=>{this.pendingCommands.delete(n),t({id:n,success:!1,error:`Command '${e}' timed out after ${(s||this.defaultTimeout)/1e3}s`})}),s||this.defaultTimeout)
69
- this.pendingCommands.set(n,{resolve:t,reject:i,timer:r})
70
- try{this.uiClient.send(JSON.stringify(o))}catch(e){clearTimeout(r),this.pendingCommands.delete(n),t({id:n,success:!1,error:"Failed to send command: "+(e instanceof Error?e.message:e+"")})}}))}handleResponse(e){const t=this.pendingCommands.get(e.id)
71
- t?(clearTimeout(t.timer),this.pendingCommands.delete(e.id),t.resolve(e)):this.logger.warn("[CLIBridge] Received response for unknown command",{id:e.id})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatInterval=setInterval((()=>{this.uiClient&&this.uiClient.readyState===n.WebSocket.OPEN&&this.uiClient.ping()}),3e4)}stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}close(){this.stopHeartbeat()
72
- for(const[e,t]of this.pendingCommands)clearTimeout(t.timer),t.reject(Error("Bridge shutting down")),this.pendingCommands.delete(e)
73
- this.uiClient&&(this.uiClient.close(1e3,"Server shutting down"),this.uiClient=null),this.wss&&(this.wss.close(),this.wss=null)}}
74
- let o=null},9150(module){module.exports=require("@modelcontextprotocol/sdk/types.js")},9414(e,exports,t){function s(e){const t=Date.now(),s=!e
75
- if(s&&null!==c&&u>t-d)return c
76
- const l=(0,i.join)(e||process.env.ALCHEMY_PROJECT_PATH||process.cwd(),r,a)
77
- if((0,n.existsSync)(l))try{const e=(0,n.readFileSync)(l,"utf-8"),i=JSON.parse(e)
78
- if("number"==typeof i.port&&i.port>0&&65536>i.port)return s&&(c=i.port,d=t),i.port}catch{}return s&&(c=o,d=t),o}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getActionServerPort=s,exports.getActionServerBaseUrl=function(){return"http://localhost:"+s()}
79
- const n=t(9896),i=t(6928),o=3456,r=".alchemy_lab",a="action-server.json"
80
- let c=null,d=0
81
- const u=5e3},9896(module){module.exports=require("fs")}},s={},n={};(()=>{Object.defineProperty(n,"__esModule",{value:!0})
82
- const t=e(6865),s=e(5802),i=e(9150),o=e(5106),r=e(2904),a=e(8524),c=process.argv.slice(2)
83
- let d=null
84
- for(let e=0;e<c.length;e++)"--port"===c[e]&&c[e+1]&&(d=parseInt(c[e+1],10),e++)
85
- if(null===d)try{const{getActionServerPort:t}=e(9414)
86
- d=t()}catch{d=3456}const u=(...e)=>process.stderr.write(`[cli-bridge] ${e.join(" ")}\n`),l=new class{isUIConnected(){return!0}async sendCommand(e,t={},s){return async function(e,t,s){const n=`http://127.0.0.1:${d}/api/cli-bridge/command`,i=new AbortController,o=s||3e4,r=setTimeout((()=>i.abort()),o+5e3)
87
- try{const s=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:e,params:t,timeout:o}),signal:i.signal})
88
- if(!s.ok){const e=await s.text()
89
- return{success:!1,error:`Action server error (${s.status}): ${e}`}}return await s.json()}catch(e){return"AbortError"===e.name?{success:!1,error:`Command timed out after ${o/1e3}s`}:{success:!1,error:`Cannot reach action server at port ${d}: ${e.message}`}}finally{clearTimeout(r)}}(e,t,s)}};(0,a.setCLIBridge)(l)
90
- const p=new t.Server({name:"alchemy-cli-bridge",version:"1.0.0"},{capabilities:{tools:{}}})
91
- p.setRequestHandler(i.ListToolsRequestSchema,(async()=>({tools:[{name:"cli_operations",description:"Interact with OpenCode CLI sessions through alchemy-ui.\n\nOperations:\n list-sessions \u2014 List all active sessions with status and agent type\n Filters: status?, agentType?, search? (title), limit?, includeArchived?\n get-messages \u2014 Get messages from a session (params: sessionId, limit?)\n send-message \u2014 Send a message to a session (params: sessionId, message, agent?, variant?, system?)\n create-session \u2014 Create a new session (params: directory?, title?, parentId?)\n get-session-status \u2014 Check if a session is busy/idle (params: sessionId)\n wait-for-idle \u2014 Wait until a session finishes (params: sessionId, timeoutSeconds?)\n\nRequest operations (no UI connection needed):\n list-requests \u2014 List recent requests (params: status?, type?, limit?, offset?)\n get-request \u2014 Get a specific request with its tasks (params: requestId)\n\nNotification operations (no UI connection needed):\n list-notifications \u2014 List recent notifications (params: sessionId?, messageType?, status?, limit?)\n get-notification \u2014 Get a specific notification by code (params: messageCode)\n respond-to-notification \u2014 Answer a question or acknowledge a report (params: messageCode, responseText?, selectedOptions?)\n\nRequires: alchemy action server running (port 3456) and OpenCode running.",inputSchema:(0,o.zodToJsonSchema)(r.CLIBridgeOperationsSchema)}]}))),p.setRequestHandler(i.CallToolRequestSchema,(async e=>{const{name:t,arguments:s={}}=e.params
92
- if("cli_operations"!==t)return{content:[{type:"text",text:"Unknown tool: "+t}],isError:!0}
93
- try{const e=r.CLIBridgeOperationsSchema.parse(s)
94
- return await(0,r.handleCLIBridgeOperations)(e)}catch(e){return{content:[{type:"text",text:"Error: "+e.message}],isError:!0}}})),async function(){u("Starting CLI Bridge MCP server"),u("Action server port: "+d)
95
- const e=new s.StdioServerTransport
96
- await p.connect(e),u("CLI Bridge MCP server ready (stdio)")}().catch((e=>{u("Fatal error:",e),process.exit(1)})),process.on("SIGTERM",(()=>process.exit(0))),process.on("SIGINT",(()=>process.exit(0)))})(),module.exports=n})()
@@ -1,114 +0,0 @@
1
- "use strict";
2
- /**
3
- * Hook Condition Evaluator
4
- *
5
- * Evaluates hook conditions against event variables to determine
6
- * if a hook should be triggered.
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.evaluateSingleCondition = evaluateSingleCondition;
10
- exports.evaluatePathsCondition = evaluatePathsCondition;
11
- exports.evaluateCondition = evaluateCondition;
12
- /**
13
- * Simple glob-to-regex converter for pattern matching
14
- * Handles common glob patterns without external dependencies
15
- */
16
- // Cache compiled glob→regex patterns to avoid recompilation on every condition check
17
- const _globCache = new Map();
18
- function globToRegex(pattern) {
19
- const cached = _globCache.get(pattern);
20
- if (cached)
21
- return cached;
22
- let regex = pattern
23
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
24
- .replace(/\*\*/g, '{{GLOBSTAR}}')
25
- .replace(/\*/g, '[^/]*')
26
- .replace(/\?/g, '.')
27
- .replace(/\{\{GLOBSTAR\}\}/g, '.*');
28
- const compiled = new RegExp(`^${regex}$`);
29
- // Cap cache to prevent unbounded growth
30
- if (_globCache.size > 500)
31
- _globCache.clear();
32
- _globCache.set(pattern, compiled);
33
- return compiled;
34
- }
35
- /**
36
- * Check if a string matches a glob pattern
37
- */
38
- function isMatch(str, pattern) {
39
- try {
40
- return globToRegex(pattern).test(str);
41
- }
42
- catch {
43
- return false;
44
- }
45
- }
46
- /**
47
- * Evaluate a single condition against the field value
48
- */
49
- function evaluateSingleCondition(fieldValue, operator, value) {
50
- switch (operator) {
51
- case 'matches':
52
- return isMatch(fieldValue, value);
53
- case 'not_matches':
54
- return !isMatch(fieldValue, value);
55
- case 'contains':
56
- return fieldValue.includes(value);
57
- case 'not_contains':
58
- return !fieldValue.includes(value);
59
- case 'equals':
60
- return fieldValue === value;
61
- case 'not_equals':
62
- return fieldValue !== value;
63
- default:
64
- return true;
65
- }
66
- }
67
- /**
68
- * Evaluate a condition against a paths array (comma-separated)
69
- * For 'matches'/'contains' - returns true if ANY path matches
70
- * For 'not_matches'/'not_contains' - returns true if ALL paths don't match
71
- */
72
- function evaluatePathsCondition(paths, operator, value) {
73
- switch (operator) {
74
- case 'matches':
75
- return paths.some(p => isMatch(p, value));
76
- case 'not_matches':
77
- return paths.every(p => !isMatch(p, value));
78
- case 'contains':
79
- return paths.some(p => p.includes(value));
80
- case 'not_contains':
81
- return paths.every(p => !p.includes(value));
82
- case 'equals':
83
- return paths.some(p => p === value);
84
- case 'not_equals':
85
- return paths.every(p => p !== value);
86
- default:
87
- return true;
88
- }
89
- }
90
- /**
91
- * Evaluate hook conditions against current variables
92
- * Returns true if hook should be triggered, false to skip
93
- */
94
- function evaluateCondition(condition, variables) {
95
- // No conditions or empty conditions = always match
96
- if (!condition || !condition.conditions || condition.conditions.length === 0) {
97
- return true;
98
- }
99
- const results = condition.conditions.map((c) => {
100
- const fieldValue = variables[c.field] || '';
101
- // For 'paths' field, check if any path matches
102
- if (c.field === 'paths' && fieldValue.includes(', ')) {
103
- const paths = fieldValue.split(', ');
104
- return evaluatePathsCondition(paths, c.operator, c.value);
105
- }
106
- // Single value evaluation
107
- return evaluateSingleCondition(fieldValue, c.operator, c.value);
108
- });
109
- // Combine results based on logic (default: AND)
110
- return condition.logic === 'OR'
111
- ? results.some((r) => r)
112
- : results.every((r) => r);
113
- }
114
- //# sourceMappingURL=condition-evaluator.js.map
@@ -1,94 +0,0 @@
1
- /**
2
- * Deduplication Utilities
3
- *
4
- * Functions to prevent duplicate hook triggers when OpenCode fires
5
- * the same event multiple times in quick succession.
6
- */
7
- // Track recent tool calls with timestamps
8
- const recentCalls = new Map();
9
- // Default window for considering calls as duplicates (2 seconds)
10
- const DEFAULT_DEDUP_WINDOW = 2000;
11
- /**
12
- * Create a hash for a tool call based on tool name and output/input
13
- * Used to identify potentially duplicate calls
14
- *
15
- * @param toolName - The name of the tool being called
16
- * @param text - The output or input text to include in hash (truncated to 100 chars)
17
- * @returns A string hash identifying this specific call
18
- */
19
- export function createToolCallHash(toolName, text) {
20
- // Simple hash using tool name + first 100 chars of text
21
- return `${toolName}:${text?.substring(0, 100) || ''}`;
22
- }
23
- /**
24
- * Check if a tool call is a duplicate of a recent call
25
- * Also cleans up old entries from the tracking map
26
- *
27
- * @param hash - The hash created by createToolCallHash
28
- * @param now - Current timestamp (allows injection for testing)
29
- * @param dedupWindow - Time window in ms to consider calls as duplicates (default: 2000ms)
30
- * @returns true if this appears to be a duplicate call, false otherwise
31
- */
32
- export function isDuplicateToolCall(hash, now = Date.now(), dedupWindow = DEFAULT_DEDUP_WINDOW) {
33
- const lastTime = recentCalls.get(hash);
34
- if (lastTime && (now - lastTime) < dedupWindow) {
35
- return true;
36
- }
37
- // Clean up old entries (older than 2x the dedup window)
38
- const cleanupThreshold = dedupWindow * 2;
39
- for (const [key, time] of recentCalls.entries()) {
40
- if (now - time > cleanupThreshold) {
41
- recentCalls.delete(key);
42
- }
43
- }
44
- // Record this call
45
- recentCalls.set(hash, now);
46
- return false;
47
- }
48
- /**
49
- * Clear all tracked calls (useful for testing)
50
- */
51
- export function clearRecentCalls() {
52
- recentCalls.clear();
53
- }
54
- /**
55
- * Get the number of tracked calls (useful for testing)
56
- */
57
- export function getRecentCallsCount() {
58
- return recentCalls.size;
59
- }
60
- // Track last processed message ID per session to prevent duplicate triggers
61
- const lastProcessedMessageId = new Map();
62
- /**
63
- * Atomically check if message was processed and mark it if not.
64
- * Returns true if this call should proceed (message was NOT already processed).
65
- * Returns false if should skip (message was already processed by another call).
66
- *
67
- * This prevents race conditions where two concurrent calls both check
68
- * hasProcessedMessage() before either calls markMessageProcessed().
69
- */
70
- export function tryMarkMessageProcessed(sessionId, messageId) {
71
- if (!messageId)
72
- return true; // No message ID = proceed (can't dedupe)
73
- const lastId = lastProcessedMessageId.get(sessionId);
74
- if (lastId === messageId) {
75
- // Already processed - skip
76
- return false;
77
- }
78
- // Mark as processed immediately (atomic with check above in single-threaded JS)
79
- lastProcessedMessageId.set(sessionId, messageId);
80
- // Clean up old entries (keep max 100 sessions)
81
- if (lastProcessedMessageId.size > 100) {
82
- const firstKey = lastProcessedMessageId.keys().next().value;
83
- if (firstKey)
84
- lastProcessedMessageId.delete(firstKey);
85
- }
86
- return true; // Proceed - we claimed this message
87
- }
88
- /**
89
- * Clear processed message tracking (for testing)
90
- */
91
- export function clearProcessedMessages() {
92
- lastProcessedMessageId.clear();
93
- }
94
- //# sourceMappingURL=dedup-utils.js.map
@@ -1,90 +0,0 @@
1
- /**
2
- * Hook Utility Functions
3
- *
4
- * Shared utilities for hook processing including template interpolation
5
- * and ID extraction from tool outputs.
6
- */
7
- /**
8
- * Interpolate template variables in prompt
9
- * Replaces {{variableName}} patterns with corresponding values from variables object
10
- *
11
- * @param template - The template string with {{variable}} placeholders
12
- * @param variables - Key-value pairs to substitute into the template
13
- * @returns The interpolated string with all variables replaced
14
- */
15
- export function interpolatePrompt(template, variables) {
16
- let result = template;
17
- for (const [key, value] of Object.entries(variables)) {
18
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
19
- }
20
- return result;
21
- }
22
- /**
23
- * Extract request ID from tool output
24
- * Matches patterns like "Request completed: req001234" or "Request req001234 completed successfully"
25
- *
26
- * @param text - The tool output text to search
27
- * @returns The request ID (e.g., "req001234") or null if not found
28
- */
29
- export function extractRequestId(text) {
30
- const patterns = [
31
- /Request completed:\s*(req\d+)/i,
32
- /Request (req\d+) completed/i,
33
- /completed.*?(req\d{6})/i,
34
- /Request finalized:\s*(req\d+)/i,
35
- /Request (req\d+) finalized/i,
36
- /finalized.*?(req\d{6})/i,
37
- ];
38
- for (const pattern of patterns) {
39
- const match = text.match(pattern);
40
- if (match)
41
- return match[1];
42
- }
43
- return null;
44
- }
45
- /**
46
- * Extract task ID from tool output
47
- * Matches patterns like "Task tas002152 has been marked as completed"
48
- *
49
- * @param text - The tool output text to search
50
- * @returns The task ID (e.g., "tas002152") or null if not found
51
- */
52
- export function extractTaskId(text) {
53
- const match = text.match(/Task\s+(tas\d+).*(?:completed|marked as completed)/i);
54
- return match ? match[1] : null;
55
- }
56
- /**
57
- * Extract file path from write_file tool output
58
- * Matches patterns like "Created file path/to/file.ts"
59
- *
60
- * @param text - The tool output text to search
61
- * @returns The file path or null if not found
62
- */
63
- export function extractFilePath(text) {
64
- // Match "Created file X" or "**Created file X**"
65
- const createdMatch = text.match(/\*?\*?Created file\s+([^\s*]+)/i);
66
- if (createdMatch)
67
- return createdMatch[1];
68
- // Match "File Modified: Yes" with path context
69
- const modifiedMatch = text.match(/File \d+:\s*([^\n]+)\n.*File Modified:\s*Yes/i);
70
- if (modifiedMatch)
71
- return modifiedMatch[1];
72
- return null;
73
- }
74
- /**
75
- * Sanitize a value for use in template interpolation
76
- * Truncates long values and escapes special characters
77
- *
78
- * @param value - The value to sanitize
79
- * @param maxLength - Maximum length before truncation (default: 1000)
80
- * @returns The sanitized string
81
- */
82
- export function sanitizeTemplateValue(value, maxLength = 1000) {
83
- if (!value)
84
- return '';
85
- if (value.length > maxLength) {
86
- return value.substring(0, maxLength) + '... (truncated)';
87
- }
88
- return value;
89
- }
90
- //# sourceMappingURL=hook-utils.js.map