alchemylab 2.3.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/994.js CHANGED
@@ -1,48 +1,49 @@
1
1
  "use strict"
2
- exports.ids=[994],exports.modules={75994(t,exports,e){var r=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}}
3
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.InteractiveShell=void 0
4
- const s=r(e(23785)),a=e(35317),i=e(83807),o=r(e(16928)),n=r(e(79896)),l=r(e(96895)),c=e(14139),u=e(44192),h=e(41152),d=e(76634),f=e(79414)
5
- exports.InteractiveShell=class{constructor(t){if(this.currentBranch="main",this.currentStatus=null,this.history=[],this.statusCache={map:new Map,ts:0},this.pathExecutableCache=null,this.projectPath=t.projectPath,this.currentSymlink=o.default.join(t.projectPath,c.ALCHEMY_DIR_NAME,"current"),this.currentDir=t.projectPath,this.logger=t.logger,this.historyFile=o.default.join(t.projectPath,c.ALCHEMY_DIR_NAME,"shell-history.txt"),this.projectId=t.projectId,this.remoteServerUrl=t.remoteServerUrl,this.authToken=t.authToken,t.remoteServerUrl){const e=(0,h.createMetricsCollector)(!1)
6
- this.apiClient=new u.BaseApiClient(t.remoteServerUrl,t.logger,e,t.authToken)}}async publishPr(t,e){if(!t||!/^req\d{6}$/.test(t))return void console.log(l.default.red("Usage: publish req###### [base-branch]"))
7
- if(!this.projectId||!this.remoteServerUrl)return void console.log(l.default.red("Project is not registered with the remote yet."))
2
+ exports.ids=[994],exports.modules={75994(t,exports,e){function r(t){if(!t.hasCode&&t.branch===t.home)return`${h.default.yellow("alchemy")}${h.default.gray(">")} `
3
+ const e=t.status&&t.branch!==t.home?` ${h.default.gray("|")} ${t.status}`:""
4
+ return`${h.default.cyan("[")}${h.default.green(t.branch)}${e}${h.default.cyan("]")} ${h.default.yellow("alchemy")}${h.default.gray(">")} `}function s(t){return t?'Type "help" for available commands. Commands run in the current branch directory.':'Type "help" for available commands.'}var a=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}}
5
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.InteractiveShell=void 0,exports.shellPrompt=r,exports.briefHelpLine=s
6
+ const i=a(e(23785)),o=e(35317),n=e(83807),l=a(e(16928)),c=a(e(79896)),h=a(e(96895)),u=e(14139),d=e(7202),f=e(44192),p=e(41152),g=e(76634),m=e(79414),y=e(52232)
7
+ exports.InteractiveShell=class{constructor(t){if(this.home="main",this.currentBranch="main",this.currentStatus=null,this.history=[],this.statusCache={map:new Map,ts:0},this.pathExecutableCache=null,this.projectPath=t.projectPath,this.currentSymlink=l.default.join(t.projectPath,u.ALCHEMY_DIR_NAME,"current"),this.currentDir=t.projectPath,this.logger=t.logger,this.historyFile=l.default.join(t.projectPath,u.ALCHEMY_DIR_NAME,"shell-history.txt"),this.projectId=t.projectId,this.remoteServerUrl=t.remoteServerUrl,this.authToken=t.authToken,this.hasCode=!1!==t.hasCode,t.remoteServerUrl){const e=(0,p.createMetricsCollector)(!1)
8
+ this.apiClient=new f.BaseApiClient(t.remoteServerUrl,t.logger,e,t.authToken)}}async publishPr(t,e){if(!t||!/^req\d{6}$/.test(t))return void console.log(h.default.red("Usage: publish req###### [base-branch]"))
9
+ if(!this.projectId||!this.remoteServerUrl)return void console.log(h.default.red("Project is not registered with the remote yet."))
8
10
  const r=this.projectId,s={"X-Client-Type":"alchemy-local-service"}
9
11
  this.authToken&&(s.Authorization=`Bearer ${this.authToken}`)
10
- try{console.log(l.default.gray(`Publishing ${t} as a GitHub PR (uses your local gh auth; no token is stored)...`))
11
- const a=await(0,d.publishRequestPr)({projectPath:this.projectPath,requestLocalId:t,base:e},{run:(0,d.defaultRunner)(),fetchRequest:async t=>{const e=await(this.apiClient?.get(`/api/projects/${r}/requests/${t}`))
12
+ try{console.log(h.default.gray(`Publishing ${t} as a GitHub PR (uses your local gh auth; no token is stored)...`))
13
+ const a=await(0,g.publishRequestPr)({projectPath:this.projectPath,requestLocalId:t,base:e},{run:(0,g.defaultRunner)(),fetchRequest:async t=>{const e=await(this.apiClient?.get(`/api/projects/${r}/requests/${t}`))
12
14
  return e?.data??e??null},fetchReceiptMarkdown:async t=>{const e=await fetch(`${this.remoteServerUrl}/api/projects/${r}/requests/${t}/receipt?format=md`,{headers:s})
13
15
  if(!e.ok)throw new Error(`Receipt fetch failed: HTTP ${e.status}`)
14
16
  return await e.text()}})
15
- console.log(l.default.green(`${a.created?"PR created":"Receipt added to the existing PR"}: ${a.prUrl}`)),a.statusPosted&&console.log(l.default.gray("Commit status alchemylab/gates set on the head commit."))}catch(t){console.log(l.default.red(t instanceof Error?t.message:String(t)))}}async linearImport(t){if(t&&/^[A-Za-z][A-Za-z0-9]{1,9}-\d{1,6}$/.test(t))try{const e=(0,f.getActionServerBaseUrl)(),r=await fetch(`${e}/api/connections/linear/import`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({issueKey:t.toUpperCase()})}),s=await r.json()
16
- s.success&&s.data?.requestId?console.log(l.default.green(`Imported ${s.data.issue} as ${s.data.requestId}.`)):console.log(l.default.red(s.error||"Import failed."))}catch(t){console.log(l.default.red(t instanceof Error?t.message:String(t)))}else console.log(l.default.red("Usage: linear <issue key> e.g. linear ENG-123"))}async start(t){return this.shutdownCallback=t,this.currentBranch="main",this.updateCurrentSymlink(),this.loadHistory(),this.rl=s.default.createInterface({input:process.stdin,output:process.stdout,prompt:this.buildPrompt(),historySize:500,removeHistoryDuplicates:!0,completer:t=>this.completer(t)}),this.history.length>0&&(this.rl.history=[...this.history].reverse()),this.printHelp(!0),this.rl.prompt(),new Promise(t=>{this.rl.on("line",async e=>{const r=e.trim()
17
+ console.log(h.default.green(`${a.created?"PR created":"Receipt added to the existing PR"}: ${a.prUrl}`)),a.statusPosted&&console.log(h.default.gray("Commit status alchemylab/gates set on the head commit."))}catch(t){console.log(h.default.red(t instanceof Error?t.message:String(t)))}}async linearImport(t){if(t&&/^[A-Za-z][A-Za-z0-9]{1,9}-\d{1,6}$/.test(t))try{const e=(0,m.getActionServerBaseUrl)(),r=await fetch(`${e}/api/connections/linear/import`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({issueKey:t.toUpperCase()})}),s=await r.json()
18
+ s.success&&s.data?.requestId?console.log(h.default.green(`Imported ${s.data.issue} as draft request ${s.data.requestId}. Start it in AlchemyLab when it is ready.`)):console.log(h.default.red(s.error||"Import failed."))}catch(t){console.log(h.default.red(t instanceof Error?t.message:String(t)))}else console.log(h.default.red("Usage: linear <issue key> e.g. linear ENG-123"))}async start(t){return this.shutdownCallback=t,this.home=await(0,y.resolveDefaultBranch)(this.projectPath).catch(()=>"main"),this.currentBranch=this.home,this.updateCurrentSymlink(),this.loadHistory(),this.rl=i.default.createInterface({input:process.stdin,output:process.stdout,prompt:this.buildPrompt(),historySize:500,removeHistoryDuplicates:!0,completer:t=>this.completer(t)}),this.history.length>0&&(this.rl.history=[...this.history].reverse()),this.printHelp(!0),this.rl.prompt(),new Promise(t=>{this.rl.on("line",async e=>{const r=e.trim()
17
19
  if(r){if(this.appendHistory(r),await this.processCommand(r))return this.rl.close(),void t()
18
20
  this.rl.prompt()}else this.rl.prompt()})
19
21
  let e,r=!1
20
- this.rl.on("SIGINT",()=>{r?(e&&clearTimeout(e),this.rl.close()):(r=!0,console.log(""),console.log(l.default.gray("Press Ctrl+C again to exit, or continue typing.")),this.rl.prompt(),e=setTimeout(()=>{r=!1},2e3))}),this.rl.on("close",async()=>{this.shutdownCallback&&await this.shutdownCallback(),t()})})}buildPrompt(){const t=l.default.green(this.currentBranch),e=this.currentStatus&&"main"!==this.currentBranch?` ${l.default.gray("|")} ${this.colorStatus(this.currentStatus)}`:""
21
- return`${l.default.cyan("[")}${t}${e}${l.default.cyan("]")} ${l.default.yellow("alchemy")}${l.default.gray(">")} `}colorStatus(t){switch(t){case"completed":return l.default.green(t)
22
- case"in_progress":return l.default.yellow(t)
23
- case"open":return l.default.blue(t)
24
- case"ready_for_review":return l.default.magenta("review")
25
- case"canceled":default:return l.default.gray(t)
26
- case"reverted":return l.default.red(t)}}async fetchRequestStatus(t){if(!this.apiClient||!this.projectId)return null
22
+ this.rl.on("SIGINT",()=>{r?(e&&clearTimeout(e),this.rl.close()):(r=!0,console.log(""),console.log(h.default.gray("Press Ctrl+C again to exit, or continue typing.")),this.rl.prompt(),e=setTimeout(()=>{r=!1},2e3))}),this.rl.on("close",async()=>{this.shutdownCallback&&await this.shutdownCallback(),t()})})}buildPrompt(){return r({branch:this.currentBranch,home:this.home,status:this.currentStatus?this.colorStatus(this.currentStatus):null,hasCode:this.hasCode})}colorStatus(t){switch(t){case"completed":return h.default.green(t)
23
+ case"in_progress":return h.default.yellow(t)
24
+ case"open":return h.default.blue(t)
25
+ case"ready_for_review":return h.default.magenta("review")
26
+ case"canceled":default:return h.default.gray(t)
27
+ case"reverted":return h.default.red(t)}}async fetchRequestStatus(t){if(!this.apiClient||!this.projectId)return null
27
28
  try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/requests/${t}`),r=e?.data||e
28
29
  return r?.status||null}catch{return null}}async fetchBulkStatuses(){if(Date.now()-this.statusCache.ts<1e4&&this.statusCache.map.size>0)return this.statusCache.map
29
30
  if(!this.apiClient||!this.projectId)return new Map
30
31
  try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/requests?limit=100&sortBy=updated&sortDirection=desc`),e=t?.data||t||[],r=new Map
31
32
  for(const t of Array.isArray(e)?e:[])t.localId&&t.status&&r.set(t.localId,t.status)
32
- return this.statusCache={map:r,ts:Date.now()},r}catch{return this.statusCache.map}}formatRequestWithStatus(t,e,r){return`${r?l.default.green(t):t}${e?` ${this.colorStatus(e)}`:""}`}updatePrompt(){this.rl.setPrompt(this.buildPrompt())}getPathExecutables(){if(this.pathExecutableCache)return this.pathExecutableCache
33
- const t=new Set,e=(process.env.PATH||"").split(o.default.delimiter)
34
- for(const r of e)try{if(!n.default.existsSync(r))continue
35
- for(const e of n.default.readdirSync(r))t.add(e)}catch{}return this.pathExecutableCache=Array.from(t).sort(),this.pathExecutableCache}completer(t){const e=["br","branch","logs","pwd","cd ","publish ","linear ","help","exit","quit"],r=t.includes(" "),s=t.indexOf(" "),a=r?t.slice(0,s).toLowerCase():""
33
+ return this.statusCache={map:r,ts:Date.now()},r}catch{return this.statusCache.map}}formatRequestWithStatus(t,e,r){return`${r?h.default.green(t):t}${e?` ${this.colorStatus(e)}`:""}`}updatePrompt(){this.rl.setPrompt(this.buildPrompt())}getPathExecutables(){if(this.pathExecutableCache)return this.pathExecutableCache
34
+ const t=new Set,e=(process.env.PATH||"").split(l.default.delimiter)
35
+ for(const r of e)try{if(!c.default.existsSync(r))continue
36
+ for(const e of c.default.readdirSync(r))t.add(e)}catch{}return this.pathExecutableCache=Array.from(t).sort(),this.pathExecutableCache}completer(t){const e=["br","branch","logs","pwd","cd ","publish ","linear ","help","exit","quit"],r=t.includes(" "),s=t.indexOf(" "),a=r?t.slice(0,s).toLowerCase():""
36
37
  if(r&&("br"===a||"branch"===a))return setImmediate(()=>this.rl.write("\n")),[[],""]
37
38
  if(!r){const r=e.filter(e=>e.startsWith(t)),s=t?this.getPathExecutables().filter(e=>e.startsWith(t)):[],a=[...new Set([...r,...s])]
38
39
  if(a.length>0)return[a,t]
39
- if(!t)return[e,t]}const i=t.lastIndexOf(" "),l=i>=0?t.slice(i+1):t,c=i>=0?t.slice(0,i+1):""
40
- try{const e=l.includes(o.default.sep)||l.includes("/"),r=e?o.default.resolve(this.currentDir,o.default.dirname(l)):this.currentDir,s=e?o.default.basename(l):l,a=e?o.default.dirname(l)+"/":""
41
- if(!n.default.existsSync(r))return[[],t]
42
- const i=n.default.readdirSync(r,{withFileTypes:!0}).filter(t=>t.name.startsWith(s)&&!t.name.startsWith(".")).map(t=>{const e=t.isDirectory()?t.name+"/":t.name
43
- return c+a+e})
44
- if(i.length>0)return[i,t]}catch{}return[[],t]}async detectCurrentBranch(){try{const t=(0,i.simpleGit)(this.currentDir),e=(await t.branch()).current||"main"
45
- this.currentBranch=this.extractRequestId(e)||e}catch{this.currentBranch="main"}}async processCommand(t){const e=t.split(/\s+/),r=e[0].toLowerCase(),s=e.slice(1)
40
+ if(!t)return[e,t]}const i=t.lastIndexOf(" "),o=i>=0?t.slice(i+1):t,n=i>=0?t.slice(0,i+1):""
41
+ try{const e=o.includes(l.default.sep)||o.includes("/"),r=e?l.default.resolve(this.currentDir,l.default.dirname(o)):this.currentDir,s=e?l.default.basename(o):o,a=e?l.default.dirname(o)+"/":""
42
+ if(!c.default.existsSync(r))return[[],t]
43
+ const i=c.default.readdirSync(r,{withFileTypes:!0}).filter(t=>t.name.startsWith(s)&&!t.name.startsWith(".")).map(t=>{const e=t.isDirectory()?t.name+"/":t.name
44
+ return n+a+e})
45
+ if(i.length>0)return[i,t]}catch{}return[[],t]}async detectCurrentBranch(){try{const t=(0,n.simpleGit)(this.currentDir),e=(await t.branch()).current||this.home
46
+ this.currentBranch=this.extractRequestId(e)||e}catch{this.currentBranch=this.home}}async processCommand(t){const e=t.split(/\s+/),r=e[0].toLowerCase(),s=e.slice(1)
46
47
  switch(r){case"exit":case"quit":return this.shutdownCallback&&await this.shutdownCallback(),!0
47
48
  case"help":return this.printHelp(!1),!1
48
49
  case"br":case"branch":return await this.listBranches(s.length>0?s.join(" "):void 0),!1
@@ -52,71 +53,71 @@ case"cd":return this.changeDirectory(s[0]||"~"),!1
52
53
  case"publish":return await this.publishPr(s[0],s[1]),!1
53
54
  case"linear":return await this.linearImport(s[0]),!1
54
55
  default:return this.executeCommand(t),!1}}changeDirectory(t){try{let e
55
- if(e="~"===t||""===t?this.currentSymlink:o.default.isAbsolute(t)?t:o.default.resolve(this.currentDir,t),!n.default.existsSync(e)||!n.default.statSync(e).isDirectory())return void console.log(l.default.red(`cd: no such directory: ${t}`))
56
- this.currentDir=e}catch(t){console.log(l.default.red(`cd: ${t.message}`))}}async tailLogs(t){const r=o.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"server.log")
57
- if(!n.default.existsSync(r))return void console.log(l.default.gray("No log file found yet. Logs will appear once the server processes requests."))
56
+ if(e="~"===t||""===t?this.currentSymlink:l.default.isAbsolute(t)?t:l.default.resolve(this.currentDir,t),!c.default.existsSync(e)||!c.default.statSync(e).isDirectory())return void console.log(h.default.red(`cd: no such directory: ${t}`))
57
+ this.currentDir=e}catch(t){console.log(h.default.red(`cd: ${t.message}`))}}async tailLogs(t){const r=l.default.join(this.projectPath,u.ALCHEMY_DIR_NAME,"server.log")
58
+ if(!c.default.existsSync(r))return void console.log(h.default.gray("No log file found yet. Logs will appear once the server processes requests."))
58
59
  const s=t[0]&&/^\d+$/.test(t[0])?parseInt(t[0],10):50
59
- console.log(l.default.gray(`Tailing ${r} (last ${s} lines, Ctrl+C to stop)`))
60
+ console.log(h.default.gray(`Tailing ${r} (last ${s} lines, Ctrl+C to stop)`))
60
61
  const{spawn:a}=await Promise.resolve().then(()=>e.t(35317,23)),i=a("tail",["-n",String(s),"-f",r],{stdio:["ignore","inherit","inherit"]})
61
62
  return new Promise(t=>{const e=process.stdin,r=e.isRaw
62
63
  this.rl.pause(),e.setRawMode(!0),e.resume()
63
64
  const s=()=>{e.removeListener("data",a)
64
65
  try{i.kill("SIGTERM")}catch{}try{e.setRawMode(r??!1)}catch{}this.rl.resume()},a=t=>{3!==t[0]&&"q"!==t.toString()||i.kill("SIGTERM")}
65
66
  e.on("data",a),i.on("close",()=>{s(),t()})})}updateCurrentSymlink(){try{let t
66
- if("main"===this.currentBranch)t=this.projectPath
67
- else{const e=o.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"worktrees",this.currentBranch)
68
- t=n.default.existsSync(e)?e:this.projectPath}try{n.default.unlinkSync(this.currentSymlink)}catch{}n.default.symlinkSync(t,this.currentSymlink),this.currentDir=this.currentSymlink}catch(t){this.logger.warn("Failed to update current symlink",{error:t.message})}}getVirtualCwd(){return this.currentDir}executeCommand(t){try{(0,a.execSync)(t,{cwd:this.currentDir,stdio:"inherit",env:{...process.env,PWD:this.currentDir,GIT_DIR:void 0,GIT_WORK_TREE:void 0}})}catch(t){void 0!==t.status&&null!==t.status||console.log(l.default.red(`Error: ${t.message}`))}}getWorktreeRequestIds(){const t=o.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"worktrees"),e=new Set
69
- if(n.default.existsSync(t))for(const r of n.default.readdirSync(t,{withFileTypes:!0}))r.isDirectory()&&e.add(r.name)
67
+ if(this.currentBranch===this.home)t=this.projectPath
68
+ else{const e=(0,d.requestWorktreePath)(this.projectPath,this.currentBranch)
69
+ t=c.default.existsSync(e)?e:this.projectPath}try{c.default.unlinkSync(this.currentSymlink)}catch{}c.default.symlinkSync(t,this.currentSymlink),this.currentDir=this.currentSymlink}catch(t){this.logger.warn("Failed to update current symlink",{error:t.message})}}getVirtualCwd(){return this.currentDir}executeCommand(t){try{(0,o.execSync)(t,{cwd:this.currentDir,stdio:"inherit",env:{...process.env,PWD:this.currentDir,GIT_DIR:void 0,GIT_WORK_TREE:void 0}})}catch(t){void 0!==t.status&&null!==t.status||console.log(h.default.red(`Error: ${t.message}`))}}getWorktreeRequestIds(){const t=(0,d.requestWorktreesDir)(this.projectPath),e=new Set
70
+ if(c.default.existsSync(t))for(const r of c.default.readdirSync(t,{withFileTypes:!0}))r.isDirectory()&&e.add(r.name)
70
71
  return e}extractRequestId(t){const e=t.match(/^(?:requests\/)?(req\d+)/)
71
- return e?e[1]:null}async listBranches(t){try{const e=(0,i.simpleGit)(this.projectPath),r=await e.branch(),s=this.getWorktreeRequestIds(),a=await this.fetchBulkStatuses(),o=new Set,n=[],c=[]
72
+ return e?e[1]:null}async listBranches(t){try{const e=(0,n.simpleGit)(this.projectPath),r=await e.branch(),s=this.getWorktreeRequestIds(),a=await this.fetchBulkStatuses(),i=new Set,o=[],l=[]
72
73
  for(const t of r.all){if(t.startsWith("remotes/"))continue
73
- if("main"===t)continue
74
+ if(t===this.home)continue
74
75
  const e=this.extractRequestId(t)
75
- e&&!o.has(e)&&(o.add(e),n.push(e))}for(const t of s)o.has(t)||(o.add(t),n.push(t))
76
- n.sort((t,e)=>t.localeCompare(e))
77
- for(const t of n){const e=this.currentBranch===t||this.currentBranch===`requests/${t}`
78
- c.push(this.formatRequestWithStatus(t,a.get(t),e))}if(n.push("main"),c.push("main"===this.currentBranch?l.default.green("main"):"main"),0===n.length)return void console.log(l.default.gray("No branches found."))
79
- if(t&&n.includes(t))return void(t!==this.currentBranch&&await this.switchBranch(t))
80
- let u=n.indexOf(this.currentBranch)
81
- u<0&&(u=n.length-1)
82
- const h=await this.selectFromList(n,"Select branch:",c,t?0:u,!0,t)
83
- this.rl.pause(),h&&h!==this.currentBranch?await this.switchBranch(h):h||console.log(l.default.gray("Cancelled.")),this.rl.resume()}catch(t){console.log(l.default.red(`Failed to list branches: ${t.message}`))}}async ensureWorktree(t,e){const r=o.default.join(this.projectPath,c.ALCHEMY_DIR_NAME,"worktrees"),s=o.default.join(r,t)
84
- if(n.default.existsSync(s)&&n.default.statSync(s).isDirectory())return s
85
- console.log(l.default.gray(`Creating worktree for ${t}...`)),n.default.mkdirSync(r,{recursive:!0})
86
- const a=(0,i.simpleGit)(this.projectPath)
87
- return await a.raw(["worktree","add",s,e]),console.log(l.default.gray(`Worktree created at ${s}`)),s}async selectFromList(t,e,r,s,a,i){const o=t,n=r||t
76
+ e&&!i.has(e)&&(i.add(e),o.push(e))}for(const t of s)i.has(t)||(i.add(t),o.push(t))
77
+ o.sort((t,e)=>t.localeCompare(e))
78
+ for(const t of o){const e=this.currentBranch===t||this.currentBranch===`requests/${t}`
79
+ l.push(this.formatRequestWithStatus(t,a.get(t),e))}if(o.push(this.home),l.push(this.currentBranch===this.home?h.default.green(this.home):this.home),0===o.length)return void console.log(h.default.gray("No branches found."))
80
+ if(t&&o.includes(t))return void(t!==this.currentBranch&&await this.switchBranch(t))
81
+ let c=o.indexOf(this.currentBranch)
82
+ c<0&&(c=o.length-1)
83
+ const u=await this.selectFromList(o,"Select branch:",l,t?0:c,!0,t)
84
+ this.rl.pause(),u&&u!==this.currentBranch?await this.switchBranch(u):u||console.log(h.default.gray("Cancelled.")),this.rl.resume()}catch(t){console.log(h.default.red(`Failed to list branches: ${t.message}`))}}async ensureWorktree(t,e){const r=(0,d.requestWorktreesDir)(this.projectPath),s=l.default.join(r,t)
85
+ if(c.default.existsSync(s)&&c.default.statSync(s).isDirectory())return s
86
+ console.log(h.default.gray(`Creating worktree for ${t}...`)),c.default.mkdirSync(r,{recursive:!0})
87
+ const a=(0,n.simpleGit)(this.projectPath)
88
+ return await a.raw(["worktree","add",s,e]),console.log(h.default.gray(`Worktree created at ${s}`)),s}async selectFromList(t,e,r,s,a,i){const o=t,n=r||t
88
89
  return new Promise(t=>{let r=0
89
- const c=process.stdin,u=c.isRaw
90
- let h=!1,d=i||"",f=o.map((t,e)=>e)
90
+ const l=process.stdin,c=l.isRaw
91
+ let u=!1,d=i||"",f=o.map((t,e)=>e)
91
92
  null!=s&&s>=0&&s<o.length&&(r=s)
92
93
  const p=()=>{if(!d)return void(f=o.map((t,e)=>e))
93
94
  const t=d.toLowerCase()
94
95
  let e=o.map((t,e)=>({item:t,i:e})).filter(({item:e})=>e.toLowerCase().startsWith(t))
95
- 0===e.length&&/^\d+$/.test(t)&&(e=o.map((t,e)=>({item:t,i:e})).filter(({item:e})=>e.replace(/\D/g,"").endsWith(t))),0===e.length&&(e=o.map((t,e)=>({item:t,i:e})).filter(({item:e})=>e.toLowerCase().includes(t))),f=e.map(t=>t.i)},g=process.stdout.rows||24,y=Math.max(3,g-4)
96
- let m=0,w=0,$=0
96
+ 0===e.length&&/^\d+$/.test(t)&&(e=o.map((t,e)=>({item:t,i:e})).filter(({item:e})=>e.replace(/\D/g,"").endsWith(t))),0===e.length&&(e=o.map((t,e)=>({item:t,i:e})).filter(({item:e})=>e.toLowerCase().includes(t))),f=e.map(t=>t.i)},g=process.stdout.rows||24,m=Math.max(3,g-4)
97
+ let y=0,w=0,$=0
97
98
  this.rl.pause()
98
- const S=()=>{const t=f.length,s=t>y
99
- s?r<m?m=r:r>=m+y&&(m=r-y+1):m=0
99
+ const S=()=>{const t=f.length,s=t>m
100
+ s?r<y?y=r:r>=y+m&&(y=r-m+1):y=0
100
101
  const i=(()=>{const t=f.length
101
102
  if(0===t)return 1
102
- const e=t>y
103
- let r=e?Math.min(y,t-m):t
104
- return e&&m>0&&r++,e&&m+y<t&&r++,r})(),o=i+1
103
+ const e=t>m
104
+ let r=e?Math.min(m,t-y):t
105
+ return e&&y>0&&r++,e&&y+m<t&&r++,r})(),o=i+1
105
106
  C&&$>1&&process.stdout.write(`\r\x1b[${$-1}A\r`),o>w&&(w=o),$=w
106
- const c=w-o
107
- for(let t=0;t<c;t++)process.stdout.write("\x1b[2K\n")
108
- if(0===t)process.stdout.write(`\x1b[2K ${l.default.gray("no matches")}\n`)
109
- else{const e=s&&m>0,a=s&&m+y<t,i=s?Math.min(m+y,t):t,o=s?m:0
110
- e&&process.stdout.write(`\x1b[2K ${l.default.gray(`\u25b2 ${m} more`)}\n`)
111
- for(let t=o;t<i;t++){const e=f[t],s=t===r?l.default.cyan("\u276f "):" ",a=t===r?l.default.cyan(n[e]):n[e]
112
- process.stdout.write(`\x1b[2K${s}${a}\n`)}a&&process.stdout.write(`\x1b[2K ${l.default.gray(`\u25bc ${t-i} more`)}\n`)}a&&d?process.stdout.write(`\x1b[2K${l.default.gray(">")} ${l.default.yellow(d)}${l.default.gray("_")}`):a?process.stdout.write(`\x1b[2K${l.default.gray(">")} ${l.default.gray("type to filter...")}`):process.stdout.write(`\x1b[2K${l.default.gray(e)}`),C=!0}
107
+ const l=w-o
108
+ for(let t=0;t<l;t++)process.stdout.write("\x1b[2K\n")
109
+ if(0===t)process.stdout.write(`\x1b[2K ${h.default.gray("no matches")}\n`)
110
+ else{const e=s&&y>0,a=s&&y+m<t,i=s?Math.min(y+m,t):t,o=s?y:0
111
+ e&&process.stdout.write(`\x1b[2K ${h.default.gray(`\u25b2 ${y} more`)}\n`)
112
+ for(let t=o;t<i;t++){const e=f[t],s=t===r?h.default.cyan("\u276f "):" ",a=t===r?h.default.cyan(n[e]):n[e]
113
+ process.stdout.write(`\x1b[2K${s}${a}\n`)}a&&process.stdout.write(`\x1b[2K ${h.default.gray(`\u25bc ${t-i} more`)}\n`)}a&&d?process.stdout.write(`\x1b[2K${h.default.gray(">")} ${h.default.yellow(d)}${h.default.gray("_")}`):a?process.stdout.write(`\x1b[2K${h.default.gray(">")} ${h.default.gray("type to filter...")}`):process.stdout.write(`\x1b[2K${h.default.gray(e)}`),C=!0}
113
114
  d&&(p(),r=0)
114
115
  let C=!1
115
- S(),c.setRawMode(!0),c.resume()
116
- const b=()=>{if(!h){h=!0,c.removeListener("data",v)
117
- try{c.setRawMode(u??!1)}catch{}C&&$>1&&process.stdout.write(`\r\x1b[${$-1}A\r`),process.stdout.write("\x1b[J"),this.rl.resume()}},v=e=>{if(!h)try{const s=e.toString()
118
- "\x1b[A"===s?f.length>0&&(r=Math.max(0,r-1),S()):"\x1b[B"===s?f.length>0&&(r=Math.min(f.length-1,r+1),S()):"\r"===s||"\n"===s?f.length>0&&(b(),t(o[f[r]])):"\x1b"===s||"\x03"===s?(b(),t(null)):a&&"\x7f"===s?d.length>0&&(d=d.slice(0,-1),p(),r=0,m=0,S()):a&&1===s.length&&s>=" "&&s<="~"&&(d+=s,p(),r=0,m=0,S())}catch{b(),t(null)}}
119
- c.on("data",v)})}findMatchingBranches(t,e,r){const s=new Set
116
+ S(),l.setRawMode(!0),l.resume()
117
+ const b=()=>{if(!u){u=!0,l.removeListener("data",v)
118
+ try{l.setRawMode(c??!1)}catch{}C&&$>1&&process.stdout.write(`\r\x1b[${$-1}A\r`),process.stdout.write("\x1b[J"),this.rl.resume()}},v=e=>{if(!u)try{const s=e.toString()
119
+ "\x1b[A"===s?f.length>0&&(r=Math.max(0,r-1),S()):"\x1b[B"===s?f.length>0&&(r=Math.min(f.length-1,r+1),S()):"\r"===s||"\n"===s?f.length>0&&(b(),t(o[f[r]])):"\x1b"===s||"\x03"===s?(b(),t(null)):a&&"\x7f"===s?d.length>0&&(d=d.slice(0,-1),p(),r=0,y=0,S()):a&&1===s.length&&s>=" "&&s<="~"&&(d+=s,p(),r=0,y=0,S())}catch{b(),t(null)}}
120
+ l.on("data",v)})}findMatchingBranches(t,e,r){const s=new Set
120
121
  for(const t of e){const e=this.extractRequestId(t)
121
122
  e&&s.add(e)}for(const t of r)s.add(t)
122
123
  const a=Array.from(s)
@@ -124,15 +125,15 @@ if(s.has(t))return[t]
124
125
  const i=a.filter(e=>e.startsWith(t))
125
126
  if(i.length>0)return i.sort()
126
127
  if(/^\d+$/.test(t)){const e=a.filter(e=>e.replace(/\D/g,"").endsWith(t))
127
- if(e.length>0)return e.sort()}return[]}async switchBranch(t){try{if("main"===t)return this.currentBranch="main",this.currentStatus=null,this.updateCurrentSymlink(),this.updatePrompt(),console.log(l.default.green("Switched to main")),void console.log(l.default.gray(` ${this.currentDir}`))
128
- const e=(0,i.simpleGit)(this.projectPath),r=await e.branch(),s=this.getWorktreeRequestIds(),a=this.findMatchingBranches(t,r.all,s)
129
- if(0===a.length)return console.log(l.default.red(`No branch matching: ${t}`)),void console.log(l.default.gray(' Use "branches" to see available branches'))
130
- let o
131
- if(1===a.length)o=a[0]
128
+ if(e.length>0)return e.sort()}return[]}async switchBranch(t){try{if(t===this.home||"main"===t)return this.currentBranch=this.home,this.currentStatus=null,this.updateCurrentSymlink(),this.updatePrompt(),console.log(h.default.green(`Switched to ${this.home}`)),void console.log(h.default.gray(` ${this.currentDir}`))
129
+ const e=(0,n.simpleGit)(this.projectPath),r=await e.branch(),s=this.getWorktreeRequestIds(),a=this.findMatchingBranches(t,r.all,s)
130
+ if(0===a.length)return console.log(h.default.red(`No branch matching: ${t}`)),void console.log(h.default.gray(' Use "branches" to see available branches'))
131
+ let i
132
+ if(1===a.length)i=a[0]
132
133
  else{const e=await this.fetchBulkStatuses(),r=a.map(t=>this.formatRequestWithStatus(t,e.get(t),!1)),s=await this.selectFromList(a,`Multiple matches for "${t}" \u2014 select one:`,r,void 0,!0)
133
- if(!s)return void console.log(l.default.gray("Cancelled."))
134
- o=s}const n=r.all.includes(`requests/${o}`)?`requests/${o}`:o
135
- await this.ensureWorktree(o,n),this.currentBranch=o,this.updateCurrentSymlink(),this.currentStatus=await this.fetchRequestStatus(o),this.updatePrompt()
136
- const c=this.currentStatus?` ${l.default.gray("|")} ${this.colorStatus(this.currentStatus)}`:""
137
- console.log(l.default.green(`Switched to ${o}`)+c),console.log(l.default.gray(` ${this.getVirtualCwd()}`))}catch(t){console.log(l.default.red(`Failed to switch: ${t.message}`))}}printHelp(t){t?(console.log(""),console.log(l.default.gray('Type "help" for available commands. Commands run in the current branch directory.')),console.log("")):(console.log(""),console.log(l.default.bold("Interactive Shell Commands:")),console.log(` ${l.default.yellow("branch")} / ${l.default.yellow("br")} Browse and switch branches`),console.log(` ${l.default.yellow("logs")} ${l.default.gray("[N]")} Tail action server log (Ctrl+C to stop, N = lines of history)`),console.log(` ${l.default.yellow("cd")} ${l.default.gray("<dir>")} Change directory within current branch`),console.log(` ${l.default.yellow("pwd")} Show current working directory`),console.log(` ${l.default.yellow("publish")} ${l.default.gray("<req id>")} Push the request branch and open a GitHub PR with its receipt`),console.log(` ${l.default.yellow("linear")} ${l.default.gray("<ENG-123>")} Import a Linear issue as a request`),console.log(` ${l.default.yellow("help")} Show this help`),console.log(` ${l.default.yellow("exit")} / ${l.default.yellow("quit")} Stop the server and exit`),console.log(""),console.log(l.default.gray(" Any other input is executed as a shell command in the current directory.")),console.log(""))}loadHistory(){try{if(n.default.existsSync(this.historyFile)){const t=n.default.readFileSync(this.historyFile,"utf-8")
138
- this.history=t.split("\n").filter(t=>""!==t.trim()),this.history.length>500&&(this.history=this.history.slice(-500))}}catch{}}appendHistory(t){try{this.history.push(t),n.default.appendFileSync(this.historyFile,t+"\n")}catch{}}}}}
134
+ if(!s)return void console.log(h.default.gray("Cancelled."))
135
+ i=s}const o=r.all.includes(`requests/${i}`)?`requests/${i}`:i
136
+ await this.ensureWorktree(i,o),this.currentBranch=i,this.updateCurrentSymlink(),this.currentStatus=await this.fetchRequestStatus(i),this.updatePrompt()
137
+ const l=this.currentStatus?` ${h.default.gray("|")} ${this.colorStatus(this.currentStatus)}`:""
138
+ console.log(h.default.green(`Switched to ${i}`)+l),console.log(h.default.gray(` ${this.getVirtualCwd()}`))}catch(t){console.log(h.default.red(`Failed to switch: ${t.message}`))}}printHelp(t){t?(console.log(""),console.log(h.default.gray(s(this.hasCode))),console.log("")):(console.log(""),console.log(h.default.bold("Interactive Shell Commands:")),console.log(` ${h.default.yellow("branch")} / ${h.default.yellow("br")} Browse and switch branches`),console.log(` ${h.default.yellow("logs")} ${h.default.gray("[N]")} Tail action server log (Ctrl+C to stop, N = lines of history)`),console.log(` ${h.default.yellow("cd")} ${h.default.gray("<dir>")} Change directory within current branch`),console.log(` ${h.default.yellow("pwd")} Show current working directory`),console.log(` ${h.default.yellow("publish")} ${h.default.gray("<req id>")} Push the request branch and open a GitHub PR with its receipt`),console.log(` ${h.default.yellow("linear")} ${h.default.gray("<ENG-123>")} Import a Linear issue as a draft request`),console.log(` ${h.default.yellow("help")} Show this help`),console.log(` ${h.default.yellow("exit")} / ${h.default.yellow("quit")} Stop the server and exit`),console.log(""),console.log(h.default.gray(" Any other input is executed as a shell command in the current directory.")),console.log(""))}loadHistory(){try{if(c.default.existsSync(this.historyFile)){const t=c.default.readFileSync(this.historyFile,"utf-8")
139
+ this.history=t.split("\n").filter(t=>""!==t.trim()),this.history.length>500&&(this.history=this.history.slice(-500))}}catch{}}appendHistory(t){try{this.history.push(t),c.default.appendFileSync(this.historyFile,t+"\n")}catch{}}}}}