alchemylab 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +43 -40
  2. package/package.json +9 -5
package/dist/cli.js CHANGED
@@ -392,14 +392,15 @@ exports.defaultLogger=s.default
392
392
  const n=t(6540)
393
393
  Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=s.default},3785(module){module.exports=require("readline")},3807(module){module.exports=require("simple-git")},3962(e,exports,t){function r(){if(n)return n
394
394
  const e=process.argv[1]||""
395
- let t=""
396
- if(e.includes("node_modules")){const r=e.lastIndexOf("/dist/")
397
- r>0&&(t=e.substring(0,r+5))}else{const r=e.lastIndexOf("/dist-dev/")
398
- if(r>0)t=e.substring(0,r+9)
399
- else{const r=e.lastIndexOf("/dist/")
400
- r>0&&(t=e.substring(0,r+5))}}if(!t){const e=process.cwd()
401
- t=e.includes("alchemy-local")||e.includes("alchemy")?s.default.join(e,"dist-dev"):s.default.join(e,"dist")}const r=t.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1,o=r?s.default.join(t,"mcp-direct.js"):t,i=process.env.REMOTE_SERVER_URL||"https://api.alchemylabcode.com"
402
- return n={installPath:t,scriptPath:o,nodePath:process.execPath,isDevMode:r,serverName:"alchemy",remoteServerUrl:i},n}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
395
+ let r=e
396
+ try{r=t(9896).realpathSync(e)}catch{}let o=""
397
+ if(r.includes("node_modules")){const e=r.lastIndexOf("/dist/")
398
+ e>0&&(o=r.substring(0,e+5))}else{const e=r.lastIndexOf("/dist-dev/")
399
+ if(e>0)o=r.substring(0,e+9)
400
+ else{const e=r.lastIndexOf("/dist/")
401
+ e>0&&(o=r.substring(0,e+5))}}if(!o){const e=process.cwd()
402
+ o=e.includes("alchemy-local")||e.includes("alchemy")?s.default.join(e,"dist-dev"):s.default.join(e,"dist")}const i=o.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1,a=s.default.join(o,"mcp-direct.js"),c=process.env.REMOTE_SERVER_URL||"https://api.alchemylabcode.com"
403
+ return n={installPath:o,scriptPath:a,nodePath:process.execPath,isDevMode:i,serverName:"alchemy",remoteServerUrl:c},n}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
403
404
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.getInstallContext=r,exports.clearInstallContextCache=function(){n=null},exports.isDevMode=function(e){return e?e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1:r().isDevMode},exports.getServerName=function(e){return e??r().isDevMode,"alchemy"}
404
405
  const s=o(t(6928))
405
406
  let n=null},4139(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.ALCHEMY_DIR_NAME=exports.TOKEN_DIR_NAME=void 0,exports.TOKEN_DIR_NAME="alchemy-key",exports.ALCHEMY_DIR_NAME=".alchemy_lab"},4192(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.BaseApiClient=void 0
@@ -530,29 +531,29 @@ if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId
530
531
  m.info("Starting run-tests action",{requestId:u,testProcessIds:g})
531
532
  try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
532
533
  if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
533
- const p=d.data,E=p.branchName||"requests/"+u
534
- let I
535
- if(g?.length)I=(await Promise.all(g.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
534
+ const p=d.data,I=p.branchName||"requests/"+u
535
+ let E
536
+ if(g?.length)E=(await Promise.all(g.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
536
537
  else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
537
- I=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===I.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
538
+ E=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===E.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
538
539
  const b=(0,s.join)(w,c,l),k=(0,s.join)(b,u),j=(0,n.simpleGit)(w)
539
540
  if((0,o.existsSync)(k))try{const e=(0,n.simpleGit)(k)
540
- await e.raw(["reset","--hard",E]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:k,branchName:E})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
541
+ await e.raw(["reset","--hard",I]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:k,branchName:I})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
541
542
  try{await j.raw(["worktree","remove","--force",k])}catch{}}if(!(0,o.existsSync)(k)){(0,o.mkdirSync)(b,{recursive:!0})
542
- try{if(!(await j.branch()).all.includes(E))return{success:!1,message:`Branch ${E} not found`,error:"Branch not found"}
543
- await j.raw(["worktree","add","--detach",k,E]),m.info("Created test worktree (detached)",{worktreePath:k,branchName:E})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
543
+ try{if(!(await j.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
544
+ await j.raw(["worktree","add","--detach",k,I]),m.info("Created test worktree (detached)",{worktreePath:k,branchName:I})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
544
545
  m.info("Test worktree already exists, using as-is",{worktreePath:k})}}const{execSync:T}=await Promise.resolve().then(t.t.bind(t,5317,23))
545
- for(const e of I){const t=e.config?.setup||[]
546
+ for(const e of E){const t=e.config?.setup||[]
546
547
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
547
548
  for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),T(r,{cwd:k,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}const S=[],P=[]
548
- for(const process of I){let t
549
+ for(const process of E){let t
549
550
  const o=e.testRunIds?.[S.length]
550
551
  try{o?(await y.patch(`/api/projects/${v}/test-runs/${o}`,{worktreePath:k}),t=(await y.get(`/api/projects/${v}/test-runs/${o}`)).data,S.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:k,parentSessionId:h})).data,S.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:o,error:(0,i.getErrorMessage)(e)})
551
552
  continue}const s=r(p,process,0,v,f),n=f?"tester":"auto-tester"
552
553
  try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:s,cwd:k,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:h,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
553
554
  if(!e.success)throw Error(e.error||"Spawn failed")
554
555
  P.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:k,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
555
- try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===P.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${P.length} test run(s) for request ${u}`,data:{testRunIds:S,agentsSpawned:P.length,processCount:I.length,worktreePath:k}}}catch(e){const t=(0,i.getErrorMessage)(e)
556
+ try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===P.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${P.length} test run(s) for request ${u}`,data:{testRunIds:S,agentsSpawned:P.length,processCount:E.length,worktreePath:k}}}catch(e){const t=(0,i.getErrorMessage)(e)
556
557
  return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
557
558
  const o=t(9896),s=t(6928),n=t(3807),i=t(8824),a=t(8148),c=".alchemy_lab",l="test-worktrees"},6636(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRevertRequestAction=async function(e,t){const{requestId:s,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
558
559
  if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
@@ -640,9 +641,9 @@ let v
640
641
  if(n.ALCHEMY_SESSION_TAG=`mcp-${m}-${y}`,r(c,"Spawn env setup",{agent:t,initialBranch:h||"none",hasParentServer:!!l,sessionId:u||"new",attachMode:!!p}),v=p&&u&&o?["run","--attach",p,"--session",u,"--agent",t,o]:u&&o?["run","--session",u,"--agent",t,o]:o?["run","--agent",t,o]:["run","--agent",t],g){const e=v.findIndex((e=>e===o))
641
642
  e>0?v.splice(e,0,"--model",g):v.push("--model",g)}r(c,"Spawning opencode",{args:v.map((e=>e===o?`[prompt:${e.length}chars]`:e)),model:g||"not specified",promptLength:o?.length,promptPreview:o?.slice(0,100),cwd:c,hasSessionId:!!u,hasParentServerUrl:!!l})
642
643
  const w=(0,a.join)(c||process.cwd(),".alchemy_lab"),C=(0,a.join)(w,"spawn-stderr.log")
643
- let E
644
- try{E=(0,i.openSync)(C,"a")}catch{}const I=(0,s.spawn)("opencode",v,{detached:!0,stdio:["ignore","ignore",E??"ignore"],cwd:c||process.cwd(),env:n})
645
- return I.unref(),f&&f(I.pid),{success:!0,pid:I.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
644
+ let I
645
+ try{I=(0,i.openSync)(C,"a")}catch{}const E=(0,s.spawn)("opencode",v,{detached:!0,stdio:["ignore","ignore",I??"ignore"],cwd:c||process.cwd(),env:n})
646
+ return E.unref(),f&&f(E.pid),{success:!0,pid:E.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
646
647
  return m&&e instanceof Error&&m(e),{success:!1,error:t}}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=o,exports.spawnJournalSubagent=function(e,t,r,s){return o({agent:"journal",prompt:`Generate a journal entry for request ${e}.\n\nSteps:\n1. Use ${t}__generate_request_journal tool with requestId: ${e}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${e}.md using ${t}__document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,cwd:r,parentServerUrl:s})},exports.spawnWorkSubagent=function(e,t,r){return o({agent:"work",prompt:e,cwd:t,parentServerUrl:r})}
647
648
  const s=t(5317),n=t(8824),i=t(9896),a=t(6928)},8148(e,exports,t){function r(e,t){try{const t=(0,s.join)(e,a,"opencode.json"),r=(0,o.readFileSync)(t,"utf8"),n=JSON.parse(r)
648
649
  if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,i.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=r,exports.spawnActionAgent=async function(e){const{agent:t,prompt:o,cwd:s,projectPath:a,title:c,requestId:l,parentSessionId:d,allowParallel:u,apiClient:g,logger:h,trackChildPid:p}=e,f=r(a,h)
@@ -708,25 +709,25 @@ return` ${o.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${s}`}: ${r}`})).join("\n")
708
709
  let i=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${r}"\n`
709
710
  if(e.color&&(i+=`color: "${e.color}"\n`),i+=`tools:\n${n}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){i+="permission:\n"
710
711
  for(const[t,r]of Object.entries(e.opencodePermission))if("object"==typeof r&&null!==r){i+=` ${t}:\n`
711
- for(const[e,t]of Object.entries(r))i+=` ${e}: ${t}\n`}else i+=` ${t}: ${r}\n`}return void 0!==e.temperature&&(i+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(i+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(i+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),i+="---\n\n",s&&(i+=s.trim()+"\n\n"),e.prompt&&(i+=e.prompt),e.promptAddition&&(i+="\n\n"+e.promptAddition),i}async configureOpenCode(e,s,n="node",i,a){try{const r=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=l?c.join(e,"mcp-direct.js"):e
712
+ for(const[e,t]of Object.entries(r))i+=` ${e}: ${t}\n`}else i+=` ${t}: ${r}\n`}return void 0!==e.temperature&&(i+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(i+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(i+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),i+="---\n\n",s&&(i+=s.trim()+"\n\n"),e.prompt&&(i+=e.prompt),e.promptAddition&&(i+="\n\n"+e.promptAddition),i}async configureOpenCode(e,s,n="node",i,a){try{const r=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=c.join(e,"mcp-direct.js")
712
713
  this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:s})
713
714
  const g=c.join(s,"opencode.json")
714
715
  let h=null,p=null,f=null,m=null,y=!1,v=!1
715
716
  const w=c.join(s,".alchemy_lab","config.json")
716
717
  if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
717
718
  t?.openCodeTools?(h=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:h})):v=!0,t?.openCodeModel&&(p=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),v=!0}try{const e=await r.readFile(g,"utf-8"),t=JSON.parse(e)
718
- t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const C=i?.openCodeTools||h||f||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},E={tools:C},I=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
719
- i?.openCodeModel?E.model=i.openCodeModel:p?E.model=p:m&&(E.model=m)
719
+ t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const C=i?.openCodeTools||h||f||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},I={tools:C},E=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
720
+ i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:m&&(I.model=m)
720
721
  const b=c.join(s,".opencode","agent")
721
722
  await r.mkdir(b,{recursive:!0})
722
- const k=I||"anthropic/claude-opus-4-5-20251101"
723
+ const k=E||"anthropic/claude-opus-4-5-20251101"
723
724
  let j,T=null,S=null
724
725
  if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),S=await this.fetchProjectFromRemote(i.projectId),j=S?.metadata?.globalSystemPrompt,j&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:j.length}),T=await this.fetchAgentsFromRemote(i.projectId),T&&T.length>0){this.logger.info("Using agents from remote database",{count:T.length})
725
726
  for(const e of T){if(!e.enabled)continue
726
727
  const t=this.generateAgentMarkdown(e,d,k,j),o=c.join(b,e.name+".md")
727
728
  await r.writeFile(o,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:o,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
728
729
  else this.logger.warn("No projectId provided - agents must be configured via database")
729
- const P={$schema:"https://opencode.ai/config.json",model:I,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0}}}
730
+ const P={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0}}}
730
731
  if(l&&process.env.REMOTE_SERVER_URL&&(P.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),T&&T.length>0){P.agent={}
731
732
  for(const e of T){if(!e.enabled)continue
732
733
  const t={}
@@ -794,11 +795,13 @@ if(!s.default.existsSync(t))return null
794
795
  try{return"http://localhost:"+JSON.parse(s.default.readFileSync(t,"utf-8")).port}catch{return null}}(t.projectPath)
795
796
  if(!l)return{success:!1,message:"OpenCode not running (no opencode.json found)"}
796
797
  const d=`${l}${i.startsWith("/")?i:"/"+i}`
797
- try{const e={"Content-Type":"application/json",...c},t={method:r.toUpperCase(),headers:e}
798
- "GET"!==r.toUpperCase()&&"HEAD"!==r.toUpperCase()&&void 0!==a&&(t.body="string"==typeof a?a:JSON.stringify(a))
799
- const o=await fetch(d,t),s=o.headers.get("content-type")||""
800
- let n
801
- return n=s.includes("application/json")?await o.json():await o.text(),{success:!0,message:"Proxy request completed",data:{status:o.status,contentType:s,body:n}}}catch(e){return{success:!1,message:"Proxy request failed: "+(0,n.getErrorMessage)(e),error:(0,n.getErrorMessage)(e)}}}
798
+ try{const e={"Content-Type":"application/json",...c},o={method:r.toUpperCase(),headers:e}
799
+ if("GET"!==r.toUpperCase()&&"HEAD"!==r.toUpperCase()&&void 0!==a){let e=a
800
+ if("object"==typeof a&&null!==a&&"_alchemyAgentType"in a){const{_alchemyAgentType:t,...r}=a
801
+ e=r}o.body="string"==typeof e?e:JSON.stringify(e)}const s=await fetch(d,o),l=s.headers.get("content-type")||""
802
+ let u
803
+ if(u=l.includes("application/json")?await s.json():await s.text(),"POST"===r.toUpperCase()&&"/session"===i&&s.status>=200&&300>s.status&&u?.id)try{const e=u.id,r=a?.parentID
804
+ await t.apiClient.post(`/api/projects/${t.projectId}/cli-sessions`,{externalSessionId:e,providerId:"opencode",...r?{parentExternalSessionId:r}:{},agentType:a?._alchemyAgentType||"unknown",status:"active"}),t.logger.info("Registered proxy-created session in DB",{sessionId:e,parentSessionId:r})}catch(e){t.logger.warn("Failed to register proxy-created session",{error:(0,n.getErrorMessage)(e)})}return{success:!0,message:"Proxy request completed",data:{status:s.status,contentType:l,body:u}}}catch(e){return{success:!1,message:"Proxy request failed: "+(0,n.getErrorMessage)(e),error:(0,n.getErrorMessage)(e)}}}
802
805
  const o=r(t(6928)),s=r(t(9896)),n=t(8824)},9278(module){module.exports=require("net")},9735(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleApproveRequestAction=async function(e,t){const{requestId:o}=e,{logger:s,apiClient:n,projectId:i}=t
803
806
  if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
804
807
  s.info("Starting approve-request action",{requestId:o})
@@ -822,18 +825,18 @@ for(var a=0;a<s.length;a++)o[s[a]]=1})(require("./"+e.u(t)))}
822
825
  var i={};(()=>{function t(){const e=[(0,g.join)(__dirname,"..","package.json"),(0,g.join)(__dirname,"..","..","package.json")]
823
826
  for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
824
827
  if(e.version)return e.version}catch{}return"unknown"}function r(){const e=t()
825
- console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start action server + launch OpenCode (default)\n serve Start the action server only (for GUI-triggered actions)\n logout Log out and remove stored auth token\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy serve # Start action server only\n alchemy logout # Log out\nFor more information, visit: https://alchemylabcode.com/\n`)}function o(e){const t=(0,g.join)(e,I.ALCHEMY_DIR_NAME,"project.json")
828
+ console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start action server + launch OpenCode (default)\n serve Start the action server only (for GUI-triggered actions)\n logout Log out and remove stored auth token\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy serve # Start action server only\n alchemy logout # Log out\nFor more information, visit: https://alchemylabcode.com/\n`)}function o(e){const t=(0,g.join)(e,E.ALCHEMY_DIR_NAME,"project.json")
826
829
  return(0,d.existsSync)(t)}function s(e){const t=(0,p.platform)(),r="darwin"===t?"open":"win32"===t?"cmd":"xdg-open",o="win32"===t?["/c","start","",e]:[e];(0,h.spawn)(r,o,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((r=>{const o=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(o),r(!0))}),2e3)
827
830
  setTimeout((()=>{clearInterval(o),r(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
828
831
  try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),o=r(t)
829
832
  return(await o.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,r,o,s="cli-init"){try{const n=(0,g.basename)(r)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:r,createdVia:s}})})
830
833
  if(i.ok){const e=await i.json()
831
834
  return e.data?.id||e.id}o.info(`Project registration failed (HTTP ${i.status})`)}catch(e){o.info("Project registration failed",{error:(0,C.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
832
- const l=e(3444),d=e(9896),u=e(1943),g=e(6928),h=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),C=e(8824),E=e(6800),I=e(4139);(async function(){const i=process.argv.slice(2)
835
+ const l=e(3444),d=e(9896),u=e(1943),g=e(6928),h=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),C=e(8824),I=e(6800),E=e(4139);(async function(){const i=process.argv.slice(2)
833
836
  let p
834
837
  const b=i.indexOf("--port");-1!==b&&i[b+1]&&(p=parseInt(i[b+1],10),isNaN(p)&&(console.error("\u274c Invalid port number"),process.exit(1)))
835
838
  const k=i.filter(((e,t)=>"--port"!==e&&(-1===b||t!==b+1)))
836
- if(0===k.length)return void await async function(r={}){const i=process.cwd(),l=(0,g.join)(i,I.ALCHEMY_DIR_NAME)
839
+ if(0===k.length)return void await async function(r={}){const i=process.cwd(),l=(0,g.join)(i,E.ALCHEMY_DIR_NAME)
837
840
  await(0,u.mkdir)(l,{recursive:!0})
838
841
  const p=(0,g.join)(l,"server.log"),{createLogger:b}=await Promise.resolve().then(e.t.bind(e,2782,23)),k=b({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),j=await a(k),T=new f.TokenManager(k,i,{remoteServerUrl:j,allowInteractiveAuth:!1})
839
842
  await T.initialize()
@@ -843,16 +846,16 @@ const e=await T.startAuthenticationFlow()
843
846
  console.log("\u{1f310} "+e.authUrl),s(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(T)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),S=await T.getToken(),console.log(`\u2705 Welcome, ${S?.username||"user"}!`),console.log("")}let P=S?.token
844
847
  await async function(e,t,r,s){if(o(e))return
845
848
  console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
846
- const n=(0,g.join)(e,I.ALCHEMY_DIR_NAME)
849
+ const n=(0,g.join)(e,E.ALCHEMY_DIR_NAME)
847
850
  let i
848
851
  await(0,u.mkdir)(n,{recursive:!0}),r?(console.log(`\u{1f4cb} Registering project "${(0,g.basename)(e)||"Local Project"}"...`),i=await c(t,r,e,s),console.log(i?"\u2705 Project registered: "+i:"\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token \u2014 skipping project registration")
849
852
  const a=(0,g.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,g.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,j,P,k)
850
- const A=(0,g.join)(i,I.ALCHEMY_DIR_NAME,"project.json")
853
+ const A=(0,g.join)(i,E.ALCHEMY_DIR_NAME,"project.json")
851
854
  let M
852
855
  try{const e=JSON.parse((0,d.readFileSync)(A,"utf-8"))
853
856
  if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,k,"cli-init-retry")
854
857
  t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(A,JSON.stringify(e,null,2),"utf-8"),M=t,console.log("\u2705 Project registered: "+t))}}catch{k.warn("Could not read project.json")}await async function(t){try{return void(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let r="latest"
855
- try{const e=(0,g.join)(t,I.ALCHEMY_DIR_NAME,"config.json")
858
+ try{const e=(0,g.join)(t,E.ALCHEMY_DIR_NAME,"config.json")
856
859
  if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
857
860
  t.opencodeVersion&&(r=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
858
861
  const o="latest"===r?"latest":"v"+r
@@ -866,7 +869,7 @@ const s=(0,g.join)(o,"opencode")
866
869
  return(0,h.execFileSync)("curl",["-fsSL","-o",s,t],{stdio:"inherit",timeout:6e4}),(0,h.execFileSync)("chmod",["+x",s],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(o)||(console.log(` \u26a0\ufe0f Add ${o} to your PATH:`),console.log(` export PATH="${o}:$PATH"`),console.log(""),process.env.PATH=`${o}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,C.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
867
870
  const _=t()
868
871
  console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+_,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
869
- const O=r.port||await(0,E.findAvailablePort)(3456),x=new y.AlchemyActionServer(k,{projectPath:i,remoteServerUrl:j,port:O,authToken:P})
872
+ const O=r.port||await(0,I.findAvailablePort)(3456),x=new y.AlchemyActionServer(k,{projectPath:i,remoteServerUrl:j,port:O,authToken:P})
870
873
  try{await x.start()
871
874
  let t=process.env.FRONTEND_URL
872
875
  if(!t)if(j.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
@@ -875,7 +878,7 @@ if(e.ok){const r=await e.json()
875
878
  t=r?.data?.frontendUrl}}catch{}t||(t=j.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
876
879
  try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(k,j,P),r=new w.UnifiedLauncher(k,e,t),o=await r.launch({projectPath:i,settings:M?{projectId:M}:void 0})
877
880
  if(o.success&&o.port){o.pid&&x.trackChildPid(o.pid),x.setOpenCodePort(o.port),console.log(" OpenCode serve ... port "+o.port)
878
- const e=(0,g.join)(i,I.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:o.port,pid:o.pid,startedAt:(new Date).toISOString()},null,2)),k.info("Wrote OpenCode config",{path:e,port:o.port})}else{if(!o.success)throw Error(o.error||"Launch failed")
881
+ const e=(0,g.join)(i,E.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:o.port,pid:o.pid,startedAt:(new Date).toISOString()},null,2)),k.info("Wrote OpenCode config",{path:e,port:o.port})}else{if(!o.success)throw Error(o.error||"Launch failed")
879
882
  console.log(" OpenCode serve ... started")}}catch(e){k.warn("Failed to launch OpenCode serve",{error:(0,C.getErrorMessage)(e)}),console.log(" OpenCode serve ... failed (run manually: opencode serve)")}console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${t}\x07${t}\x1b]8;;\x07`),console.log("")
880
883
  let r=!1,o=null
881
884
  const a=async()=>{r||(r=!0,o&&clearInterval(o),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
@@ -895,7 +898,7 @@ const i=await a(s),c=new f.TokenManager(s,r,{remoteServerUrl:i,allowInteractiveA
895
898
  await c.initialize()
896
899
  const d=await c.getToken(),u=d?.token
897
900
  u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
898
- const g=e.port||await(0,E.findAvailablePort)(3456),h=new y.AlchemyActionServer(s,{projectPath:r,remoteServerUrl:i,port:g,authToken:u})
901
+ const g=e.port||await(0,I.findAvailablePort)(3456),h=new y.AlchemyActionServer(s,{projectPath:r,remoteServerUrl:i,port:g,authToken:u})
899
902
  try{await h.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${h.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
900
903
  let e=!1
901
904
  const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await h.stop(),process.exit(0))}
package/package.json CHANGED
@@ -1,13 +1,19 @@
1
1
  {
2
2
  "name": "alchemylab",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "private": false,
5
-
6
5
  "description": "AlchemyLab - agentic coding IDEm",
7
6
  "author": "Testfire Software",
8
7
  "license": "SEE LICENSE IN LICENSE.txt",
9
8
  "homepage": "https://alchemylabcode.com",
10
- "keywords": ["ai", "development", "orchestration", "mcp", "opencode", "claude"],
9
+ "keywords": [
10
+ "ai",
11
+ "development",
12
+ "orchestration",
13
+ "mcp",
14
+ "opencode",
15
+ "claude"
16
+ ],
11
17
  "main": "dist/index.js",
12
18
  "bin": {
13
19
  "alchemy": "dist/cli.js",
@@ -19,7 +25,6 @@
19
25
  "package.json",
20
26
  "README.md"
21
27
  ],
22
-
23
28
  "scripts": {
24
29
  "build": "npm run build:dev",
25
30
  "build:dev": "tsc -p tsconfig.json && chmod +x dist-dev/*.js",
@@ -34,7 +39,6 @@
34
39
  "@modelcontextprotocol/sdk": "^1.7.0",
35
40
  "cors": "^2.8.5",
36
41
  "express": "^4.18.2",
37
-
38
42
  "micromatch": "^4.0.5",
39
43
  "simple-git": "^3.19.0",
40
44
  "winston": "^3.8.2",