alchemylab 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/60.js CHANGED
@@ -3,7 +3,7 @@ exports.id=60,exports.ids=[60],exports.modules={9060(e,exports,t){var i=this&&th
3
3
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ConfigManager=void 0,exports.createConfigManager=function(e){return new c(e)}
4
4
  const r=i(t(1943)),a=i(t(6928)),n=i(t(857)),o=t(4139),s=t(8824),l={multiAgent:!1,lockOverride:{enabled:!0,passwordValidityMinutes:5}},d={remoteServerUrl:"https://api.alchemylabcode.com",nodePath:"node"}
5
5
  class c{constructor(e){this.logger=e.child({component:"config-manager"}),this.installationConfigDir=this.getInstallationConfigDir()}getInstallationConfigDir(){const e=__dirname||process.cwd(),t=n.default.homedir()
6
- return e.includes("node_modules")?a.default.join(t,".alchemy-mcp"):a.default.join(t,".alchemy-mcp-local")}async ensureDirectory(e){try{await r.default.access(e)}catch(t){await r.default.mkdir(e,{recursive:!0}),this.logger.debug("Created directory",{dirPath:e})}}async readConfig(e,t){try{const i=await r.default.readFile(e,"utf8"),a=JSON.parse(i)
6
+ return e.includes("node_modules")?a.default.join(t,".alchemy_lab","config"):a.default.join(t,".alchemy_lab","config-dev")}async ensureDirectory(e){try{await r.default.access(e)}catch(t){await r.default.mkdir(e,{recursive:!0}),this.logger.debug("Created directory",{dirPath:e})}}async readConfig(e,t){try{const i=await r.default.readFile(e,"utf8"),a=JSON.parse(i)
7
7
  return{...t,...a}}catch(i){return"ENOENT"===i.code||this.logger.warn("Error reading config file, using defaults",{filePath:e,error:(0,s.getErrorMessage)(i)}),t}}async writeConfig(e,t){await this.ensureDirectory(a.default.dirname(e)),await r.default.writeFile(e,JSON.stringify(t,null,2),"utf8"),this.logger.debug("Wrote config file",{filePath:e})}async getProjectConfig(e){const t=a.default.join(e,o.ALCHEMY_DIR_NAME,"config.json")
8
8
  return this.readConfig(t,l)}async updateProjectConfig(e,t){const i=a.default.join(e,o.ALCHEMY_DIR_NAME,"config.json"),r={...await this.getProjectConfig(e),...t}
9
9
  await this.writeConfig(i,r),this.logger.info("Updated project config",{projectPath:e,updates:t})}async getProjectMetadata(e){const t=a.default.join(e,o.ALCHEMY_DIR_NAME,"project.json")
package/dist/cli.js CHANGED
@@ -45,20 +45,20 @@ if(o&&!a){const r=parseInt(o[1],10)
45
45
  a=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:n.pid,port:r}),n.unref(),e({success:!0,pid:n.pid,port:r,configPath:t.configPath})}}
46
46
  n.stdout?.on("data",(e=>c(e.toString()))),n.stderr?.on("data",(e=>c(e.toString()))),setTimeout((()=>{a||(a=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:i.slice(0,500)}),n.unref(),e({success:!0,pid:n.pid,configPath:t.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),n.on("error",(t=>{a||(a=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),n.on("exit",(t=>{a||(a=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:t,output:i.slice(0,500)}),e({success:!1,error:"opencode serve exited with code "+t}))}))}))}}},1253(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
47
47
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
48
- const o=r(t(6928)),s=r(t(1943)),n=r(t(6982)),i=r(t(857)),a=t(4139),c=t(9067),l=t(8824),d="alchemy-lab"
49
- exports.TokenManager=class{constructor(e,t,r={}){this.projectId=null,this.encryptionKey=null,this.pendingAuthCheck=!1,this.tempAuthCode=null,this.CACHE_TTL_MS=6e4,this.TOKEN_CACHE={manualToken:null,encryptedToken:null,hasValidToken:null},this.initPromise=null,this.initialized=!1,this.logger=e.child({component:"token-manager"}),this.remoteServerUrl=r.remoteServerUrl||"https://api.alchemylabcode.com",this.allowInteractiveAuth=!1!==r.allowInteractiveAuth,void 0!==r.cacheTtlMs&&r.cacheTtlMs>0&&(this.CACHE_TTL_MS=r.cacheTtlMs),this.serverInstallDir=this.getModuleDir(),this.tokenDirPath=o.default.join(this.serverInstallDir,a.TOKEN_DIR_NAME),this.authFlow=new c.ExternalAuthFlow(e,this.remoteServerUrl),this.logger.info("TokenManager initialized",{workingDir:t,serverInstallDir:this.serverInstallDir,tokenDirPath:this.tokenDirPath,allowInteractiveAuth:this.allowInteractiveAuth,cacheTtlMs:this.CACHE_TTL_MS}),this.generateEncryptionKey()}getModuleDir(){const e=__dirname||process.cwd(),t=i.default.homedir()
50
- return e.includes("node_modules")?o.default.join(t,".alchemy-mcp"):o.default.join(t,".alchemy-mcp-local")}async initialize(){return this.initPromise||(this.initPromise=this.ensureTokenDirectoryExists().then((()=>{this.initialized=!0,this.logger.info("TokenManager initialization completed")})).catch((e=>{throw this.logger.error("TokenManager initialization failed",{error:e instanceof Error?e.stack:e+""}),Error("TokenManager initialization failed")}))),this.initPromise}getStableMachineId(){try{const e=t(857),r=o.default.join(this.serverInstallDir,".machine-id")
48
+ const o=r(t(6928)),s=r(t(1943)),n=r(t(6982)),i=r(t(857)),a=t(9067),c=t(8824),l="alchemy-lab"
49
+ exports.TokenManager=class{constructor(e,t,r={}){this.projectId=null,this.encryptionKey=null,this.pendingAuthCheck=!1,this.tempAuthCode=null,this.CACHE_TTL_MS=6e4,this.TOKEN_CACHE={manualToken:null,encryptedToken:null,hasValidToken:null},this.initPromise=null,this.initialized=!1,this.logger=e.child({component:"token-manager"}),this.remoteServerUrl=r.remoteServerUrl||"https://api.alchemylabcode.com",this.allowInteractiveAuth=!1!==r.allowInteractiveAuth,void 0!==r.cacheTtlMs&&r.cacheTtlMs>0&&(this.CACHE_TTL_MS=r.cacheTtlMs),this.serverInstallDir=this.getModuleDir(),this.tokenDirPath=this.serverInstallDir,this.authFlow=new a.ExternalAuthFlow(e,this.remoteServerUrl),this.logger.info("TokenManager initialized",{workingDir:t,serverInstallDir:this.serverInstallDir,tokenDirPath:this.tokenDirPath,allowInteractiveAuth:this.allowInteractiveAuth,cacheTtlMs:this.CACHE_TTL_MS}),this.generateEncryptionKey()}getModuleDir(){const e=__dirname||process.cwd(),t=i.default.homedir()
50
+ return e.includes("node_modules")?o.default.join(t,".alchemy_lab","tokens"):o.default.join(t,".alchemy_lab","tokens-dev")}async initialize(){return this.initPromise||(this.initPromise=this.ensureTokenDirectoryExists().then((()=>{this.initialized=!0,this.logger.info("TokenManager initialization completed")})).catch((e=>{throw this.logger.error("TokenManager initialization failed",{error:e instanceof Error?e.stack:e+""}),Error("TokenManager initialization failed")}))),this.initPromise}getStableMachineId(){try{const e=t(857),r=o.default.join(this.serverInstallDir,".machine-id")
51
51
  try{const e=t(9896).readFileSync(r,"utf8").trim()
52
52
  if(e&&e.length>0)return e}catch{}const s=e.platform(),i=e.arch(),a=e.homedir(),c=e.cpus(),l=`${s}-${i}-${c&&c.length>0?c[0].model:"unknown"}-${a}`,d=n.default.createHash("sha256").update(l).digest("hex")
53
- try{t(9896).writeFileSync(r,d,"utf8"),this.logger.debug("Created new machine ID file",{path:r})}catch(e){this.logger.warn("Could not persist machine ID, will regenerate on restart",{error:e instanceof Error?e.message:e+""})}return d}catch(e){return this.logger.error("Error getting stable machine ID",{error:(0,l.getErrorMessage)(e)}),n.default.randomBytes(32).toString("hex")}}generateEncryptionKey(){try{const e=t(857).platform(),r=this.getStableMachineId(),o=`${d}-${e}-${r}`
54
- this.encryptionKey=n.default.createHash("sha256").update(o).digest(),this.logger.debug("Generated encryption key using stable machine ID")}catch(e){this.logger.error("Error generating encryption key",{error:(0,l.getErrorMessage)(e)}),this.encryptionKey=Buffer.from(d.repeat(4),"utf8")}}generateLegacyEncryptionKey(){try{const e=t(857).platform(),r=t(857).hostname(),o=`${d}-${e}-${r}`
55
- return n.default.createHash("sha256").update(o).digest()}catch(e){return this.logger.error("Error generating legacy encryption key",{error:(0,l.getErrorMessage)(e)}),Buffer.from(d.repeat(4),"utf8")}}async ensureTokenDirectoryExists(){try{await s.default.mkdir(this.tokenDirPath,{recursive:!0}),this.logger.debug("Token directory ensured exists",{path:this.tokenDirPath})}catch(e){throw this.logger.error("Error creating token directory",{path:this.tokenDirPath,error:(0,l.getErrorMessage)(e)}),Error("Error creating token directory: "+this.tokenDirPath)}}setProjectId(e){this.projectId=e,this.logger.info("Set project ID for token manager",{projectId:e})}getAccountKey(){return"default-user-"+n.default.createHash("sha256").update(this.remoteServerUrl).digest("hex").substring(0,8)}getTokenFilePath(e){const t=e.replace(/[^a-zA-Z0-9]/g,"_")
53
+ try{t(9896).writeFileSync(r,d,"utf8"),this.logger.debug("Created new machine ID file",{path:r})}catch(e){this.logger.warn("Could not persist machine ID, will regenerate on restart",{error:e instanceof Error?e.message:e+""})}return d}catch(e){return this.logger.error("Error getting stable machine ID",{error:(0,c.getErrorMessage)(e)}),n.default.randomBytes(32).toString("hex")}}generateEncryptionKey(){try{const e=t(857).platform(),r=this.getStableMachineId(),o=`${l}-${e}-${r}`
54
+ this.encryptionKey=n.default.createHash("sha256").update(o).digest(),this.logger.debug("Generated encryption key using stable machine ID")}catch(e){this.logger.error("Error generating encryption key",{error:(0,c.getErrorMessage)(e)}),this.encryptionKey=Buffer.from(l.repeat(4),"utf8")}}generateLegacyEncryptionKey(){try{const e=t(857).platform(),r=t(857).hostname(),o=`${l}-${e}-${r}`
55
+ return n.default.createHash("sha256").update(o).digest()}catch(e){return this.logger.error("Error generating legacy encryption key",{error:(0,c.getErrorMessage)(e)}),Buffer.from(l.repeat(4),"utf8")}}async ensureTokenDirectoryExists(){try{await s.default.mkdir(this.tokenDirPath,{recursive:!0}),this.logger.debug("Token directory ensured exists",{path:this.tokenDirPath})}catch(e){throw this.logger.error("Error creating token directory",{path:this.tokenDirPath,error:(0,c.getErrorMessage)(e)}),Error("Error creating token directory: "+this.tokenDirPath)}}setProjectId(e){this.projectId=e,this.logger.info("Set project ID for token manager",{projectId:e})}getAccountKey(){return"default-user-"+n.default.createHash("sha256").update(this.remoteServerUrl).digest("hex").substring(0,8)}getTokenFilePath(e){const t=e.replace(/[^a-zA-Z0-9]/g,"_")
56
56
  return o.default.join(this.tokenDirPath,t+".token")}invalidateCache(){this.logger.debug("Invalidating all token caches"),this.TOKEN_CACHE.manualToken=null,this.TOKEN_CACHE.encryptedToken=null,this.TOKEN_CACHE.hasValidToken=null}async checkForManualToken(){const e=Date.now(),t=o.default.join(this.tokenDirPath,"manual-token.json")
57
57
  if(this.TOKEN_CACHE.manualToken&&e<this.TOKEN_CACHE.manualToken.expiresAt){const t=this.TOKEN_CACHE.manualToken
58
58
  return this.logger.debug("Using cached manual token check result",{ageSecs:Math.round((e-t.timestamp)/1e3),exists:t.exists,hasToken:null!==t.value}),t.exists&&t.value?t.value:null}this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Checking for manual token file",{tokenFilePath:t})
59
59
  try{let r=!1,o=null
60
60
  try{await s.default.stat(t),r=!0,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file exists")}catch{this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file does not exist")}if(r)try{const r=await s.default.readFile(t,"utf8"),n=JSON.parse(r)
61
- new Date(n.expiresAt)>new Date?(o=n,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Valid manual token found",{username:o.username,expiresAt:o.expiresAt})):this.logger.debug("Manual token has expired",{expiresAt:n.expiresAt})}catch(e){this.logger.error("Error parsing manual token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.manualToken={value:o,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:r},o}catch(e){return this.logger.error("Error checking for manual token",{error:(0,l.getErrorMessage)(e)}),null}}encrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
61
+ new Date(n.expiresAt)>new Date?(o=n,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Valid manual token found",{username:o.username,expiresAt:o.expiresAt})):this.logger.debug("Manual token has expired",{expiresAt:n.expiresAt})}catch(e){this.logger.error("Error parsing manual token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.manualToken={value:o,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:r},o}catch(e){return this.logger.error("Error checking for manual token",{error:(0,c.getErrorMessage)(e)}),null}}encrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
62
62
  const t=n.default.randomBytes(16),r=n.default.createCipheriv("aes-256-cbc",this.encryptionKey,t)
63
63
  let o=r.update(e,"utf8","hex")
64
64
  return o+=r.final("hex"),`${t.toString("hex")}:${o}`}decrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
@@ -72,7 +72,7 @@ const s=Buffer.from(r,"hex"),i=n.default.createDecipheriv("aes-256-cbc",t,s)
72
72
  let a=i.update(o,"hex","utf8")
73
73
  return a+=i.final("utf8"),a}catch(e){throw e}}async storeToken(e){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
74
74
  try{const t=this.getAccountKey(),r=this.getTokenFilePath(t),o=JSON.stringify(e),n=this.encrypt(o)
75
- await s.default.writeFile(r,n,"utf8"),this.logger.info("Token stored securely",{username:e.username,expiresAt:e.expiresAt}),this.invalidateCache()}catch(e){throw this.logger.error("Error storing token",{error:(0,l.getErrorMessage)(e)}),Error("Failed to store token securely")}}async getToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
75
+ await s.default.writeFile(r,n,"utf8"),this.logger.info("Token stored securely",{username:e.username,expiresAt:e.expiresAt}),this.invalidateCache()}catch(e){throw this.logger.error("Error storing token",{error:(0,c.getErrorMessage)(e)}),Error("Failed to store token securely")}}async getToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
76
76
  try{if(this.hasPendingAuth()&&this.tempAuthCode){const e=await this.checkAuthStatus(this.tempAuthCode)
77
77
  if(e)return e}const e=await this.checkForManualToken()
78
78
  if(e)return e
@@ -80,32 +80,32 @@ const t=Date.now()
80
80
  if(this.TOKEN_CACHE.encryptedToken&&t<this.TOKEN_CACHE.encryptedToken.expiresAt){const e=this.TOKEN_CACHE.encryptedToken
81
81
  return this.logger.debug("Using cached encrypted token",{ageSecs:Math.round((t-e.timestamp)/1e3),hasToken:null!==e.value}),e.value}const r=this.getAccountKey(),o=this.getTokenFilePath(r)
82
82
  try{await s.default.stat(o)}catch{return this.logger.debug("No encrypted token file found"),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:t,expiresAt:t+this.CACHE_TTL_MS,lastCheck:t,exists:!1},this.allowInteractiveAuth&&!this.hasPendingAuth()?(this.logger.debug("No stored token found, starting authentication flow"),this.startAuthenticationFlow().catch((e=>{this.logger.error("Failed to start authentication flow",{error:e instanceof Error?e.message:e+""})}))):this.logger.debug("Interactive authentication disabled or already pending"),null}try{const e=await s.default.readFile(o,"utf8"),t=this.decrypt(e),r=JSON.parse(t),n=new Date(r.expiresAt),i=new Date,a=n.getTime()-i.getTime(),c=a/36e5
83
- return this.logger.info("Token expiration check details",{tokenExpiresAt:r.expiresAt,currentTime:i.toISOString(),isExpired:i>=n,timeUntilExpiryMs:a,timeUntilExpiryHours:Math.round(100*c)/100,username:r.username,systemTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone}),n>i?(this.logger.info("Retrieved valid token from storage",{username:r.username,expiresAt:r.expiresAt}),this.TOKEN_CACHE.encryptedToken={value:r,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!0},r):(this.logger.info("Stored token has expired",{expiresAt:r.expiresAt,expiredByMs:i.getTime()-n.getTime(),expiredByHours:Math.round((i.getTime()-n.getTime())/36e5*100)/100}),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null)}catch(e){this.logger.error("Error reading/decrypting token file",{error:(0,l.getErrorMessage)(e),path:o})
83
+ return this.logger.info("Token expiration check details",{tokenExpiresAt:r.expiresAt,currentTime:i.toISOString(),isExpired:i>=n,timeUntilExpiryMs:a,timeUntilExpiryHours:Math.round(100*c)/100,username:r.username,systemTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone}),n>i?(this.logger.info("Retrieved valid token from storage",{username:r.username,expiresAt:r.expiresAt}),this.TOKEN_CACHE.encryptedToken={value:r,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!0},r):(this.logger.info("Stored token has expired",{expiresAt:r.expiresAt,expiredByMs:i.getTime()-n.getTime(),expiredByHours:Math.round((i.getTime()-n.getTime())/36e5*100)/100}),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null)}catch(e){this.logger.error("Error reading/decrypting token file",{error:(0,c.getErrorMessage)(e),path:o})
84
84
  try{const e=o+".backup"
85
- await s.default.rename(o,e),this.logger.info("Moved corrupted token file to backup",{from:o,to:e})}catch(e){this.logger.error("Error creating backup of corrupted token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null}}catch(e){return this.logger.error("Error retrieving token",{error:(0,l.getErrorMessage)(e)}),null}}async removeToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
85
+ await s.default.rename(o,e),this.logger.info("Moved corrupted token file to backup",{from:o,to:e})}catch(e){this.logger.error("Error creating backup of corrupted token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null}}catch(e){return this.logger.error("Error retrieving token",{error:(0,c.getErrorMessage)(e)}),null}}async removeToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
86
86
  try{const e=this.getAccountKey(),t=this.getTokenFilePath(e)
87
- try{await s.default.stat(t)}catch{return this.logger.info("No token file to remove"),!1}return await s.default.unlink(t),this.logger.info("Token removed from secure storage"),this.invalidateCache(),!0}catch(e){return this.logger.error("Error removing token",{error:(0,l.getErrorMessage)(e)}),!1}}async hasValidToken(){const e=Date.now()
87
+ try{await s.default.stat(t)}catch{return this.logger.info("No token file to remove"),!1}return await s.default.unlink(t),this.logger.info("Token removed from secure storage"),this.invalidateCache(),!0}catch(e){return this.logger.error("Error removing token",{error:(0,c.getErrorMessage)(e)}),!1}}async hasValidToken(){const e=Date.now()
88
88
  if(this.TOKEN_CACHE.hasValidToken&&e<this.TOKEN_CACHE.hasValidToken.expiresAt){const t=this.TOKEN_CACHE.hasValidToken
89
89
  return this.logger.debug("Using cached hasValidToken result",{ageSecs:Math.round((e-t.timestamp)/1e3),hasToken:t.value}),t.value}this.logger.debug("Checking if valid token exists (cache miss or expired)")
90
90
  const t=null!==await this.getToken()
91
91
  return this.TOKEN_CACHE.hasValidToken={value:t,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:t},t}getTokenDirectoryPath(){return this.tokenDirPath}getManualTokenFilePath(){return o.default.join(this.tokenDirPath,"manual-token.json")}logTokenLocationInstructions(){const e=this.getManualTokenFilePath()
92
92
  console.log("========================================"),console.log("To use MCP server, you need to authenticate."),console.log("Token file location: "+e),console.log("You can either:"),console.log('\n1. Use the authenticate tool (recommended)\n2. Manually place a token file at the above location\n3. Set up automatic authentication flow\n\nFor manual token placement, create a JSON file with:\n{\n "token": "your_token_here",\n "userId": "your_user_id",\n "username": "your_username", \n "role": "user",\n "expiresAt": "2025-12-31T23:59:59.000Z"\n}\n'),console.log("For more information, see the authentication documentation."),console.log("========================================")}async startAuthenticationFlow(){this.pendingAuthCheck=!0
93
93
  try{const{tempCode:e,authUrl:t}=await this.authFlow.startAuthFlow()
94
- return this.tempAuthCode=e,this.logger.info("Started external authentication flow",{tempCode:e,authUrl:t}),this.authFlow.startPolling(e,(async t=>{this.logger.info("Authentication flow completed",{tempCode:e.substring(0,4)+"***",username:t.username}),await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),console.log("Authentication successful!"),console.log(`Welcome, ${t.username}!`),console.log("You can now use all MCP tools and features."),console.log("========================================")}),(()=>{this.logger.info("Authentication flow timed out",{tempCode:e.substring(0,4)+"***"}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow timed out."),console.log("Please try again or manually place a token file."),console.log("Token file location:"),console.log(""+this.getManualTokenFilePath()),console.log("========================================")}),(t=>{this.logger.error("Authentication flow error",{tempCode:e.substring(0,4)+"***",error:t}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow error."),console.log("Error: "+t),console.log("Please try again or manually place a token file."),console.log(""+this.getManualTokenFilePath()),console.log("========================================")})),{tempCode:e,authUrl:t}}catch(e){throw this.logger.error("Error starting authentication flow",{error:(0,l.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,e}}hasPendingAuth(){return this.pendingAuthCheck||!!this.tempAuthCode}getAuthStatus(){return this.hasPendingAuth()?{hasToken:!1,isPendingAuth:!0,tempCode:this.tempAuthCode,authUrl:this.authFlow.getAuthUrl(this.tempAuthCode)}:{hasToken:!1,isPendingAuth:!1}}async checkAuthStatus(e){if(!e)return null
94
+ return this.tempAuthCode=e,this.logger.info("Started external authentication flow",{tempCode:e,authUrl:t}),this.authFlow.startPolling(e,(async t=>{this.logger.info("Authentication flow completed",{tempCode:e.substring(0,4)+"***",username:t.username}),await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),console.log("Authentication successful!"),console.log(`Welcome, ${t.username}!`),console.log("You can now use all MCP tools and features."),console.log("========================================")}),(()=>{this.logger.info("Authentication flow timed out",{tempCode:e.substring(0,4)+"***"}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow timed out."),console.log("Please try again or manually place a token file."),console.log("Token file location:"),console.log(""+this.getManualTokenFilePath()),console.log("========================================")}),(t=>{this.logger.error("Authentication flow error",{tempCode:e.substring(0,4)+"***",error:t}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow error."),console.log("Error: "+t),console.log("Please try again or manually place a token file."),console.log(""+this.getManualTokenFilePath()),console.log("========================================")})),{tempCode:e,authUrl:t}}catch(e){throw this.logger.error("Error starting authentication flow",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,e}}hasPendingAuth(){return this.pendingAuthCheck||!!this.tempAuthCode}getAuthStatus(){return this.hasPendingAuth()?{hasToken:!1,isPendingAuth:!0,tempCode:this.tempAuthCode,authUrl:this.authFlow.getAuthUrl(this.tempAuthCode)}:{hasToken:!1,isPendingAuth:!1}}async checkAuthStatus(e){if(!e)return null
95
95
  try{const t=this.authFlow.getPollingStatus()
96
- if(t.status===c.PollingStatus.AUTHENTICATED&&t.tokenInfo)return this.logger.info("Authentication polling has token available",{tempCode:e.substring(0,4)+"***",username:t.tokenInfo.username}),t.tokenInfo
97
- if(t.status===c.PollingStatus.ERROR||t.status===c.PollingStatus.TIMEOUT)return this.pendingAuthCheck=!1,this.tempAuthCode=null,null
96
+ if(t.status===a.PollingStatus.AUTHENTICATED&&t.tokenInfo)return this.logger.info("Authentication polling has token available",{tempCode:e.substring(0,4)+"***",username:t.tokenInfo.username}),t.tokenInfo
97
+ if(t.status===a.PollingStatus.ERROR||t.status===a.PollingStatus.TIMEOUT)return this.pendingAuthCheck=!1,this.tempAuthCode=null,null
98
98
  const r=this.authFlow.getPollingUrl(e),o=await fetch(r)
99
99
  if(o.ok){const e=await o.json()
100
100
  if(e.authenticated&&e.token){const t=this.authFlow.parseTokenResponse(e)
101
- if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,l.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
101
+ if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,c.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
102
102
  try{const e=await this.getToken()
103
103
  if(!e)return this.logger.debug("No token available for validation"),!1
104
104
  const t=this.remoteServerUrl+"/auth/validate"
105
105
  this.logger.debug("Making token validation request",{validationUrl:t})
106
106
  const r=await fetch(t,{method:"GET",headers:{Authorization:"Bearer "+e.token,"Content-Type":"application/json"},signal:AbortSignal.timeout(1e4)})
107
107
  if(r.ok){const e=await r.json()
108
- return this.logger.info("Token validation successful",{status:e.status||"unknown",username:e.username||"unknown"}),!0}return this.logger.info("Token validation failed - token may be invalid or expired",{status:r.status,statusText:r.statusText}),this.invalidateCache(),!1}catch(e){return e instanceof Error&&"AbortError"===e.name?(this.logger.info("Token validation request timed out - assuming offline or server unavailable"),!0):(this.logger.info("Token validation request failed",{error:(0,l.getErrorMessage)(e)}),!0)}}async hasValidAndWorkingToken(){return!!await this.hasValidToken()&&await this.validateTokenWithRemoteServer()}async isGoogleOAuthAvailable(){return await this.authFlow.isGoogleOAuthAvailable()}async startGoogleOAuthFlow(){return await this.authFlow.startGoogleOAuthFlow()}async startGoogleOAuthPolling(e,t,r,o,s){return await this.authFlow.startGoogleOAuthPolling(e,t,r,o,s)}}},1943(module){module.exports=require("fs/promises")},2096(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
108
+ return this.logger.info("Token validation successful",{status:e.status||"unknown",username:e.username||"unknown"}),!0}return this.logger.info("Token validation failed - token may be invalid or expired",{status:r.status,statusText:r.statusText}),this.invalidateCache(),!1}catch(e){return e instanceof Error&&"AbortError"===e.name?(this.logger.info("Token validation request timed out - assuming offline or server unavailable"),!0):(this.logger.info("Token validation request failed",{error:(0,c.getErrorMessage)(e)}),!0)}}async hasValidAndWorkingToken(){return!!await this.hasValidToken()&&await this.validateTokenWithRemoteServer()}async isGoogleOAuthAvailable(){return await this.authFlow.isGoogleOAuthAvailable()}async startGoogleOAuthFlow(){return await this.authFlow.startGoogleOAuthFlow()}async startGoogleOAuthPolling(e,t,r,o,s){return await this.authFlow.startGoogleOAuthPolling(e,t,r,o,s)}}},1943(module){module.exports=require("fs/promises")},2096(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
109
109
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProjectConfigService=void 0
110
110
  const o=r(t(1943)),s=r(t(6928)),n=r(t(857)),i=t(4139),a=t(8824)
111
111
  exports.ProjectConfigService=class{constructor(e){this.logger=e.child({component:"project-config"})}async readProjectId(e){try{const t=s.default.join(e,i.ALCHEMY_DIR_NAME,"project.json"),r=await o.default.readFile(t,"utf-8"),n=JSON.parse(r)
@@ -124,10 +124,10 @@ try{const e=await o.default.readFile(i,"utf-8")
124
124
  c=JSON.parse(e)}catch(e){}c[e]={preset:t,disabledTools:r||[],allowedAlchemyTools:n||[],updatedAt:(new Date).toISOString()},await o.default.writeFile(i,JSON.stringify(c,null,2),"utf-8"),this.logger.info("User tool preferences saved",{clientType:e,prefsFile:i})}}},2366(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
125
125
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
126
126
  const o=r(t(7252)),s=r(t(8577)),n=t(4192),i=t(1152),a=r(t(6928)),c=r(t(9896)),l=r(t(857)),d=t(5607),u=t(8984),g=t(8524),h=t(8824),p=t(2096),f=t(7210),m=t(5317)
127
- class y{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.settingsSyncCounter=0,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
127
+ class y{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.settingsSyncCounter=0,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
128
128
  const r=(0,i.createMetricsCollector)(!1)
129
129
  this.apiClient=new n.BaseApiClient(t.remoteServerUrl,e,r,t.authToken),this.configurer=new u.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new g.CLIBridgeWebSocket(e),(0,g.setCLIBridge)(this.cliBridge),this.hookExecutor=new f.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,o.default)()
130
- return e.use((0,s.default)()),e.use(o.default.json({limit:"1mb"})),e.get("/health",this.handleHealth.bind(this)),e.get("/status",this.handleStatus.bind(this)),e.get("/opencode",this.handleOpenCodeConfig.bind(this)),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.post("/api/generate-journal",this.handleGenerateJournal.bind(this)),e.get("/api/event-hooks/by-event/:eventType",this.handleEventHooksProxy.bind(this)),e.post("/api/hook-triggers",this.handleHookTriggersProxy.bind(this)),e.get("/api/hook-iterations/:sessionId/:hookId",this.handleGetHookIteration.bind(this)),e.post("/api/hook-iterations/:sessionId/:hookId/increment",this.handleIncrementHookIteration.bind(this)),e.get("/api/project-settings",((e,t)=>{t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.post("/api/sync-settings",(async(e,t)=>{await this.syncProjectSettings(),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.get("/api/agents/by-name/:agentName",this.handleGetAgentByName.bind(this)),e.get("/api/sessions/:sessionId/agent-type",this.handleGetSessionAgentType.bind(this)),e.get("/api/sessions/:sessionId/parent",this.handleGetSessionParent.bind(this)),e.get("/api/sessions/:sessionId/ancestors",(async(e,t)=>{try{const{sessionId:r}=e.params,o=this.projectId
130
+ return e.use((0,s.default)()),e.use(o.default.json({limit:"10mb"})),e.get("/health",this.handleHealth.bind(this)),e.get("/status",this.handleStatus.bind(this)),e.get("/opencode",this.handleOpenCodeConfig.bind(this)),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.post("/api/generate-journal",this.handleGenerateJournal.bind(this)),e.get("/api/event-hooks/by-event/:eventType",this.handleEventHooksProxy.bind(this)),e.post("/api/hook-triggers",this.handleHookTriggersProxy.bind(this)),e.get("/api/hook-iterations/:sessionId/:hookId",this.handleGetHookIteration.bind(this)),e.post("/api/hook-iterations/:sessionId/:hookId/increment",this.handleIncrementHookIteration.bind(this)),e.get("/api/project-settings",((e,t)=>{t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.post("/api/sync-settings",(async(e,t)=>{await this.syncProjectSettings(),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.get("/api/agents/by-name/:agentName",this.handleGetAgentByName.bind(this)),e.get("/api/sessions/:sessionId/agent-type",this.handleGetSessionAgentType.bind(this)),e.get("/api/sessions/:sessionId/parent",this.handleGetSessionParent.bind(this)),e.get("/api/sessions/:sessionId/ancestors",(async(e,t)=>{try{const{sessionId:r}=e.params,o=this.projectId
131
131
  if(!o)return void t.status(400).json({success:!1,error:"No project"})
132
132
  const s=await this.apiClient.get(`/api/projects/${o}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),n=(s?.data||s||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title})))
133
133
  t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:r,sessionId:o,variables:s,timing:n}=e.body
@@ -136,7 +136,7 @@ const i=await this.hookExecutor.processEvent({eventType:r,sessionId:o||"",variab
136
136
  t.json({success:!0,data:i})}catch(e){this.logger.error("Hook event processing failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.use("/api/opencode",((e,t)=>this.handleOpenCodeProxy(e,t))),e}handleHealth(e,t){t.json({status:"healthy",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),timestamp:(new Date).toISOString()})}handleStatus(e,t){t.json({status:"running",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),startTime:this.startTime.toISOString(),projectId:this.projectId,serverId:this.serverId,isPolling:this.isPolling,port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const r=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
137
137
  try{if(!c.default.existsSync(r))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
138
138
  const e=c.default.readFileSync(r,"utf-8"),o=JSON.parse(e)
139
- t.json({port:o.port,pid:o.pid,startedAt:o.startedAt,baseUrl:"http://localhost:"+o.port,directory:this.options.projectPath,proxyEnabled:!!this.options.proxyOpenCode})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,h.getErrorMessage)(e)})}}async handleSyncAgents(e,t){try{const e=this.readProjectConfig()
139
+ t.json({port:o.port,pid:o.pid,startedAt:o.startedAt,baseUrl:"http://localhost:"+o.port,directory:this.options.projectPath,proxyEnabled:!0})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,h.getErrorMessage)(e)})}}async handleSyncAgents(e,t){try{const e=this.readProjectConfig()
140
140
  if(!e?.id)return void t.status(400).json({success:!1,error:"No project configuration found"})
141
141
  this.logger.info("Syncing agents from remote",{projectId:e.id})
142
142
  const r=await this.configurer.fetchAgentsFromRemote(e.id)
@@ -163,7 +163,7 @@ o.stdout?.on("data",(s=>{t+=s.toString()
163
163
  const n=t.match(/listening on http:\/\/[^:]+:(\d+)/)
164
164
  n&&(clearTimeout(r),o.unref(),e({port:parseInt(n[1],10),pid:o.pid}))})),o.on("error",(()=>{clearTimeout(r)
165
165
  try{o.kill("SIGTERM")}catch{}e(null)}))})),n=await s
166
- n&&(this.trackChildPid(n.pid),c.default.writeFileSync(e,JSON.stringify({port:n.port,pid:n.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:n.port,pid:n.pid}),l=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,h.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${i} agent(s) to local files${l?" - OpenCode reloading":""}`,count:i,restarted:l})}catch(e){this.logger.error("Failed to sync agents",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGenerateJournal(e,t){try{const{requestId:r}=e.body
166
+ n&&(this.trackChildPid(n.pid),c.default.writeFileSync(e,JSON.stringify({port:n.port,pid:n.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:n.port,pid:n.pid}),this.setOpenCodePort(n.port),l=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,h.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${i} agent(s) to local files${l?" - OpenCode reloading":""}`,count:i,restarted:l})}catch(e){this.logger.error("Failed to sync agents",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGenerateJournal(e,t){try{const{requestId:r}=e.body
167
167
  if(!r)return void t.status(400).json({success:!1,error:"requestId is required"})
168
168
  if(!/^req\d+$/.test(r))return void t.status(400).json({success:!1,error:"Invalid requestId format"})
169
169
  this.logger.info("Generating journal for request",{requestId:r})
@@ -210,20 +210,20 @@ if(o?.id)try{const e=await this.apiClient.get(`/api/projects/${o.id}/cli-session
210
210
  if(this.logger.info("Agent type DB lookup",{sessionId:r,dbAgentType:s?.agentType||null,dbId:s?.id||s?.localId||null}),s?.agentType)return void t.json({success:!0,agentType:s.agentType,source:"db"})}catch(e){this.logger.info("Agent type DB lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:r},5e3)
211
211
  if(this.logger.info("Agent type bridge lookup",{sessionId:r,bridgeAgentType:e?.data?.agentType||null,bridgeSuccess:e?.success}),e.success&&e.data?.agentType)return void t.json({success:!0,agentType:e.data.agentType,source:"bridge"})}catch(e){this.logger.info("Agent type bridge lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:r}),t.json({success:!0,agentType:null,source:"unknown"})}catch(e){this.logger.debug("Failed to get session agent type",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetSessionParent(e,t){try{const{sessionId:r}=e.params,o=this.readProjectConfig()
212
212
  if(o?.id)try{const e=await this.apiClient.get(`/api/projects/${o.id}/cli-sessions/resolve/${encodeURIComponent(r)}`),s=e?.data?.parent?.externalSessionId||""
213
- return void t.json({success:!0,parentSessionId:s})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{if(!this.openCodeUrlCache||Date.now()-this.openCodeUrlCache.timestamp>3e4){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
214
- if(!c.default.existsSync(e))return void t.status(502).json({error:"OpenCode not running"})
215
- const r=JSON.parse(c.default.readFileSync(e,"utf-8"))
216
- this.openCodeUrlCache={url:"http://localhost:"+r.port,timestamp:Date.now()}}const r=`${this.openCodeUrlCache.url}/${e.path.replace(/^\//,"")}${e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):""}`,o=e.headers.accept?.includes("text/event-stream")
217
- if(o){const o=await fetch(r,{headers:{Accept:"text/event-stream"}})
218
- if(!o.ok||!o.body)return void t.status(o.status).json({error:"SSE upstream error"})
213
+ return void t.json({success:!0,parentSessionId:s})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{const r=this.getOpenCodeUrl()
214
+ if(!r)return void t.status(502).json({error:"OpenCode not running"})
215
+ const o=`${r}/${e.path.replace(/^\//,"")}${e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):""}`,s=e.headers.accept?.includes("text/event-stream")
216
+ if(s){const r=new AbortController,s=()=>{this.proxyAbortControllers.delete(r)},n=(async()=>{try{const e=await fetch(o,{headers:{Accept:"text/event-stream"},signal:r.signal})
217
+ if(!e.ok||!e.body)return s(),void t.status(e.status).json({error:"SSE upstream error"})
219
218
  t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"})
220
- const s=o.body.getReader()
221
- return(async()=>{try{for(;;){const{done:e,value:r}=await s.read()
219
+ const n=e.body.getReader()
220
+ try{for(;;){const{done:e,value:r}=await n.read()
222
221
  if(e)break
223
- t.write(r)}}catch{}t.end()})(),void e.on("close",(()=>{try{s.cancel()}catch{}}))}const s={"Content-Type":e.headers["content-type"]||"application/json"},n={method:e.method,headers:s}
224
- "GET"!==e.method&&"HEAD"!==e.method&&e.body&&(n.body=JSON.stringify(e.body))
225
- const i=await fetch(r,n),l=i.headers.get("content-type")||"",d=l.includes("application/json")?await i.json():await i.text()
226
- t.status(i.status),l&&t.setHeader("Content-Type",l),"string"==typeof d?t.send(d):t.json(d)}catch(r){this.logger.error("OpenCode proxy error",{error:(0,h.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,h.getErrorMessage)(r)})}}async handleGetAgentByName(e,t){try{const r=this.readProjectConfig()
222
+ t.write(r)}}catch{}t.end()}catch{t.headersSent||t.status(502).json({error:"SSE upstream error"})}finally{s()}})()
223
+ return this.proxyAbortControllers.set(r,n),void e.on("close",(()=>{try{r.abort()}catch{}}))}const n={"Content-Type":e.headers["content-type"]||"application/json"},i={method:e.method,headers:n}
224
+ "GET"!==e.method&&"HEAD"!==e.method&&e.body&&(i.body=JSON.stringify(e.body))
225
+ const a=await fetch(o,i),c=a.headers.get("content-type")||"",l=c.includes("application/json")?await a.json():await a.text()
226
+ t.status(a.status),c&&t.setHeader("Content-Type",c),"string"==typeof l?t.send(l):t.json(l)}catch(r){this.openCodeUrlCache=null,this.logger.error("OpenCode proxy error",{error:(0,h.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,h.getErrorMessage)(r)})}}async handleGetAgentByName(e,t){try{const r=this.readProjectConfig()
227
227
  if(!r?.id)return void t.status(400).json({success:!1,error:"No project configured"})
228
228
  const{agentName:o}=e.params,s=await this.apiClient.get(`/api/projects/${r.id}/agents`),n=(s?.data||s||[]).find((e=>e.name===o))
229
229
  if(!n)return void t.status(404).json({success:!1,error:`Agent '${o}' not found`})
@@ -231,7 +231,10 @@ t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by n
231
231
  const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
232
232
  try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
233
233
  const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
234
- return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,h.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}getOpenCodeUrl(){if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
234
+ return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,h.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}setOpenCodePort(e){this.openCodePort=e
235
+ const t="http://127.0.0.1:"+e
236
+ this.openCodeUrlCache={url:t,timestamp:Date.now()},this.logger.info("OpenCode port set directly",{port:e})}getOpenCodeUrl(){if(this.openCodePort)return"http://127.0.0.1:"+this.openCodePort
237
+ if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
235
238
  const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
236
239
  try{if(!c.default.existsSync(e))return null
237
240
  const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
@@ -292,16 +295,18 @@ if(!t)return
292
295
  this.logger.debug("SSE hook: session_idle",{sessionId:t}),await this.hookExecutor.processEvent({eventType:"session_idle",sessionId:t,variables:{sessionId:t}})}else if("session.created"===t){const t=e?.properties?.info?.id||e?.info?.id||""
293
296
  if(!t)return
294
297
  setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:t}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:t,variables:{sessionId:t}})}),500)}}async flushSSEEvents(){if(0===this.sseEventBuffer.length||!this.projectId)return
298
+ if(!this.hasRemoteEventConsumers)return void(this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500))
295
299
  const e=this.sseEventBuffer.splice(0)
296
- try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e})}catch(t){this.logger.debug("SSE event flush failed",{error:(0,h.getErrorMessage)(t),count:e.length}),this.sseEventBuffer.unshift(...e),this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500)}}startPolling(){this.projectId&&(this.isPolling=!0,this.logger.info("Starting action polling",{projectId:this.projectId}),this.pollingInterval=setInterval((async()=>{await this.pollOnce()}),3e3),this.pollOnce())}async pollOnce(){if(this.isPolling&&this.projectId)try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/actions/pending`)
297
- if(e.success&&e.data?.settings){const{defaultModel:t,autoJournal:r}=e.data.settings
300
+ try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e})}catch(t){this.logger.debug("SSE event flush failed",{error:(0,h.getErrorMessage)(t),count:e.length}),this.sseEventBuffer.unshift(...e),this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500)}}startPolling(){this.projectId&&(this.isPolling=!0,this.pollAbortController=new AbortController,this.logger.info("Starting action long-polling",{projectId:this.projectId}),this.longPollLoop())}async longPollLoop(){for(;this.isPolling;)try{await this.pollOnce()}catch(e){if(!this.isPolling)return
301
+ this.logger.debug("Long-poll error, retrying in 5s",{error:(0,h.getErrorMessage)(e)}),await new Promise((e=>setTimeout(e,5e3)))}}async pollOnce(){if(this.isPolling&&this.projectId)try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/actions/pending?timeout=30000`)
302
+ if(e.success&&e.data&&void 0!==e.data.hasRemoteEventConsumers&&(this.hasRemoteEventConsumers=e.data.hasRemoteEventConsumers),e.success&&e.data?.settings){const{defaultModel:t,autoJournal:r}=e.data.settings
298
303
  if(void 0!==t&&(this.cachedDefaultModel=t,this.hookExecutor.setDefaultModel(t)),void 0!==r&&r!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:r})
299
304
  const e=new p.ProjectConfigService(this.logger)
300
305
  await e.saveMultiAgentConfig(this.options.projectPath,void 0,r),this.cachedAutoJournal=r}}if(e.success&&e.data?.actions?.length>0){const t=[],r=[]
301
306
  for(const o of e.data.actions)"opencode-proxy"===o.type||"run-tests"===o.type?t.push(o):r.push(o)
302
307
  for(const e of t)this.processAction(e).catch((t=>this.logger.debug("Proxy action failed",{actionId:e.id,error:(0,h.getErrorMessage)(t)})))
303
308
  if(r.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
304
- try{for(const e of r)await this.processAction(e)}finally{this.isProcessingAction=!1}}}}catch(e){this.logger.debug("Poll failed",{error:(0,h.getErrorMessage)(e)})}}async syncProjectSettings(){try{const e=await this.apiClient.get("/api/projects/"+this.projectId)
309
+ try{for(const e of r)await this.processAction(e)}finally{this.isProcessingAction=!1}}}}catch(e){throw e}}async syncProjectSettings(){try{const e=await this.apiClient.get("/api/projects/"+this.projectId)
305
310
  if(e.success&&e.data?.metadata){const{autoJournal:t,defaultModel:r}=e.data.metadata
306
311
  if(void 0!==r&&(this.cachedDefaultModel=r,this.hookExecutor.setDefaultModel(r)),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed, syncing to local config",{oldValue:this.cachedAutoJournal,newValue:t})
307
312
  const e=new p.ProjectConfigService(this.logger)
@@ -329,10 +334,11 @@ const e=encodeURIComponent(t.requestId),[o,s]=await Promise.all([this.apiClient.
329
334
  return{success:!0,data:{...o.data,tasks:s.data||[]}}}default:return{success:!1,error:"Unknown request action: "+e}}}catch(e){return{success:!1,error:(0,h.getErrorMessage)(e)}}}handleCLIBridgeStatus(e,t){t.json({uiConnected:this.cliBridge.isUIConnected()})}async start(){this.logger.info("Starting AlchemyActionServer",{projectPath:this.options.projectPath,port:this.port})
330
335
  for(let e=0;10>e;e++)try{await new Promise(((e,t)=>{try{this.server=this.app.listen(this.port,"127.0.0.1",(()=>{this.logger.info("HTTP server started",{port:this.port}),this.cliBridge.attach(this.server),e()})),this.server.on("error",(e=>{e.code,t(e)}))}catch(e){t(e)}}))
331
336
  break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
332
- continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){this.logger.warn("Remote registration failed, continuing in local-only mode",{error:(0,h.getErrorMessage)(e)})}this.startPolling(),this.options.proxyOpenCode&&this.projectId&&this.startSSEEventForwarder()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,h.getErrorMessage)(t)})}}this.trackedChildPids.clear()
337
+ continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){this.logger.warn("Remote registration failed, continuing in local-only mode",{error:(0,h.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,h.getErrorMessage)(t)})}}this.trackedChildPids.clear()
333
338
  try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
334
339
  if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
335
- if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,h.getErrorMessage)(e)})}}}}catch{}}async stop(){if(this.logger.info("Stopping AlchemyActionServer..."),this.killTrackedProcesses(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
340
+ if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,h.getErrorMessage)(e)})}}}}catch{}}async stop(){this.logger.info("Stopping AlchemyActionServer...")
341
+ for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}if(this.proxyAbortControllers.size>0&&await Promise.allSettled([...this.proxyAbortControllers.values()]),this.proxyAbortControllers.clear(),this.killTrackedProcesses(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
336
342
  this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=y,y.PROJECT_CONFIG_TTL=3e4},2782(e,exports,t){function r(){return"true"===process.env.ENABLE_DEBUG_TOOLS?.toLowerCase()}function o(e={}){const t={...function(e){const t={}
337
343
  if(!r())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.logToFile=!1,t
338
344
  if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
@@ -524,29 +530,29 @@ if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId
524
530
  m.info("Starting run-tests action",{requestId:u,testProcessIds:g})
525
531
  try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
526
532
  if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
527
- const p=d.data,I=p.branchName||"requests/"+u
528
- let E
529
- 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))
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))
530
536
  else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
531
- 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"}
532
- const k=(0,s.join)(w,c,l),b=(0,s.join)(k,u),j=(0,n.simpleGit)(w)
533
- if((0,o.existsSync)(b))try{const e=(0,n.simpleGit)(b)
534
- await e.raw(["reset","--hard",I]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:b,branchName:I})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
535
- try{await j.raw(["worktree","remove","--force",b])}catch{}}if(!(0,o.existsSync)(b)){(0,o.mkdirSync)(k,{recursive:!0})
536
- try{if(!(await j.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
537
- await j.raw(["worktree","add","--detach",b,I]),m.info("Created test worktree (detached)",{worktreePath:b,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)}
538
- m.info("Test worktree already exists, using as-is",{worktreePath:b})}}const{execSync:T}=await Promise.resolve().then(t.t.bind(t,5317,23))
539
- for(const e of E){const t=e.config?.setup||[]
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
+ const b=(0,s.join)(w,c,l),k=(0,s.join)(b,u),j=(0,n.simpleGit)(w)
539
+ 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
+ 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)}
544
+ 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||[]
540
546
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
541
- for(const r of t)try{m.info("Setup command",{command:r,worktreePath:b}),T(r,{cwd:b,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=[]
542
- for(const process of E){let t
547
+ 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
543
549
  const o=e.testRunIds?.[S.length]
544
- try{o?(await y.patch(`/api/projects/${v}/test-runs/${o}`,{worktreePath:b}),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:b,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)})
550
+ 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)})
545
551
  continue}const s=r(p,process,0,v,f),n=f?"tester":"auto-tester"
546
- try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:s,cwd:b,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:h,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
552
+ 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})
547
553
  if(!e.success)throw Error(e.error||"Spawn failed")
548
- 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:b,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
549
- 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:b}}}catch(e){const t=(0,i.getErrorMessage)(e)
554
+ 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)
550
556
  return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
551
557
  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
552
558
  if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
@@ -634,9 +640,9 @@ let v
634
640
  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))
635
641
  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})
636
642
  const w=(0,a.join)(c||process.cwd(),".alchemy_lab"),C=(0,a.join)(w,"spawn-stderr.log")
637
- let I
638
- 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})
639
- return E.unref(),f&&f(E.pid),{success:!0,pid:E.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
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)
640
646
  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})}
641
647
  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)
642
648
  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)
@@ -709,18 +715,18 @@ let h=null,p=null,f=null,m=null,y=!1,v=!1
709
715
  const w=c.join(s,".alchemy_lab","config.json")
710
716
  if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
711
717
  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)
712
- 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"
713
- i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:m&&(I.model=m)
714
- const k=c.join(s,".opencode","agent")
715
- await r.mkdir(k,{recursive:!0})
716
- const b=E||"anthropic/claude-opus-4-5-20251101"
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)
720
+ const b=c.join(s,".opencode","agent")
721
+ await r.mkdir(b,{recursive:!0})
722
+ const k=I||"anthropic/claude-opus-4-5-20251101"
717
723
  let j,T=null,S=null
718
724
  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})
719
725
  for(const e of T){if(!e.enabled)continue
720
- const t=this.generateAgentMarkdown(e,d,b,j),o=c.join(k,e.name+".md")
726
+ const t=this.generateAgentMarkdown(e,d,k,j),o=c.join(b,e.name+".md")
721
727
  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")
722
728
  else this.logger.warn("No projectId provided - agents must be configured via database")
723
- const P={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0}}}
729
+ const P={$schema:"https://opencode.ai/config.json",model:I,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0}}}
724
730
  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={}
725
731
  for(const e of T){if(!e.enabled)continue
726
732
  const t={}
@@ -728,7 +734,9 @@ for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
728
734
  t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=S?.metadata?.bashToolAgents||{}
729
735
  e.name in r&&(t.bash=!!r[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name)&&void 0===t.task&&(t.task=!1)
730
736
  let s=""
731
- j&&(s+=j.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),P.agent[e.name]={tools:t,...s?{prompt:s}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(P.agent).length})}const A=[]
737
+ j&&(s+=j.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),P.agent[e.name]={tools:t,...s?{prompt:s}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}P.agent.general={...P.agent.general,disable:!0},P.agent.explore={...P.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
738
+ const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${j?j.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`
739
+ await r.writeFile(c.join(b,"explore.md"),e,"utf8"),this.logger.info("Created explore agent override with research prompt"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(P.agent).length})}const A=[]
732
740
  if(A.push("file://"+c.join(s,".opencode","plugin","inject-session.js")),A.push("file://"+c.join(s,".opencode","plugin","event-hooks.js")),A.push("file://"+c.join(s,".opencode","plugin","anthropic-auth.js")),A.push("file://"+c.join(s,".opencode","plugin","system-logger.js")),A.length>0&&(P.plugin=A),await r.writeFile(g,JSON.stringify(P,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:g,plugins:A}),v&&!i)try{let e={}
733
741
  try{const t=await r.readFile(w,"utf-8")
734
742
  e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=C,await r.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPlugins:r}=await t.e(939).then(t.t.bind(t,9939,23)),o=await r(s,l??!1,this.logger,e)
@@ -736,10 +744,10 @@ o.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{syn
736
744
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
737
745
  const o=r(t(6982)),s=t(8824)
738
746
  var n
739
- !function(e){e.NOT_STARTED="not_started",e.POLLING="polling",e.AUTHENTICATED="authenticated",e.TIMEOUT="timeout",e.ERROR="error"}(n||(exports.PollingStatus=n={})),exports.ExternalAuthFlow=class{constructor(e,t,r=5e3,o=9e5){this.pollingStatus=n.NOT_STARTED,this.pollingStartTime=0,this.pollingIntervalId=null,this.tokenResult=null,this.pollingError=null,this.logger=e.child({component:"auth-flow"}),this.remoteServerUrl=t,this.pollingIntervalMs=r,this.maxPollingTimeMs=o}getAuthUrl(e){let t=this.remoteServerUrl
740
- t=t.includes("api.alchemylabcode.com")?t.replace("api.alchemylabcode.com","alchemylabcode.com"):t.match(/localhost:\d+/)?t.replace(/localhost:\d+/,"localhost:3000"):t.replace(/\/\/api\./g,"//")
741
- const r=`${t}/auth?mcp_code=${e}`
742
- return this.logger.info("Generated auth URL",{url:r,frontendUrl:t,backendUrl:this.remoteServerUrl}),r}getPollingUrl(e){const t=`${this.remoteServerUrl}/auth/check-auth?code=${e}`
747
+ !function(e){e.NOT_STARTED="not_started",e.POLLING="polling",e.AUTHENTICATED="authenticated",e.TIMEOUT="timeout",e.ERROR="error"}(n||(exports.PollingStatus=n={})),exports.ExternalAuthFlow=class{constructor(e,t,r=5e3,o=9e5){this.pollingStatus=n.NOT_STARTED,this.pollingStartTime=0,this.pollingIntervalId=null,this.tokenResult=null,this.pollingError=null,this.logger=e.child({component:"auth-flow"}),this.remoteServerUrl=t,this.pollingIntervalMs=r,this.maxPollingTimeMs=o}getAuthUrl(e){let r=this.remoteServerUrl
748
+ r=r.includes("api.alchemylabcode.com")?r.replace("api.alchemylabcode.com","alchemylabcode.com"):r.match(/localhost:\d+/)?r.replace(/localhost:\d+/,"localhost:3000"):r.replace(/\/\/api\./g,"//")
749
+ const o=t(6928).basename(process.cwd()),s=`${r}/login?mcp_code=${e}&project=${encodeURIComponent(o)}`
750
+ return this.logger.info("Generated auth URL",{url:s,frontendUrl:r,backendUrl:this.remoteServerUrl}),s}getPollingUrl(e){const t=`${this.remoteServerUrl}/auth/check-auth?code=${e}`
743
751
  return this.logger.info("Generated polling URL",{url:t}),t}async requestTemporaryCode(){const e=this.remoteServerUrl+"/auth/generate-code"
744
752
  this.logger.info("Requesting temporary code from remote server",{url:e})
745
753
  try{const t=await fetch(e)
@@ -814,20 +822,20 @@ for(var a=0;a<s.length;a++)o[s[a]]=1})(require("./"+e.u(t)))}
814
822
  var i={};(()=>{function t(){const e=[(0,g.join)(__dirname,"..","package.json"),(0,g.join)(__dirname,"..","..","package.json")]
815
823
  for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
816
824
  if(e.version)return e.version}catch{}return"unknown"}function r(){const e=t()
817
- 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 --proxy-opencode Proxy OpenCode API through action server (for remote UI)\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\n alchemy serve --proxy-opencode # Serve with OpenCode API proxy\n\nFor more information, visit: https://alchemylabcode.com/\n`)}function o(e){const t=(0,g.join)(e,E.ALCHEMY_DIR_NAME,"project.json")
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")
818
826
  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)
819
827
  setTimeout((()=>{clearInterval(o),r(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
820
828
  try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),o=r(t)
821
829
  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}})})
822
830
  if(i.ok){const e=await i.json()
823
831
  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})
824
- 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),p=i.includes("--proxy-opencode")
825
- let k
826
- const b=i.indexOf("--port");-1!==b&&i[b+1]&&(k=parseInt(i[b+1],10),isNaN(k)&&(console.error("\u274c Invalid port number"),process.exit(1)))
827
- const j=i.filter(((e,t)=>"--proxy-opencode"!==e&&"--port"!==e&&(-1===b||t!==b+1)))
828
- if(0===j.length)return void await async function(r={}){const i=process.cwd(),l=(0,g.join)(i,E.ALCHEMY_DIR_NAME)
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)
833
+ let p
834
+ 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
+ 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)
829
837
  await(0,u.mkdir)(l,{recursive:!0})
830
- const p=(0,g.join)(l,"server.log"),{createLogger:k}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=k({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),j=await a(b),T=new f.TokenManager(b,i,{remoteServerUrl:j,allowInteractiveAuth:!1})
838
+ 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})
831
839
  await T.initialize()
832
840
  let S=await T.getToken()
833
841
  if(!S?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
@@ -835,16 +843,16 @@ const e=await T.startAuthenticationFlow()
835
843
  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
836
844
  await async function(e,t,r,s){if(o(e))return
837
845
  console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
838
- const n=(0,g.join)(e,E.ALCHEMY_DIR_NAME)
846
+ const n=(0,g.join)(e,I.ALCHEMY_DIR_NAME)
839
847
  let i
840
848
  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")
841
- 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,b)
842
- const A=(0,g.join)(i,E.ALCHEMY_DIR_NAME,"project.json")
849
+ 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")
843
851
  let M
844
852
  try{const e=JSON.parse((0,d.readFileSync)(A,"utf-8"))
845
- if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,b,"cli-init-retry")
846
- 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{b.warn("Could not read project.json")}await async function(t){try{return void(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let r="latest"
847
- try{const e=(0,g.join)(t,E.ALCHEMY_DIR_NAME,"config.json")
853
+ if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,k,"cli-init-retry")
854
+ 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")
848
856
  if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
849
857
  t.opencodeVersion&&(r=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
850
858
  const o="latest"===r?"latest":"v"+r
@@ -856,30 +864,30 @@ const o=(0,g.join)(process.env.HOME||"/usr/local/bin",".local","bin")
856
864
  await(0,u.mkdir)(o,{recursive:!0})
857
865
  const s=(0,g.join)(o,"opencode")
858
866
  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)
859
- const O=t()
860
- 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"+O,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
861
- const _=r.port||await(0,I.findAvailablePort)(3456),x=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:j,port:_,authToken:P,proxyOpenCode:r.proxyOpenCode})
862
- try{await x.start(),r.proxyOpenCode&&console.log("\u{1f500} OpenCode API proxy enabled")
867
+ const _=t()
868
+ 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})
870
+ try{await x.start()
863
871
  let t=process.env.FRONTEND_URL
864
872
  if(!t)if(j.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
865
873
  else{try{const e=await fetch(j+"/health")
866
874
  if(e.ok){const r=await e.json()
867
875
  t=r?.data?.frontendUrl}}catch{}t||(t=j.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
868
- try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(b,j,P),r=new w.UnifiedLauncher(b,e,t),o=await r.launch({projectPath:i,settings:M?{projectId:M}:void 0})
869
- if(o.success&&o.port){o.pid&&x.trackChildPid(o.pid),console.log(" OpenCode serve ... port "+o.port)
870
- 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)),b.info("Wrote OpenCode config",{path:e,port:o.port})}else{if(!o.success)throw Error(o.error||"Launch failed")
871
- console.log(" OpenCode serve ... started")}}catch(e){b.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("")
872
- let o=!1,a=null
873
- const c=async()=>{o||(o=!0,a&&clearInterval(a),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
874
- process.on("SIGTERM",c),process.on("SIGINT",c)
875
- const{InteractiveShell:l}=await e.e(994).then(e.t.bind(e,5994,23)),u=new l({projectPath:i,logger:b,remoteServerUrl:j,authToken:P,projectId:M})
876
- a=setInterval((async()=>{try{if(await T.hasValidToken())return
876
+ 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
+ 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")
879
+ 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
+ let r=!1,o=null
881
+ const a=async()=>{r||(r=!0,o&&clearInterval(o),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
882
+ process.on("SIGTERM",a),process.on("SIGINT",a)
883
+ const{InteractiveShell:c}=await e.e(994).then(e.t.bind(e,5994,23)),l=new c({projectPath:i,logger:k,remoteServerUrl:j,authToken:P,projectId:M})
884
+ o=setInterval((async()=>{try{if(await T.hasValidToken())return
877
885
  console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
878
886
  const e=await T.startAuthenticationFlow()
879
887
  if(console.log("\u{1f310} "+e.authUrl),s(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(T)){const e=await T.getToken()
880
- e?.token&&(P=e.token,x.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){b.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await u.start(c)}catch(e){b.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({proxyOpenCode:p,port:k})
881
- const T=j[0].toLowerCase()
882
- switch("--help"!==T&&"-h"!==T||(r(),process.exit(0)),"--version"!==T&&"-v"!==T||(console.log("alchemy v"+t()),process.exit(0)),T){case"serve":await async function(e={}){const r=process.cwd()
888
+ e?.token&&(P=e.token,x.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){k.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await l.start(a)}catch(e){k.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({port:p})
889
+ const j=k[0].toLowerCase()
890
+ switch("--help"!==j&&"-h"!==j||(r(),process.exit(0)),"--version"!==j&&"-v"!==j||(console.log("alchemy v"+t()),process.exit(0)),j){case"serve":await async function(e={}){const r=process.cwd()
883
891
  o(r)||(console.log("\u{1f4c1} Current directory is not an AlchemyLab project."),console.log(""),console.log("\u{1f4a1} Run `alchemy` to initialize this project."),console.log(""),process.exit(1))
884
892
  const s=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),n=t()
885
893
  console.log(`\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 AlchemyLab Server v${n.padEnd(10)} \u2551\n\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n`),console.log("\u{1f4c1} Project: "+r)
@@ -887,13 +895,13 @@ const i=await a(s),c=new f.TokenManager(s,r,{remoteServerUrl:i,allowInteractiveA
887
895
  await c.initialize()
888
896
  const d=await c.getToken(),u=d?.token
889
897
  u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
890
- const g=e.port||await(0,I.findAvailablePort)(3456),h=new y.AlchemyActionServer(s,{projectPath:r,remoteServerUrl:i,port:g,authToken:u,proxyOpenCode:e.proxyOpenCode})
891
- try{await h.start(),console.log(""),console.log("\u2705 Action server running!"),e.proxyOpenCode&&console.log(" \u{1f500} OpenCode API proxy: enabled"),console.log(` Health: http://127.0.0.1:${h.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
892
- let t=!1
893
- const r=async()=>{t||(t=!0,console.log(""),console.log("Shutting down action server..."),await h.stop(),process.exit(0))}
894
- process.on("SIGTERM",r),process.on("SIGINT",r),await new Promise((()=>{}))}catch(e){s.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({proxyOpenCode:p,port:k})
898
+ const g=e.port||await(0,E.findAvailablePort)(3456),h=new y.AlchemyActionServer(s,{projectPath:r,remoteServerUrl:i,port:g,authToken:u})
899
+ 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
+ let e=!1
901
+ const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await h.stop(),process.exit(0))}
902
+ process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){s.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({port:p})
895
903
  break
896
904
  case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),r=await a(t),o=new f.TokenManager(t,e,{remoteServerUrl:r,allowInteractiveAuth:!1})
897
905
  await o.initialize(),await o.hasValidToken()?await o.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
898
906
  break
899
- default:console.error("\u274c Unknown command: "+T),console.log(""),r(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()
907
+ default:console.error("\u274c Unknown command: "+j),console.log(""),r(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()