alchemylab 1.4.3 → 1.4.4

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.
@@ -1161,7 +1161,11 @@ try{if(o.default.existsSync(this.projectFilePath)){this.logger.debug("Updating e
1161
1161
  const t=JSON.parse(o.default.readFileSync(this.projectFilePath,"utf8"))
1162
1162
  t.id=e,t.updatedAt=(new Date).toISOString(),o.default.writeFileSync(this.projectFilePath,JSON.stringify(t,null,2)),this.logger.info("Successfully updated project.json file",{path:this.projectFilePath,oldId:this.projectId,newId:e})}else this.logger.warn("project.json file does not exist, cannot update",{path:this.projectFilePath,projectId:e})
1163
1163
  this.projectId=e,this.initialized=!0,this.logger.info("Project ID update completed successfully",{projectId:e,filePath:this.projectFilePath})}catch(t){const r=(0,c.getErrorMessage)(t),n=`Cannot start AlchemyMCP: Unable to update project.json file.\nFile: ${this.projectFilePath}\nProject ID: ${e}\n\nPlease check:\n\u2022 File permissions - ensure you can write to the alchemy_lab directory\n\u2022 Disk space - ensure sufficient space available\n\u2022 File not in use - close any editors that might have project.json open\n\u2022 Directory exists - alchemy_lab folder should be writable\n\nOriginal error: `+r
1164
- throw this.logger.error("Critical: Project ID update failed, cannot start server",{error:t instanceof Error?t.stack:t+"",projectId:e,filePath:this.projectFilePath,directory:this.alchemyPath}),Error(n)}}isInitialized(){return this.initialized}createMiddleware(){return r(this.getProjectId(),this.logger)}}},1887(e,exports,t){"use strict"
1164
+ throw this.logger.error("Critical: Project ID update failed, cannot start server",{error:t instanceof Error?t.stack:t+"",projectId:e,filePath:this.projectFilePath,directory:this.alchemyPath}),Error(n)}}isInitialized(){return this.initialized}createMiddleware(){return r(this.getProjectId(),this.logger)}}},1816(e,exports){"use strict"
1165
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.formatCommandOutput=function(e,n,o){const s=o.maxOut??t,i=o.maxErr??r,a=e.length>s,c=n.length>i,l=a||c
1166
+ return{output:[a?"\u2026"+e.slice(-s):e,c?"\u2026"+n.slice(-i):n].filter(Boolean).join("\n")||"(no output)",truncatedNote:l?`\n_(output truncated \u2014 view full with \`log:${o.invocationId}\`)_`:"",logFooter:`Log: \`log:${o.invocationId}\``}},exports.formatCombinedOutput=function(e,r){const n=r.maxOut??t,o=e.length>n
1167
+ return{output:o?"\u2026"+e.slice(-n):e,truncatedNote:o?`\n_(output truncated \u2014 view full with \`log:${r.invocationId}\`)_`:"",logFooter:`Log: \`log:${r.invocationId}\``}}
1168
+ const t=3e3,r=1500},1887(e,exports,t){"use strict"
1165
1169
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProxyClient=void 0
1166
1170
  const r=t(9860),n=t(5674),o=t(4192),s=t(2161),i=t(8824)
1167
1171
  class a extends o.BaseApiClient{constructor(e,t,r,n,o,s,i){super(e,t.child({component:"proxy-client"}),r,o,i),this.projectClient=n,this.connectionStatusManager=s,this.logger.info("Proxy client initialized")}setConnectionStatusManager(e){this.logger.debug("Setting connection status manager in ProxyClient"),this.connectionStatusManager=e,this.logger.info("Connection status manager set on "+this.constructor.name)}setAlchemyPath(e){this.logger.info(`Set alchemy path to ${e} on ${this.constructor.name}`)}isNetworkError(e){return"ECONNREFUSED"===e?.code||"ECONNRESET"===e?.code||"ETIMEDOUT"===e?.code||e?.message?.includes("network")||e?.message?.includes("connection")||"ConnectionError"===e?.name||503===e?.status||504===e?.status}isOffline(){return this.connectionStatusManager?.isOffline()||!1}async handlePotentialOfflineOperation(e,t,r){if(this.isOffline()){this.logger.info(`Operation ${e} attempted while offline`,{params:JSON.stringify(t).substring(0,100)+"..."})
@@ -1249,16 +1253,16 @@ if(p.push("session: "+(r?.sessionId||"NONE")),r?.sessionId){const t=await e.getS
1249
1253
  if(p.push("reqId: "+(t||"NONE")),t){const e=await o.proxyGet(`/api/projects/${n.projectId}/requests/${t}/test-runs`),r=e?.data||e||[],s=Array.isArray(r)?r.find((e=>e.worktreePath||e.worktree_path)):null
1250
1254
  p.push(`runs: ${Array.isArray(r)?r.length:0}, withWorktree: ${s?.id||"NONE"}, path: ${s?.worktreePath||s?.worktree_path||"NONE"}`),s&&(h=s.worktreePath||s.worktree_path,c.info("Using test worktree from request test run",{worktreePath:h,reqId:t}))}}}catch(e){c.warn("Failed to resolve test worktree",{error:(0,r.getErrorMessage)(e)})}if(!h)return a({success:!1,error:"no_worktree"}),{content:[{type:"text",text:`No test worktree found.\n\nDiagnostics:\n${p.length>0?p.map((e=>"\u2022 "+e)).join("\n"):"\u2022 No session identity found"}\n\nEnsure this session is linked to a test process and a test run has been created.`}],isError:!0}
1251
1255
  const m=[],{default:g}=await Promise.resolve().then(t.t.bind(t,6928,23)),{default:f}=await Promise.resolve().then(t.t.bind(t,9896,23)),{ALCHEMY_DIR_NAME:y}=await Promise.resolve().then(t.bind(t,4139)),w=g.join(h,y,"command-logs")
1252
- try{f.mkdirSync(w,{recursive:!0})}catch{}const{runTrackedShellCommand:b}=await Promise.resolve().then(t.bind(t,4600)),{sessionIdentity:I}=await Promise.resolve().then(t.bind(t,6688)),k=I.getIdentity()?.sessionId
1256
+ try{f.mkdirSync(w,{recursive:!0})}catch{}const{runTrackedShellCommand:b}=await Promise.resolve().then(t.bind(t,4600)),{formatCombinedOutput:I}=await Promise.resolve().then(t.bind(t,1816)),{sessionIdentity:k}=await Promise.resolve().then(t.bind(t,6688)),v=k.getIdentity()?.sessionId
1253
1257
  for(const t of l){c.info("Executing test command",{name:t.name,command:t.command,timeout:t.timeout,cwd:h})
1254
1258
  const r=`${t.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,n=g.join(w,r+".log")
1255
1259
  let o=null
1256
- try{o=f.createWriteStream(n,{flags:"w"}),o.write(`# ${(new Date).toISOString()} starting: ${t.command}\n# cwd: ${h}\n# timeout: ${t.timeout}s\n\n`)}catch(e){c.warn("Failed to open command log file",{logPath:n,error:e+""})}const s=await b(t.command,{cwd:h,timeoutMs:1e3*t.timeout,env:{...u.env,FORCE_COLOR:"0"},onChunk:o?(e,t)=>{o?.write(t)}:void 0},{id:r,name:t.name,sessionId:k,logPath:n})
1260
+ try{o=f.createWriteStream(n,{flags:"w"}),o.write(`# ${(new Date).toISOString()} starting: ${t.command}\n# cwd: ${h}\n# timeout: ${t.timeout}s\n\n`)}catch(e){c.warn("Failed to open command log file",{logPath:n,error:e+""})}const s=await b(t.command,{cwd:h,timeoutMs:1e3*t.timeout,env:{...u.env,FORCE_COLOR:"0"},onChunk:o?(e,t)=>{o?.write(t)}:void 0},{id:r,name:t.name,sessionId:v,logPath:n})
1257
1261
  o&&(o.write(`\n# exit: ${s.exitCode}${s.timedOut?" (timed out)":""}\n`),o.end())
1258
- const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim().slice(-4e3)
1259
- if(m.push({name:t.name,command:t.command,exitCode:s.exitCode,output:i,timedOut:s.timedOut}),"setup"===e.commandName&&0!==s.exitCode)break}const v=m.every((e=>0===e.exitCode)),C=m.map((e=>{const t=e.timedOut?"\u23f1 TIMEOUT":0===e.exitCode?"\u2705 PASS":`\u274c FAIL (exit ${e.exitCode})`
1260
- return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n\`\`\``})).join("\n\n")
1261
- return a({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${v?"PASSED":"FAILED"}\n\n${C}`}]}}catch(e){return a({success:!1,error:"execution_error"}),{content:[{type:"text",text:"Failed to execute test command: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleReportTestResults=async function(e,s,i,a,c,l){const d=c.startTimer("tool_execution_time")
1262
+ const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim(),{output:a,truncatedNote:l,logFooter:d}=I(i,{invocationId:r})
1263
+ if(m.push({name:t.name,command:t.command,exitCode:s.exitCode,output:a,timedOut:s.timedOut,invocationId:r,truncatedNote:l,logFooter:d}),"setup"===e.commandName&&0!==s.exitCode)break}const C=m.every((e=>0===e.exitCode)),_=m.map((e=>{const t=e.timedOut?"\u23f1 TIMEOUT":0===e.exitCode?"\u2705 PASS":`\u274c FAIL (exit ${e.exitCode})`
1264
+ return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n\`\`\`${e.truncatedNote}\n${e.logFooter}`})).join("\n\n")
1265
+ return a({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${C?"PASSED":"FAILED"}\n\n${_}`}]}}catch(e){return a({success:!1,error:"execution_error"}),{content:[{type:"text",text:"Failed to execute test command: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleReportTestResults=async function(e,s,i,a,c,l){const d=c.startTimer("tool_execution_time")
1262
1266
  try{const{sessionIdentity:u}=await Promise.resolve().then(t.bind(t,6688)),h=await u.getSessionRequestId(i,a,l?.sessionId)
1263
1267
  if(!h)return d({success:!1,error:"no_request_association"}),{content:[{type:"text",text:"Cannot report test results: this session is not associated with a request."}],isError:!0}
1264
1268
  const p=await i.proxyGet(`/api/projects/${s.projectId}/requests/${h}/test-runs`),m=p?.data||p||[],g=Array.isArray(m)?m:[],f=g.find((e=>"pending"===e.status||"running"===e.status))||g[0]
@@ -2458,51 +2462,48 @@ const a=await r.updateDocument(e.planId,i),c=a.localId||a.id
2458
2462
  return o.info("Plan appended",{planId:c,appendedLength:e.content.length}),{content:[{type:"text",text:`\u2705 **Plan Appended**\n\n**Plan ID:** ${c}\n**Appended:** ${e.content.length} characters\n**Total Length:** ${s.length} characters`}]}}catch(e){return o.error("Failed to append to plan",{error:(0,r.getErrorMessage)(e)}),{content:[{type:"text",text:"\u274c **Failed to append to plan:** "+(0,r.getErrorMessage)(e)}],isError:!0}}}
2459
2463
  const r=t(8824)},4425(e,exports,t){"use strict"
2460
2464
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
2461
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCommands=async function(e,t,r,u,h,p,m){const g=h.startTimer("tool_execution_time"),f=e.operation||"run"
2462
- try{const h=await r.proxyGet(`/api/projects/${t.projectId}/check-commands`),y=h?.data||h||[],w=y.filter((e=>e.isActive)),b=y.filter((e=>!e.isActive))
2463
- if("list"===f){if(0===y.length)return g({success:!0}),{content:[{type:"text",text:'No commands configured for this project yet.\n\nUse `commands({ operation: "register", commands: [{ name: "typecheck", command: "tsc --noEmit", description: "..." }] })` to add one, or `commands({ operation: "setup" })` to spawn the architect for an interview.'}],...(0,a.alchemyMeta)({kind:"list-commands"})}
2465
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCommands=async function(e,t,r,h,p,m,g){const f=p.startTimer("tool_execution_time"),y=e.operation||"run"
2466
+ try{const p=await r.proxyGet(`/api/projects/${t.projectId}/check-commands`),w=p?.data||p||[],b=w.filter((e=>e.isActive)),I=w.filter((e=>!e.isActive))
2467
+ if("list"===y){if(0===w.length)return f({success:!0}),{content:[{type:"text",text:'No commands configured for this project yet.\n\nUse `commands({ operation: "register", commands: [{ name: "typecheck", command: "tsc --noEmit", description: "..." }] })` to add one, or `commands({ operation: "setup" })` to spawn the architect for an interview.'}],...(0,c.alchemyMeta)({kind:"list-commands"})}
2464
2468
  const e=[]
2465
- return w.length>0&&(e.push(`## Available Commands (${w.length})\n`),e.push(...w.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""}${e.description?" \u2014 "+e.description:""}`)))),b.length>0&&(e.push(`\n## Pending Approval (${b.length})\n`),e.push(...b.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""} \u2014 \u23f3 waiting for user approval`)))),e.push('\nRun: `commands({ name: "name" })`\nRegister: `commands({ operation: "register", commands: [{ name, command, description, cwd? }, ...] })` \u2014 submits 1+ commands for approval. Single-item arrays work for one command; multiples bundle into one batch notification.\nRemove: `commands({ operation: "remove", name: "..." })`\nSetup (interactive): `commands({ operation: "setup" })` \u2014 spawns the command-architect for guided setup'),g({success:!0}),{content:[{type:"text",text:e.join("\n")}],...(0,a.alchemyMeta)({kind:"list-commands"})}}if("setup"===f){const t=m?.sessionId,n=t?await l.sessionIdentity.getSessionRequestId(r,u,t):void 0
2466
- try{const r=await fetch((0,d.getActionServerBaseUrl)()+"/api/setup-commands",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:n,parentSessionId:t,context:e.context}),signal:AbortSignal.timeout(15e3)})
2469
+ return b.length>0&&(e.push(`## Available Commands (${b.length})\n`),e.push(...b.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""}${e.description?" \u2014 "+e.description:""}`)))),I.length>0&&(e.push(`\n## Pending Approval (${I.length})\n`),e.push(...I.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""} \u2014 \u23f3 waiting for user approval`)))),e.push('\nRun: `commands({ name: "name" })`\nRegister: `commands({ operation: "register", commands: [{ name, command, description, cwd? }, ...] })` \u2014 submits 1+ commands for approval. Single-item arrays work for one command; multiples bundle into one batch notification.\nRemove: `commands({ operation: "remove", name: "..." })`\nSetup (interactive): `commands({ operation: "setup" })` \u2014 spawns the command-architect for guided setup'),f({success:!0}),{content:[{type:"text",text:e.join("\n")}],...(0,c.alchemyMeta)({kind:"list-commands"})}}if("setup"===y){const t=g?.sessionId,n=t?await d.sessionIdentity.getSessionRequestId(r,h,t):void 0
2470
+ try{const r=await fetch((0,u.getActionServerBaseUrl)()+"/api/setup-commands",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:n,parentSessionId:t,context:e.context}),signal:AbortSignal.timeout(15e3)})
2467
2471
  if(!r.ok){const e=await r.text().catch((()=>""))
2468
- return g({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:`commands setup failed: action server returned ${r.status}: ${e.slice(0,300)}`}],isError:!0}}const o=await r.json()
2469
- return o.success&&o.data?(g({success:!0}),{content:[{type:"text",text:`Commands setup session started.\nWorktree: ${o.data.worktreePath} (branch: ${o.data.branch})\n\nThe command-architect agent is now interviewing the user. You will receive a notification on your next turn when configuration completes \u2014 continue with other work in the meantime.`}],...(0,a.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:o.data.sessionId,agentType:"command-architect",description:"Commands setup"})}):(g({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(o.error||"unknown error")}],isError:!0})}catch(e){return g({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(0,c.getErrorMessage)(e)}],isError:!0}}}if("register"===f){if(!Array.isArray(e.commands)||0===e.commands.length)return g({success:!1,error:"missing_commands"}),{content:[{type:"text",text:"Error: `commands` array is required and must be non-empty for the register operation."}],isError:!0}
2470
- const n=function(e){return e?.sessionId||l.sessionIdentity.getIdentity()?.sessionId||null}(m)
2471
- if(!n)return g({success:!1,error:"no_session"}),{content:[{type:"text",text:"Error: no session context available to attribute approval notifications."}],isError:!0}
2472
- const o=new Set(y.map((e=>e.name.toLowerCase()))),s=[],i=[]
2472
+ return f({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:`commands setup failed: action server returned ${r.status}: ${e.slice(0,300)}`}],isError:!0}}const o=await r.json()
2473
+ return o.success&&o.data?(f({success:!0}),{content:[{type:"text",text:`Commands setup session started.\nWorktree: ${o.data.worktreePath} (branch: ${o.data.branch})\n\nThe command-architect agent is now interviewing the user. You will receive a notification on your next turn when configuration completes \u2014 continue with other work in the meantime.`}],...(0,c.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:o.data.sessionId,agentType:"command-architect",description:"Commands setup"})}):(f({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(o.error||"unknown error")}],isError:!0})}catch(e){return f({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(0,l.getErrorMessage)(e)}],isError:!0}}}if("register"===y){if(!Array.isArray(e.commands)||0===e.commands.length)return f({success:!1,error:"missing_commands"}),{content:[{type:"text",text:"Error: `commands` array is required and must be non-empty for the register operation."}],isError:!0}
2474
+ const n=function(e){return e?.sessionId||d.sessionIdentity.getIdentity()?.sessionId||null}(g)
2475
+ if(!n)return f({success:!1,error:"no_session"}),{content:[{type:"text",text:"Error: no session context available to attribute approval notifications."}],isError:!0}
2476
+ const o=new Set(w.map((e=>e.name.toLowerCase()))),s=[],i=[]
2473
2477
  for(const t of e.commands){const e=t.name?.trim(),r=t.command?.trim(),n=t.description?.trim()
2474
- e&&r&&n?o.has(e.toLowerCase())?i.push({name:e,reason:"already exists"}):(o.add(e.toLowerCase()),s.push({name:e,command:r,description:n,cwd:t.cwd?.trim(),timeout:t.timeout})):i.push({name:e||"(unnamed)",reason:"missing required fields (name, command, description)"})}const d=await Promise.all(s.map((async e=>{try{const n=await r.proxyPost(`/api/projects/${t.projectId}/check-commands`,{name:e.name,command:e.command,description:e.description,timeout:e.timeout||60,cwd:e.cwd||void 0,addedBySessionId:m?.sessionId,isActive:!1}),o=n?.data?.id||n?.id
2478
+ e&&r&&n?o.has(e.toLowerCase())?i.push({name:e,reason:"already exists"}):(o.add(e.toLowerCase()),s.push({name:e,command:r,description:n,cwd:t.cwd?.trim(),timeout:t.timeout})):i.push({name:e||"(unnamed)",reason:"missing required fields (name, command, description)"})}const a=await Promise.all(s.map((async e=>{try{const n=await r.proxyPost(`/api/projects/${t.projectId}/check-commands`,{name:e.name,command:e.command,description:e.description,timeout:e.timeout||60,cwd:e.cwd||void 0,addedBySessionId:g?.sessionId,isActive:!1}),o=n?.data?.id||n?.id
2475
2479
  if(!o)throw Error("command create returned no id")
2476
- return{ok:!0,proposal:e,commandId:o}}catch(t){return{ok:!1,proposal:e,error:(0,c.getErrorMessage)(t)}}}))),h=d.filter((e=>e.ok))
2477
- for(const e of d)e.ok||i.push({name:e.proposal.name,reason:e.error})
2480
+ return{ok:!0,proposal:e,commandId:o}}catch(t){return{ok:!1,proposal:e,error:(0,l.getErrorMessage)(t)}}}))),u=a.filter((e=>e.ok))
2481
+ for(const e of a)e.ok||i.push({name:e.proposal.name,reason:e.error})
2478
2482
  const p=[]
2479
- if(h.length>0)try{const e=r.getSessionMessageClient()
2483
+ if(u.length>0)try{const e=r.getSessionMessageClient()
2480
2484
  if(!e)throw Error("session message client unavailable")
2481
- await e.createQuestion({fromSessionId:n,questionType:"alternatives",question:`Command batch approval: ${h.length} command${1===h.length?"":"s"}`,options:[{id:"approve-all",label:"Approve all",description:`Activate all ${h.length} commands`},{id:"deny-all",label:"Deny all",description:"Delete all proposed commands"},{id:"select",label:"Select\u2026",description:"Pick which to approve individually"}],context:JSON.stringify({type:"command-approval-batch",commands:h.map((e=>({commandId:e.commandId,name:e.proposal.name,command:e.proposal.command,description:e.proposal.description,cwd:e.proposal.cwd})))}),category:"action-required",priority:"high"})
2482
- for(const e of h)p.push(e.proposal.name)}catch(e){await Promise.allSettled(h.map((e=>r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${e.commandId}`).catch((t=>u.warn("Rollback delete after batch question failure",{commandId:e.commandId,error:(0,c.getErrorMessage)(t)}))))))
2483
- for(const t of h)i.push({name:t.proposal.name,reason:(0,c.getErrorMessage)(e)})}u.info("Register submitted commands for approval",{total:e.commands.length,submitted:p.length,skipped:i.length}),g({success:!0})
2484
- const f=[`## Submitted ${p.length} command${1===p.length?"":"s"} for approval${i.length?`, ${i.length} skipped`:""}`]
2485
- return p.length>0&&(f.push("","### Pending approval"),f.push(...p.map((e=>`- **${e}**`))),f.push("","The user will see one approval notification per command. Approved commands become runnable; denied ones are removed.")),i.length>0&&(f.push("",`### Skipped (${i.length})`),f.push(...i.map((e=>`- **${e.name}** \u2014 ${e.reason}`)))),f.push("",'Use `commands({ operation: "list" })` to see status.'),{content:[{type:"text",text:f.join("\n")}],...(0,a.alchemyMeta)({kind:"register-commands"})}}if("remove"===f){if(!e.name?.trim())return g({success:!1,error:"missing_name"}),{content:[{type:"text",text:"Error: `name` is required when removing a command."}],isError:!0}
2486
- const n=y.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
2487
- if(!n)return g({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.`}],isError:!0}
2488
- try{return await r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${n.id}`),u.info("Removed command",{name:n.name,id:n.id}),g({success:!0}),{content:[{type:"text",text:`Removed command **${n.name}**.`}]}}catch(e){return g({success:!1,error:"remove_failed"}),{content:[{type:"text",text:"Failed to remove command: "+(0,c.getErrorMessage)(e)}],isError:!0}}}if(!e.name){const e=w.map((e=>e.name)).join(", ")
2489
- return g({success:!1,error:"missing_name"}),{content:[{type:"text",text:"Error: `name` is required when running a command.\n\nAvailable commands: "+(e||"(none configured)")}],isError:!0}}const I=w.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
2490
- if(!I){const t=w.map((e=>e.name)).join(", ")
2491
- return g({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.\n\nAvailable commands: ${t||"(none configured)"}`}],isError:!0}}let k=e.args?`${I.command} ${e.args}`:I.command
2492
- e.grep&&(k=`${k} 2>&1 | grep -i ${JSON.stringify(e.grep)}`)
2493
- const v=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath||t.basePath,C=I.cwd?o.default.resolve(v,I.cwd):v,_=o.default.join(v,i.ALCHEMY_DIR_NAME,"command-logs"),E=`${I.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,S=o.default.join(_,E+".log")
2494
- let q=null
2495
- try{s.default.mkdirSync(_,{recursive:!0}),q=s.default.createWriteStream(S,{flags:"w"}),q.write(`# ${(new Date).toISOString()} starting: ${k}\n# cwd: ${C}\n# timeout: ${I.timeout||60}s\n\n`)}catch(e){u.warn("Failed to open command log file",{logPath:S,error:(0,c.getErrorMessage)(e)})}u.info("Running command",{name:I.name,command:k,cwd:C,configuredCwd:I.cwd,logPath:S})
2496
- const T=await(0,n.runTrackedShellCommand)(k,{cwd:C,timeoutMs:1e3*(I.timeout||60),env:{...process.env,FORCE_COLOR:"0"},onChunk:q?(e,t)=>{q?.write(t)}:void 0},{id:E,name:I.name,sessionId:m?.sessionId,logPath:S})
2497
- q&&(q.write(`\n# exit: ${T.exitCode}${T.timedOut?" (timed out)":""}\n`),q.end())
2498
- let x=T.stdout,P=T.stderr
2499
- const R=T.exitCode,M=T.timedOut,$=8e3,j=4e3
2500
- x.length>$&&(x="\u2026"+x.slice(-$)),P.length>j&&(P="\u2026"+P.slice(-j))
2501
- const O=M?"TIMEOUT":null,D=[x,P].filter(Boolean).join("\n")||"(no output)"
2502
- let A
2503
- if(p)try{const e=await p.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:I.name,command:k,exitCode:R,timedOut:M,status:M?"timeout":0===R?"success":"failed",timestamp:(new Date).toISOString()}})
2504
- A=e?.id}catch(e){u.debug("Failed to create tool record for command run",{error:(0,c.getErrorMessage)(e)})}return g({success:!0}),{content:[{type:"text",text:`## ${I.name}${O?` \u2014 ${O} (killed after ${I.timeout}s)`:""}\n\`\`\`\n$ ${k}\n${D}\n\`\`\`\n\nExit code: ${R}\nFull log: ${S}`}],...(0,a.alchemyMeta)({kind:"run-command",toolRecordId:A})}}catch(e){return g({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to run command: "+(0,c.getErrorMessage)(e)}],isError:!0}}}
2505
- const n=t(4600),o=r(t(6928)),s=r(t(9896)),i=t(4139),a=t(2272),c=t(8824),l=t(6688),d=t(9414)},4466(e,exports,t){"use strict"
2485
+ await e.createQuestion({fromSessionId:n,questionType:"alternatives",question:`Command batch approval: ${u.length} command${1===u.length?"":"s"}`,options:[{id:"approve-all",label:"Approve all",description:`Activate all ${u.length} commands`},{id:"deny-all",label:"Deny all",description:"Delete all proposed commands"},{id:"select",label:"Select\u2026",description:"Pick which to approve individually"}],context:JSON.stringify({type:"command-approval-batch",commands:u.map((e=>({commandId:e.commandId,name:e.proposal.name,command:e.proposal.command,description:e.proposal.description,cwd:e.proposal.cwd})))}),category:"action-required",priority:"high"})
2486
+ for(const e of u)p.push(e.proposal.name)}catch(e){await Promise.allSettled(u.map((e=>r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${e.commandId}`).catch((t=>h.warn("Rollback delete after batch question failure",{commandId:e.commandId,error:(0,l.getErrorMessage)(t)}))))))
2487
+ for(const t of u)i.push({name:t.proposal.name,reason:(0,l.getErrorMessage)(e)})}h.info("Register submitted commands for approval",{total:e.commands.length,submitted:p.length,skipped:i.length}),f({success:!0})
2488
+ const m=[`## Submitted ${p.length} command${1===p.length?"":"s"} for approval${i.length?`, ${i.length} skipped`:""}`]
2489
+ return p.length>0&&(m.push("","### Pending approval"),m.push(...p.map((e=>`- **${e}**`))),m.push("","The user will see one approval notification per command. Approved commands become runnable; denied ones are removed.")),i.length>0&&(m.push("",`### Skipped (${i.length})`),m.push(...i.map((e=>`- **${e.name}** \u2014 ${e.reason}`)))),m.push("",'Use `commands({ operation: "list" })` to see status.'),{content:[{type:"text",text:m.join("\n")}],...(0,c.alchemyMeta)({kind:"register-commands"})}}if("remove"===y){if(!e.name?.trim())return f({success:!1,error:"missing_name"}),{content:[{type:"text",text:"Error: `name` is required when removing a command."}],isError:!0}
2490
+ const n=w.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
2491
+ if(!n)return f({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.`}],isError:!0}
2492
+ try{return await r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${n.id}`),h.info("Removed command",{name:n.name,id:n.id}),f({success:!0}),{content:[{type:"text",text:`Removed command **${n.name}**.`}]}}catch(e){return f({success:!1,error:"remove_failed"}),{content:[{type:"text",text:"Failed to remove command: "+(0,l.getErrorMessage)(e)}],isError:!0}}}if(!e.name){const e=b.map((e=>e.name)).join(", ")
2493
+ return f({success:!1,error:"missing_name"}),{content:[{type:"text",text:"Error: `name` is required when running a command.\n\nAvailable commands: "+(e||"(none configured)")}],isError:!0}}const k=b.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
2494
+ if(!k){const t=b.map((e=>e.name)).join(", ")
2495
+ return f({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.\n\nAvailable commands: ${t||"(none configured)"}`}],isError:!0}}let v=e.args?`${k.command} ${e.args}`:k.command
2496
+ e.grep&&(v=`${v} 2>&1 | grep -i ${JSON.stringify(e.grep)}`)
2497
+ const C=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath||t.basePath,_=k.cwd?s.default.resolve(C,k.cwd):C,E=s.default.join(C,a.ALCHEMY_DIR_NAME,"command-logs"),S=`${k.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,q=s.default.join(E,S+".log")
2498
+ let T=null
2499
+ try{i.default.mkdirSync(E,{recursive:!0}),T=i.default.createWriteStream(q,{flags:"w"}),T.write(`# ${(new Date).toISOString()} starting: ${v}\n# cwd: ${_}\n# timeout: ${k.timeout||60}s\n\n`)}catch(e){h.warn("Failed to open command log file",{logPath:q,error:(0,l.getErrorMessage)(e)})}h.info("Running command",{name:k.name,command:v,cwd:_,configuredCwd:k.cwd,logPath:q})
2500
+ const x=await(0,n.runTrackedShellCommand)(v,{cwd:_,timeoutMs:1e3*(k.timeout||60),env:{...process.env,FORCE_COLOR:"0"},onChunk:T?(e,t)=>{T?.write(t)}:void 0},{id:S,name:k.name,sessionId:g?.sessionId,logPath:q})
2501
+ T&&(T.write(`\n# exit: ${x.exitCode}${x.timedOut?" (timed out)":""}\n`),T.end())
2502
+ const{stdout:P,stderr:R,exitCode:M,timedOut:$}=x,{output:j,truncatedNote:O,logFooter:D}=(0,o.formatCommandOutput)(P,R,{invocationId:S}),A=$?"TIMEOUT":null
2503
+ let F
2504
+ if(m)try{const e=await m.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:k.name,command:v,exitCode:M,timedOut:$,status:$?"timeout":0===M?"success":"failed",timestamp:(new Date).toISOString()}})
2505
+ F=e?.id}catch(e){h.debug("Failed to create tool record for command run",{error:(0,l.getErrorMessage)(e)})}return f({success:!0}),{content:[{type:"text",text:`## ${k.name}${A?` \u2014 ${A} (killed after ${k.timeout}s)`:""}\n\`\`\`\n$ ${v}\n${j}\n\`\`\`${O}\n\nExit code: ${M}\n${D} (pass to read_files or search)`}],...(0,c.alchemyMeta)({kind:"run-command",toolRecordId:F})}}catch(e){return f({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to run command: "+(0,l.getErrorMessage)(e)}],isError:!0}}}
2506
+ const n=t(4600),o=t(1816),s=r(t(6928)),i=r(t(9896)),a=t(4139),c=t(2272),l=t(8824),d=t(6688),u=t(9414)},4466(e,exports,t){"use strict"
2506
2507
  function r(e,t){return`${e}#${t.toString().padStart(2,"0")}`}async function n(e,t,n,s){if(exports.TASK_REF_PATTERN.test(e))return e
2507
2508
  const i=e.match(exports.TASK_REF_SHORT_PATTERN)
2508
2509
  if(!i)return e
@@ -2543,7 +2544,7 @@ if("interactive"===n)return"interactive"}catch(e){t.warn("Failed to read project
2543
2544
  if("ask"===e||"interactive"===e){h.info("Returning planner choice pending",{planningMode:e})
2544
2545
  try{const e=u?.sessionId||o.sessionIdentity.getIdentity()?.sessionId,t=d?.getSessionMessageClient()
2545
2546
  if(t&&e){const r=await o.sessionIdentity.getParentSessionId(e,d,h),n=c.description||c.prompt
2546
- await t.createQuestion({fromSessionId:e,toSessionId:r||void 0,questionType:"alternatives",question:"Planning requested: "+n,options:[{id:"auto",label:"Auto Plan",description:"Spawn an autonomous planner agent"},{id:"manual",label:"Manual Plan",description:"Open the interactive plan editor"},{id:"cancel",label:"Skip",description:"Dismiss this planning request"}],context:c.prompt,category:"action-required",priority:"high"}),h.info("planner-request: notification created",{fromSessionId:e})}else h.warn("planner-request: skipping notification",{hasClient:!!t,hasCallerSessionId:!!e})}catch(e){h.warn("planner-request: failed to create notification",{error:(0,n.getErrorMessage)(e)})}f={content:[{type:"text",text:"Planning request submitted. The user will choose the planning approach. This is non-blocking \u2014 continue with your other work."}],...(0,i.alchemyMeta)({pendingAction:{kind:"planner-choice",payload:{prompt:c.prompt,description:c.description}}})}
2547
+ await t.createQuestion({fromSessionId:e,toSessionId:r||void 0,questionType:"alternatives",question:"Planning requested: "+n,options:[{id:"auto",label:"Auto Plan",description:"Spawn an autonomous planner agent"},{id:"manual",label:"Manual Plan",description:"Open the interactive plan editor"},{id:"cancel",label:"Skip",description:"Dismiss this planning request"}],context:{type:"planner-request",prompt:c.prompt,description:c.description},category:"action-required",priority:"high"}),h.info("planner-request: notification created",{fromSessionId:e})}else h.warn("planner-request: skipping notification",{hasClient:!!t,hasCallerSessionId:!!e})}catch(e){h.warn("planner-request: failed to create notification",{error:(0,n.getErrorMessage)(e)})}f={content:[{type:"text",text:"Planning request submitted. The user will choose the planning approach. This is non-blocking \u2014 continue with your other work."}],...(0,i.alchemyMeta)({pendingAction:{kind:"planner-choice",payload:{prompt:c.prompt,description:c.description}}})}
2547
2548
  break}const r="auto-planner"
2548
2549
  if(h.info("Spawning planner",{planningMode:e,agent:r}),f=await(0,s.handleSpawn)({agent:r,prompt:c.prompt,title:c.description},t,h,d,u),!f.isError&&f.content.length>0){const t=f.content[0].text
2549
2550
  f.content[0].text=`Planning mode: ${e} (agent: ${r}). Planning is running in the background \u2014 continue with your other work.\n${t}`}break}case"check-status":f=await(0,s.handleCheckStatus)(g,t,h,d)
@@ -3547,7 +3548,7 @@ if(!s)return{content:[{type:"text",text:"\u274c No project context available. Pl
3547
3548
  const i=e.name.replace(/\.md$/i,""),a=`${d.filenamePrefix}${i}.md`,l=o.getDocumentClient(),p=await l.listDocuments({type:d.docType,filename:a,limit:1,offset:0})
3548
3549
  if(p&&p.length>0)return{content:[{type:"text",text:`\u274c A ${d.label} with filename "${a}" already exists (${p[0].localId||p[0].id}). Choose a different name.`}],isError:!0}
3549
3550
  const m=u?{criteria:e.criteria||"",status:n.QUEST_STATUS_OPEN,tickIntervalSeconds:e.tickIntervalSeconds??n.QUEST_DEFAULTS.tickIntervalSeconds,maxInFlight:e.maxInFlight??n.QUEST_DEFAULTS.maxInFlight,maxPerDay:e.maxPerDay??n.QUEST_DEFAULTS.maxPerDay,minIterations:e.minIterations??n.QUEST_DEFAULTS.minIterations}:void 0,g=await l.createDocument(void 0,{filename:a,content:e.content,type:d.docType,...m&&{metadata:m},...c&&{cliOrigin:c}}),f=g.localId||g.id,y=g.id
3550
- let w=!1
3551
+ if(c?.sessionId)try{await o.proxyPost(`/api/projects/${s}/session-plan-associations`,{opencodeSessionId:c.sessionId,documentId:y})}catch(e){h.warn("Failed to create session-plan association",{error:(0,r.getErrorMessage)(e)})}let w=!1
3551
3552
  if(d.canBeActivePlan)try{const e=o.getProjectClient(),t=await e.getProject(s)
3552
3553
  t&&!t.activePlanId&&(await e.updateProject(s,{activePlanId:y}),w=!0,h.info("Auto-set as active plan",{planId:f,projectId:s}))}catch(e){h.warn("Failed to auto-set active plan",{error:(0,r.getErrorMessage)(e)})}return h.info(""+d.headingCreated,{planId:f,filename:a}),{content:[{type:"text",text:`\u2705 **${d.headingCreated}**\n\n**${d.idLabel}:** ${f}\n**Filename:** ${a}\n**Created:** `+new Date(g.createdAt).toLocaleString()+(w?"\n\n\u{1f4cc} _Automatically set as the active project plan._":"")}]}}catch(e){return h.error("Failed to "+d.errorVerb,{error:(0,r.getErrorMessage)(e)}),{content:[{type:"text",text:`\u274c **Failed to ${d.errorVerb}:** ${(0,r.getErrorMessage)(e)}`}],isError:!0}}}
3553
3554
  const r=t(8824),n=t(3416)},6688(e,exports,t){"use strict"
@@ -4429,21 +4430,28 @@ if(r.startsWith("R")){const e=t[1],n=t[2],o=_.get(n)||{additions:0,deletions:0,b
4429
4430
  Number.isFinite(s)&&(i.similarity=s),o.binary&&(i.binary=!0),E.push(i)}else{const e=t[1],n=_.get(e)||{additions:0,deletions:0,binary:!1},o={path:e,status:"A"===r?"added":"D"===r?"deleted":"modified",additions:n.additions,deletions:n.deletions}
4430
4431
  n.binary&&(o.binary=!0),E.push(o)}}return E.sort(((e,t)=>e.path.localeCompare(t.path))),u({success:!0}),l.incrementCounter("request_diff_total",1,{format:"summary"}),r({baseSha:b,headSha:I,branch:y?null:w,files:E})}catch(e){return d.error("request_diff failed",{error:e instanceof Error?e.stack:e+""}),u({success:!1}),l.incrementCounter("request_diff_errors",1),n("request_diff failed: "+(0,i.getErrorMessage)(e))}}
4431
4432
  const o=t(3807),s=t(2272),i=t(8824),a=5e5},8291(e,exports,t){"use strict"
4432
- var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
4433
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){let r=e.replace(/\\/g,"/")
4434
- const s=function(e){return e.workspaceManager?e.workspaceManager.getCurrentContext(e.currentSessionId).basePath:e.basePath}(t)
4435
- if(n.default.isAbsolute(r)){const e=n.default.normalize(r)
4436
- if(e.startsWith(s))r=n.default.relative(s,e),r=r.replace(/\\/g,"/")
4437
- else{const i=t.basePath
4438
- if(!i||!e.startsWith(i)){const r=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4439
- throw(0,o.createValidationError)(`File path is outside the allowed directory\n Requested path (absolute): ${e}\n Allowed base (current workspace): ${s}\n Allowed base (main repo): ${i||"N/A"}\n Workspace mode: ${r}\n Current request: ${n}\n Note: Use relative paths (e.g., 'src/file.txt') instead of absolute paths`)}r=n.default.relative(i,e),r=r.replace(/\\/g,"/")}}if(function(e){const t=e.replace(/\\/g,"/").replace(/^\//g,"")
4440
- for(const e of a)if(t===e||t.startsWith(e+"/"))return!0
4441
- for(const e of c)if(t===e||t.endsWith("/"+e))return!0
4442
- return!1}(r))throw(0,o.createValidationError)(`Access to '${r}' is not allowed - this directory contains system files`)
4443
- const i=n.default.resolve(s,r)
4444
- if(!i.startsWith(s)){const e=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4445
- throw(0,o.createValidationError)(`File path is outside the allowed directory\n Requested path: ${r}\n Resolved to: ${i}\n Allowed base: ${s}\n Workspace mode: ${e}\n Current request: `+n)}return{normalizedPath:r,fullPath:i}},exports.validateTaskIdFormat=function(e,t,r,n){e&&(0,i.isTaskRef)(e)||(t.warn("Invalid task ID format provided",{taskId:e}),r.incrementCounter(n+"_invalid_task_format",1))}
4446
- const n=r(t(6928)),o=t(9860),s=t(4139),i=t(4466),a=[s.ALCHEMY_DIR_NAME,".opencode",".git"],c=[".mcp-server.lock"]},8309(e,exports,t){"use strict"
4433
+ function r(e){return e.workspaceManager?e.workspaceManager.getCurrentContext(e.currentSessionId).basePath:e.basePath}var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
4434
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){const n=function(e,t){if(!e.startsWith("log:"))return null
4435
+ const r=e.slice(4).replace(/^\/+/,""),n=r?`${i.ALCHEMY_DIR_NAME}/command-logs/${r}.log`:i.ALCHEMY_DIR_NAME+"/command-logs"
4436
+ if(r&&(!/^[a-zA-Z0-9._-]+$/.test(r)||r.includes("..")))throw(0,s.createValidationError)(`Invalid log id in '${e}'. Expected alphanumeric characters, dots, underscores, hyphens.`)
4437
+ const a=o.default.resolve(t,n)
4438
+ if(!a.startsWith(t+o.default.sep))throw(0,s.createValidationError)("Log path resolves outside the workspace: "+e)
4439
+ return{normalizedPath:n,fullPath:a}}(e,r(t))
4440
+ if(n)return n
4441
+ let a=e.replace(/\\/g,"/")
4442
+ const d=r(t)
4443
+ if(o.default.isAbsolute(a)){const e=o.default.normalize(a)
4444
+ if(e.startsWith(d))a=o.default.relative(d,e),a=a.replace(/\\/g,"/")
4445
+ else{const r=t.basePath
4446
+ if(!r||!e.startsWith(r)){const n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",o=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4447
+ throw(0,s.createValidationError)(`File path is outside the allowed directory\n Requested path (absolute): ${e}\n Allowed base (current workspace): ${d}\n Allowed base (main repo): ${r||"N/A"}\n Workspace mode: ${n}\n Current request: ${o}\n Note: Use relative paths (e.g., 'src/file.txt') instead of absolute paths`)}a=o.default.relative(r,e),a=a.replace(/\\/g,"/")}}if(function(e){const t=e.replace(/\\/g,"/").replace(/^\//g,"")
4448
+ for(const e of c)if(t===e||t.startsWith(e+"/"))return!0
4449
+ for(const e of l)if(t===e||t.endsWith("/"+e))return!0
4450
+ return!1}(a))throw(0,s.createValidationError)(`Access to '${a}' is not allowed - this directory contains system files`)
4451
+ const u=o.default.resolve(d,a)
4452
+ if(!u.startsWith(d)){const e=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",r=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4453
+ throw(0,s.createValidationError)(`File path is outside the allowed directory\n Requested path: ${a}\n Resolved to: ${u}\n Allowed base: ${d}\n Workspace mode: ${e}\n Current request: `+r)}return{normalizedPath:a,fullPath:u}},exports.validateTaskIdFormat=function(e,t,r,n){e&&(0,a.isTaskRef)(e)||(t.warn("Invalid task ID format provided",{taskId:e}),r.incrementCounter(n+"_invalid_task_format",1))}
4454
+ const o=n(t(6928)),s=t(9860),i=t(4139),a=t(4466),c=[i.ALCHEMY_DIR_NAME,".opencode",".git"],l=[".mcp-server.lock"]},8309(e,exports,t){"use strict"
4447
4455
  var r,n=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
4448
4456
  var o=Object.getOwnPropertyDescriptor(t,r)
4449
4457
  o&&!("get"in o?!t.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get(){return t[r]}}),Object.defineProperty(e,n,o)}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),s=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemylab",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "private": false,
5
5
  "description": "AlchemyLab - agentic coding IDEm",
6
6
  "author": "Testfire Software",