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.
- package/dist/cli.js +43 -40
- 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
|
|
396
|
-
|
|
397
|
-
r
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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,
|
|
534
|
-
let
|
|
535
|
-
if(g?.length)
|
|
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
|
-
|
|
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",
|
|
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(
|
|
543
|
-
await j.raw(["worktree","add","--detach",k,
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
644
|
-
try{
|
|
645
|
-
return
|
|
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=
|
|
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},
|
|
719
|
-
i?.openCodeModel?
|
|
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=
|
|
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:
|
|
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},
|
|
798
|
-
"GET"!==r.toUpperCase()&&"HEAD"!==r.toUpperCase()&&void 0!==a
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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,
|
|
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),
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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": [
|
|
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",
|