@sma1lboy/rove 0.9.281-next.31 → 0.9.281

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/cli/chunk-01tghgf3.js +8 -0
  2. package/dist/cli/{chunk-jk4f0xsh.js → chunk-03fv92ya.js} +1 -1
  3. package/dist/cli/{chunk-7aaw17vr.js → chunk-08mb3qss.js} +1 -1
  4. package/dist/cli/{chunk-y7mjhjby.js → chunk-0gbg7bg8.js} +1 -1
  5. package/dist/cli/{chunk-zv0rrhck.js → chunk-0nkk13z9.js} +1 -1
  6. package/dist/cli/{chunk-zp0q931y.js → chunk-14tevt5v.js} +1 -1
  7. package/dist/cli/{chunk-1g6vhgfe.js → chunk-16e6jqq6.js} +1 -1
  8. package/dist/cli/{chunk-3hxm56h6.js → chunk-1a57ejnh.js} +1 -1
  9. package/dist/cli/{chunk-4z1a65bz.js → chunk-32y7f5dr.js} +1 -1
  10. package/dist/cli/{chunk-j54c5a0a.js → chunk-3m0ydze9.js} +1 -1
  11. package/dist/cli/{chunk-12wn9jtf.js → chunk-3pgp6z3k.js} +1 -1
  12. package/dist/cli/{chunk-amq030gb.js → chunk-3qj9yw5c.js} +2 -2
  13. package/dist/cli/chunk-453qpfae.js +2 -0
  14. package/dist/cli/{chunk-gk8efsx0.js → chunk-46qs744w.js} +1 -1
  15. package/dist/cli/{chunk-am5cpb9n.js → chunk-4vkrv4eb.js} +1 -1
  16. package/dist/cli/{chunk-hfvzvj91.js → chunk-4yr1534j.js} +1 -1
  17. package/dist/cli/{chunk-byh32qnx.js → chunk-5swmvt20.js} +1 -1
  18. package/dist/cli/{chunk-j8d9sk5j.js → chunk-6wt08a28.js} +1 -1
  19. package/dist/cli/{chunk-f262ywvg.js → chunk-7911jkq6.js} +1 -1
  20. package/dist/cli/chunk-8ng1756g.js +3 -0
  21. package/dist/cli/{chunk-ebdp0z1d.js → chunk-95kk75wh.js} +1 -1
  22. package/dist/cli/{chunk-6mk6ceb6.js → chunk-9qpea48c.js} +2 -2
  23. package/dist/cli/chunk-9vwb9f7q.js +7 -0
  24. package/dist/cli/{chunk-nqwza8b0.js → chunk-a5xbsvx2.js} +1 -1
  25. package/dist/cli/{chunk-t7dyf50m.js → chunk-ap3fgbbt.js} +1 -1
  26. package/dist/cli/{chunk-5kgq379z.js → chunk-b5fdqynx.js} +1 -1
  27. package/dist/cli/{chunk-x85rcep5.js → chunk-bbq6wc47.js} +1 -1
  28. package/dist/cli/{chunk-np398f2a.js → chunk-bbt217yd.js} +1 -1
  29. package/dist/cli/{chunk-w2tcb3rn.js → chunk-bm6qhcck.js} +1 -1
  30. package/dist/cli/{chunk-txvjg1g9.js → chunk-btxfcc77.js} +3 -3
  31. package/dist/cli/chunk-c9ptxthj.js +2 -0
  32. package/dist/cli/{chunk-zkby63ej.js → chunk-cs410j8b.js} +1 -1
  33. package/dist/cli/{chunk-rvjnafyt.js → chunk-cx78tyg1.js} +1 -1
  34. package/dist/cli/{chunk-4drm07mp.js → chunk-cx8wnz54.js} +1 -1
  35. package/dist/cli/{chunk-t2p22ww7.js → chunk-ejfs22aq.js} +1 -1
  36. package/dist/cli/chunk-f23manyf.js +8 -0
  37. package/dist/cli/{chunk-7nnpp89t.js → chunk-ga82ras9.js} +1 -1
  38. package/dist/cli/{chunk-f7egph8g.js → chunk-gzkes5bw.js} +1 -1
  39. package/dist/cli/{chunk-f0sahrfw.js → chunk-hmy01yg2.js} +16 -14
  40. package/dist/cli/{chunk-3a7x7jg7.js → chunk-hnxs5shy.js} +16 -17
  41. package/dist/cli/{chunk-bw2y7670.js → chunk-j2e7jgh8.js} +1 -1
  42. package/dist/cli/{chunk-qc9sj5wh.js → chunk-j5y7hhc2.js} +1 -1
  43. package/dist/cli/{chunk-qzhfs668.js → chunk-jar50zcp.js} +1 -1
  44. package/dist/cli/{chunk-r98h6b42.js → chunk-k2rmj0v3.js} +2 -2
  45. package/dist/cli/{chunk-bv050ssb.js → chunk-k3h4cpdy.js} +1 -1
  46. package/dist/cli/{chunk-wz307hf6.js → chunk-k9xabf0t.js} +1 -1
  47. package/dist/cli/{chunk-02x0ahxk.js → chunk-kp43j420.js} +1 -1
  48. package/dist/cli/{chunk-6cfs0nqe.js → chunk-kt5ngemc.js} +1 -1
  49. package/dist/cli/chunk-nkgn0bqq.js +2 -0
  50. package/dist/cli/{chunk-fkgemeww.js → chunk-q2nhmaq9.js} +2 -2
  51. package/dist/cli/{chunk-hd1szhba.js → chunk-q6cwy6v5.js} +1 -1
  52. package/dist/cli/{chunk-jkgp816v.js → chunk-q72w3dsx.js} +1 -1
  53. package/dist/cli/{chunk-hmsvt5nj.js → chunk-rqm2kn0c.js} +1 -1
  54. package/dist/cli/{chunk-q6tsx6zc.js → chunk-sm96b61g.js} +1 -1
  55. package/dist/cli/chunk-sqdmngys.js +2 -0
  56. package/dist/cli/{chunk-k2j0tg1h.js → chunk-tgv90744.js} +7 -3
  57. package/dist/cli/{chunk-vbvbwjad.js → chunk-twy9bfps.js} +1 -1
  58. package/dist/cli/{chunk-5g6v6tq5.js → chunk-v3ecphrz.js} +1 -1
  59. package/dist/cli/{chunk-mjzjah9d.js → chunk-vtay379p.js} +1 -1
  60. package/dist/cli/chunk-vx44yxra.js +3 -0
  61. package/dist/cli/{chunk-w003rpr2.js → chunk-w3kvkax7.js} +1 -1
  62. package/dist/cli/{chunk-s1xn6wav.js → chunk-w6ft4djp.js} +1 -1
  63. package/dist/cli/{chunk-8hernv9y.js → chunk-w8dmmnys.js} +1 -1
  64. package/dist/cli/{chunk-9f34p80j.js → chunk-ww0zctb7.js} +1 -1
  65. package/dist/cli/{chunk-qwwt4ch2.js → chunk-x119xqa6.js} +1 -1
  66. package/dist/cli/{chunk-nrx8pf9p.js → chunk-x35dfb1x.js} +1 -1
  67. package/dist/cli/{chunk-ggjmcqpt.js → chunk-x94e0rw4.js} +3 -3
  68. package/dist/cli/chunk-xaam0d8r.js +4 -0
  69. package/dist/cli/{chunk-azt0c2b5.js → chunk-xekk4k54.js} +1 -1
  70. package/dist/cli/{chunk-mqvkd2g1.js → chunk-xqvyzv62.js} +1 -1
  71. package/dist/cli/chunk-xxz3np4w.js +2 -0
  72. package/dist/cli/{chunk-p11emdb2.js → chunk-y07cp9mb.js} +1 -1
  73. package/dist/cli/{chunk-5dq0n3h7.js → chunk-y2v1x23z.js} +1 -1
  74. package/dist/cli/chunk-yn01fet6.js +3 -0
  75. package/dist/cli/{chunk-cakyme4s.js → chunk-z8rvxvx5.js} +1 -1
  76. package/dist/cli/chunk-zr058kbe.js +3 -0
  77. package/dist/cli/index.js +3 -3
  78. package/dist/cli/rove.js +3 -3
  79. package/dist/skills/rove/SKILL.md +4 -2
  80. package/dist/skills/rove/references/api-flags.md +7 -4
  81. package/dist/voice/asr_server.py +318 -0
  82. package/dist/voice/engine.py +174 -0
  83. package/dist/voice/fetch_model.py +30 -0
  84. package/dist/voice/learn.py +184 -0
  85. package/dist/voice/live.py +93 -0
  86. package/dist/voice/phonetic.py +87 -0
  87. package/dist/voice/polish.py +84 -0
  88. package/dist/voice/polish_prompt.md +50 -0
  89. package/dist/voice/pyproject.toml +28 -0
  90. package/dist/voice/uv.lock +815 -0
  91. package/dist/voice/vocab.py +193 -0
  92. package/dist/web/assets/index-Bo92_NP5.css +1 -0
  93. package/dist/web/assets/index-Ct8dDnCc.js +154 -0
  94. package/dist/web/index.html +3 -3
  95. package/dist/web/relay/index.html +5 -5
  96. package/package.json +4 -3
  97. package/dist/cli/chunk-0g1h35mr.js +0 -2
  98. package/dist/cli/chunk-1wwqf6kx.js +0 -3
  99. package/dist/cli/chunk-26ydgwym.js +0 -2
  100. package/dist/cli/chunk-2pfh3zm0.js +0 -33
  101. package/dist/cli/chunk-2wrt80ft.js +0 -3
  102. package/dist/cli/chunk-7fya3y0n.js +0 -2
  103. package/dist/cli/chunk-d2wndk4q.js +0 -2
  104. package/dist/cli/chunk-mey3vksj.js +0 -2
  105. package/dist/cli/chunk-nv8eyjep.js +0 -3
  106. package/dist/cli/chunk-ve76p2mx.js +0 -3
  107. package/dist/cli/chunk-wfgrn8p2.js +0 -4
  108. package/dist/cli/chunk-xmjknfy2.js +0 -7
  109. package/dist/web/assets/index-Dc0Khk0q.js +0 -149
  110. package/dist/web/assets/index-T8cCSdnk.css +0 -1
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- import{dedupeMachines,discoverMachine,ensureForwards,learnedFrom,listMachines,localDaemonSocketPath,localPtySocketPath,updateMachine}from"./chunk-vytrhm50.js";import{DISPATCH_PROMPT_HEAD_CHARS,normalizeRemoteUrl,resolvePtyOwner}from"./chunk-vp29jgbt.js";import{taskEngineArgv}from"./chunk-3hxm56h6.js";import{roveApiInvocation}from"./chunk-rjq38twt.js";import{ApiError}from"./chunk-r5j8fycq.js";import{ptyScopeFor}from"./chunk-g9ba25q5.js";import{connectIfRunning}from"./chunk-e58kj4gm.js";import{getSavedRepos,isRemoteRepoKey}from"./chunk-rfp0ayra.js";import{errorMessage}from"./chunk-2brn8e1y.js";import{loadStateFile,updateStateFile}from"./chunk-32j6y5d1.js";import{RoveDaemonClient}from"./chunk-qzzmd63x.js";import{ROVE_API_VERSION}from"./chunk-96kvwzj8.js";import{ROVE_PRODUCT_NAME}from"./chunk-zt7wfnva.js";import{__require}from"./chunk-91sykzqq.js";import{hostname}from"os";async function ptyScopeOf(daemon,task){let listed=await daemon.request("task.list",{}).catch(()=>({tasks:[]})),scope=ptyScopeFor(listed.tasks,task.id);return task.ptyAlias?{...scope,alias:task.ptyAlias}:scope}async function recordSend(daemon,sent){let self=await ownSession(daemon).catch(()=>null);await daemon.request("dispatch.record",{taskId:sent.taskId,...sent.tabId?{tabId:sent.tabId}:{},promptHead:sent.prompt.slice(0,DISPATCH_PROMPT_HEAD_CHARS),delivered:sent.delivered,...sent.deliveryId?{deliveryId:sent.deliveryId}:{},...self?{fromTaskId:self.taskId,fromTabId:self.tabId}:{}}).catch(()=>{})}var selfSessionOnce,identityWarning=null;async function realProbe(){let[{openPtyHost,listSessions},{psSnapshot}]=await Promise.all([import("./chunk-26ydgwym.js"),import("./chunk-sakkdjyn.js")]);return{sessions:async()=>{let host=await openPtyHost();if(!host)return[];try{return await listSessions(host.rpc)}finally{host.close()}},ps:psSnapshot,pid:process.pid}}async function verifiedSelfSession(env=process.env,probe){if(!probe&&selfSessionOnce)return selfSessionOnce;let run=resolveSelfSession(env,probe);return selfSessionOnce=run,run}async function resolveSelfSession(env,probe){let taskId=env.ROVE_TASK_ID;if(!taskId)return null;let tabId=env.ROVE_TAB_ID||"tab-1";try{let p=probe??await realProbe(),key=`${taskId}::${tabId}`,session=(await p.sessions()).find((s)=>s.key===key&&s.alive);if(session?.pid){let{hasAncestor,parsePsSnapshot}=await import("./chunk-sakkdjyn.js");if(hasAncestor(parsePsSnapshot(await p.ps([session.pid,p.pid])),p.pid,session.pid))return identityWarning=null,{taskId,tabId}}}catch{}return identityWarning=`$ROVE_TASK_ID/$ROVE_TASK_ID names task ${taskId} ${tabId}, but this process is not running inside that tab (an inherited env, not an identity) \u2014 dispatcher/peer provenance omitted`,null}function takeIdentityWarning(){let warning=identityWarning;return identityWarning=null,warning}async function ownSession(daemon,env=process.env,probe){let self=await verifiedSelfSession(env,probe);if(!self)return null;let listed=await daemon.request("task.list",{}).catch(()=>({tasks:[]}));return{...self,taskId:resolvePtyOwner(listed.tasks,self.taskId,self.tabId)}}async function dispatcherEnvPayload(daemon,env=process.env,probe){let self=await ownSession(daemon,env,probe);if(!self)return{};return{dispatcherTaskId:self.taskId,dispatcherTabId:self.tabId}}async function readOwnDispatcher(daemon){let self=await ownSession(daemon);if(!self)return null;try{return(await daemon.request("task.get",{taskId:self.taskId})).task.dispatcher??null}catch{return null}}async function resolveDispatcherTab(runtime,daemon,dispatcher){let task=await daemon.request("task.get",{taskId:dispatcher.taskId}).then((res)=>res.task).catch(()=>null),{tabs,running}=await runtime.taskTabs(dispatcher.taskId,void 0,task?await ptyScopeOf(daemon,task):void 0);if(tabs.some((t)=>t.id===dispatcher.tabId&&t.alive))return dispatcher.tabId;if(running)return;throw new ApiError(`dispatcher tab ${dispatcher.tabId} on task ${dispatcher.taskId} is dead and the task has no live engine tab \u2014 the reply has nowhere to land`,"DISPATCHER_UNREACHABLE",{dispatcher,hint:`address an alive target explicitly with --task-id/--tab (see \`${ROVE_PRODUCT_NAME} api pty-list\`), or notify the user with \`${ROVE_PRODUCT_NAME} api notify\``,nextCommandArgs:["api","pty-list"]})}async function replyTabOf(runtime,daemon,taskId){let self=await ownSession(daemon);if(self?.taskId===taskId)return self.tabId;let task=await daemon.request("task.get",{taskId}).then((res)=>res.task).catch(()=>null);if(!task)return;let{tabs}=await runtime.taskTabs(taskId,taskEngineArgv(task),await ptyScopeOf(daemon,task));return tabs.find((t)=>t.kind==="engine"&&t.alive===!0&&t.engineAlive!==!1)?.id}async function withPeerProvenance(daemon,targetTaskId,prompt){let self=await ownSession(daemon),senderId=self?.taskId;if(!self||!senderId||senderId===targetTaskId)return prompt;let label=senderId;try{let res=await daemon.request("task.get",{taskId:senderId});label=res.task.title||res.task.branch||senderId}catch{}let api=roveApiInvocation(),replyTarget=`--task-id ${senderId} --tab ${self.tabId}`;return`[ROVE PEER] from "${label}" (task ${senderId} \u2014 Rove agent skill /rove, read it once per session; reply only if it changes what I do next: \`${api} send ${replyTarget} --prompt "<text>"\`; verb reference: \`${api} schema\`)
2
+ import{dedupeMachines,discoverMachine,ensureForwards,learnedFrom,listMachines,localDaemonSocketPath,localPtySocketPath,updateMachine}from"./chunk-vytrhm50.js";import{DISPATCH_PROMPT_HEAD_CHARS,normalizeRemoteUrl,resolvePtyOwner}from"./chunk-vp29jgbt.js";import{taskEngineArgv}from"./chunk-1a57ejnh.js";import{roveApiInvocation}from"./chunk-rjq38twt.js";import{ApiError}from"./chunk-r5j8fycq.js";import{ptyScopeFor}from"./chunk-g9ba25q5.js";import{connectIfRunning}from"./chunk-e58kj4gm.js";import{getSavedRepos,isRemoteRepoKey}from"./chunk-rfp0ayra.js";import{errorMessage}from"./chunk-2brn8e1y.js";import{loadStateFile,updateStateFile}from"./chunk-32j6y5d1.js";import{RoveDaemonClient}from"./chunk-qzzmd63x.js";import{ROVE_API_VERSION}from"./chunk-96kvwzj8.js";import{ROVE_PRODUCT_NAME}from"./chunk-zt7wfnva.js";import{__require}from"./chunk-91sykzqq.js";import{hostname}from"os";async function ptyScopeOf(daemon,task){let listed=await daemon.request("task.list",{}).catch(()=>({tasks:[]})),scope=ptyScopeFor(listed.tasks,task.id);return task.ptyAlias?{...scope,alias:task.ptyAlias}:scope}async function recordSend(daemon,sent){let self=await ownSession(daemon).catch(()=>null);await daemon.request("dispatch.record",{taskId:sent.taskId,...sent.tabId?{tabId:sent.tabId}:{},promptHead:sent.prompt.slice(0,DISPATCH_PROMPT_HEAD_CHARS),delivered:sent.delivered,...sent.deliveryId?{deliveryId:sent.deliveryId}:{},...self?{fromTaskId:self.taskId,fromTabId:self.tabId}:{}}).catch(()=>{})}var selfSessionOnce,identityWarning=null;async function realProbe(){let[{openPtyHost,listSessions},{psSnapshot}]=await Promise.all([import("./chunk-nkgn0bqq.js"),import("./chunk-sakkdjyn.js")]);return{sessions:async()=>{let host=await openPtyHost();if(!host)return[];try{return await listSessions(host.rpc)}finally{host.close()}},ps:psSnapshot,pid:process.pid}}async function verifiedSelfSession(env=process.env,probe){if(!probe&&selfSessionOnce)return selfSessionOnce;let run=resolveSelfSession(env,probe);return selfSessionOnce=run,run}async function resolveSelfSession(env,probe){let taskId=env.ROVE_TASK_ID;if(!taskId)return null;let tabId=env.ROVE_TAB_ID||"tab-1";try{let p=probe??await realProbe(),key=`${taskId}::${tabId}`,session=(await p.sessions()).find((s)=>s.key===key&&s.alive);if(session?.pid){let{hasAncestor,parsePsSnapshot}=await import("./chunk-sakkdjyn.js");if(hasAncestor(parsePsSnapshot(await p.ps([session.pid,p.pid])),p.pid,session.pid))return identityWarning=null,{taskId,tabId}}}catch{}return identityWarning=`$ROVE_TASK_ID/$ROVE_TASK_ID names task ${taskId} ${tabId}, but this process is not running inside that tab (an inherited env, not an identity) \u2014 dispatcher/peer provenance omitted`,null}function takeIdentityWarning(){let warning=identityWarning;return identityWarning=null,warning}async function ownSession(daemon,env=process.env,probe){let self=await verifiedSelfSession(env,probe);if(!self)return null;let listed=await daemon.request("task.list",{}).catch(()=>({tasks:[]}));return{...self,taskId:resolvePtyOwner(listed.tasks,self.taskId,self.tabId)}}async function dispatcherEnvPayload(daemon,env=process.env,probe){let self=await ownSession(daemon,env,probe);if(!self)return{};return{dispatcherTaskId:self.taskId,dispatcherTabId:self.tabId}}async function readOwnDispatcher(daemon){let self=await ownSession(daemon);if(!self)return null;try{return(await daemon.request("task.get",{taskId:self.taskId})).task.dispatcher??null}catch{return null}}async function resolveDispatcherTab(runtime,daemon,dispatcher){let task=await daemon.request("task.get",{taskId:dispatcher.taskId}).then((res)=>res.task).catch(()=>null),{tabs,running}=await runtime.taskTabs(dispatcher.taskId,void 0,task?await ptyScopeOf(daemon,task):void 0);if(tabs.some((t)=>t.id===dispatcher.tabId&&t.alive))return dispatcher.tabId;if(running)return;throw new ApiError(`dispatcher tab ${dispatcher.tabId} on task ${dispatcher.taskId} is dead and the task has no live engine tab \u2014 the reply has nowhere to land`,"DISPATCHER_UNREACHABLE",{dispatcher,hint:`address an alive target explicitly with --task-id/--tab (see \`${ROVE_PRODUCT_NAME} api pty-list\`), or notify the user with \`${ROVE_PRODUCT_NAME} api notify\``,nextCommandArgs:["api","pty-list"]})}async function replyTabOf(runtime,daemon,taskId){let self=await ownSession(daemon);if(self?.taskId===taskId)return self.tabId;let task=await daemon.request("task.get",{taskId}).then((res)=>res.task).catch(()=>null);if(!task)return;let{tabs}=await runtime.taskTabs(taskId,taskEngineArgv(task),await ptyScopeOf(daemon,task));return tabs.find((t)=>t.kind==="engine"&&t.alive===!0&&t.engineAlive!==!1)?.id}async function withPeerProvenance(daemon,targetTaskId,prompt){let self=await ownSession(daemon),senderId=self?.taskId;if(!self||!senderId||senderId===targetTaskId)return prompt;let label=senderId;try{let res=await daemon.request("task.get",{taskId:senderId});label=res.task.title||res.task.branch||senderId}catch{}let api=roveApiInvocation(),replyTarget=`--task-id ${senderId} --tab ${self.tabId}`;return`[ROVE PEER] from "${label}" (task ${senderId} \u2014 Rove agent skill /rove, read it once per session; reply only if it changes what I do next: \`${api} send ${replyTarget} --prompt "<text>"\`; verb reference: \`${api} schema\`)
3
3
 
4
4
  ${prompt}`}import{spawnSync}from"child_process";import{existsSync}from"fs";import{join}from"path";function git(cwd,args){let res=spawnSync("git",["-C",cwd,...args],{encoding:"utf8",timeout:5000});return res.status===0?res.stdout.trim():null}function repoIdentity(path){if(!existsSync(path)||git(path,["rev-parse","--git-dir"])===null)return null;let origin=git(path,["remote","get-url","origin"]),root=git(path,["rev-list","--max-parents=0","HEAD"])?.split(`
5
5
  `).filter(Boolean).sort()[0];return{...origin?{origin:normalizeRemoteUrl(origin)}:{},...root?{root}:{}}}function isLocalCheckout(path){return existsSync(join(path,".git"))||existsSync(path)&&git(path,["rev-parse","--git-dir"])!==null}function unpushedCommits(path,ref){if(git(path,["rev-parse","--verify","--quiet",`${ref}^{commit}`])===null)return null;let ahead=git(path,["rev-list","--count",`${ref}@{upstream}..${ref}`]);if(ahead!==null)return Number.parseInt(ahead,10)||0;let lone=git(path,["rev-list","--count",ref,"--not","--remotes"]);return lone===null?null:Number.parseInt(lone,10)||0}import{spawnSync as spawnSync2}from"child_process";import{existsSync as existsSync2,mkdirSync}from"fs";import{homedir}from"os";import{dirname,isAbsolute,join as join2,resolve}from"path";var URL_LIKE=/^[a-z][a-z0-9+.-]*:\/\//i,SCP_LIKE=/^[^@/\s]+@[^:/\s]+:[^\s]+$/,SEGMENT=/^[A-Za-z0-9_.-]+$/;function parseRepoRef(raw,cwd=process.cwd(),exists=existsSync2){let value=raw.trim(),asPath={kind:"path",path:value};if(value===""||isRemoteRepoKey(value))return asPath;if(URL_LIKE.test(value)||SCP_LIKE.test(value))return{kind:"id",id:normalizeRemoteUrl(value),cloneUrl:value};if(isAbsolute(value)||/^[A-Za-z]:[\\/]/.test(value)||/^[~.\\]/.test(value))return asPath;if(exists(resolve(cwd,value)))return asPath;let parts=value.replace(/\.git$/i,"").split("/");if(!parts.every((p)=>SEGMENT.test(p)))return asPath;if(parts.length===2){let id=`github.com/${parts.join("/")}`.toLowerCase();return{kind:"id",id,cloneUrl:`https://${id}.git`}}if(parts.length>=3&&parts[0]?.includes(".")){let id=parts.join("/").toLowerCase();return{kind:"id",id,cloneUrl:`https://${id}.git`}}return asPath}function repoIdOf(identity){if(!identity)return null;if(identity.origin)return identity.origin;return identity.root?`root:${identity.root}`:null}function findCheckouts(want,candidates){let out=[];for(let c of candidates){let sameRoot=Boolean(want.root&&c.identity.root===want.root),sameOrigin=Boolean(want.origin&&c.identity.origin===want.origin);if(!sameRoot&&!sameOrigin)continue;let fork=sameRoot&&!sameOrigin&&Boolean(want.origin&&c.identity.origin);out.push({path:c.path,via:sameRoot?"root":"origin",fork})}return out}function pickCheckout(matches,opts){let usable=matches.filter((m)=>opts.allowFork||!m.fork);if(usable.length===0){let fork=matches.find((m)=>m.fork)?.path;return fork?{path:null,fork}:{path:null}}let primary=usable.find((m)=>m.path===opts.primary);if(primary)return{path:primary.path};if(usable.length>1&&opts.isClean){let clean=usable.find((m)=>opts.isClean?.(m.path));if(clean)return{path:clean.path}}return{path:usable[0]?.path??null}}function isCleanCheckout(path){let res=spawnSync2("git",["-C",path,"status","--porcelain","--untracked-files=no"],{encoding:"utf8",timeout:5000});return res.status===0&&res.stdout.trim()===""}var REPO_PRIMARY_KEY="repoPrimary";function primaryCheckouts(state=loadStateFile()){let raw=state[REPO_PRIMARY_KEY],out={};if(raw&&typeof raw==="object"){for(let[id,path]of Object.entries(raw))if(typeof path==="string")out[id]=path}return out}function setPrimaryCheckout(id,path){updateStateFile((state)=>{let next=primaryCheckouts(state);if(path===null)Reflect.deleteProperty(next,id);else next[id]=path;return state[REPO_PRIMARY_KEY]=next,!0})}function cloneDirFor(id,home=homedir()){return join2(home,".rove","repos",...id.split("/"))}function cloneRepo(ref,home){let dir=cloneDirFor(ref.id,home);if(repoIdentity(dir))return dir;mkdirSync(dirname(dir),{recursive:!0});let res=spawnSync2("git",["clone",ref.cloneUrl,dir],{encoding:"utf8",timeout:600000,env:{...process.env,GIT_TERMINAL_PROMPT:"0"}});if(res.status!==0)throw Error((res.stderr||res.stdout||"git clone failed").trim());return dir}var LOCAL_MACHINE="local";async function machineDaemonSocket(entry){let socketPath=localDaemonSocketPath(entry.alias);if(await answers(socketPath))return socketPath;if(!entry.sockets||!await forward(entry,entry.sockets))return null;if(await answers(socketPath))return socketPath;let found=await discoverMachine(entry.alias,entry);if(!found.ok)return null;let learned=learnedFrom(found);return updateMachine(entry.alias,learned),await forward({...entry,...learned},learned.sockets)&&await answers(socketPath)?socketPath:null}function forward(entry,sockets){return ensureForwards({alias:entry.alias,config:{...entry,sockets},remoteDaemonSocket:sockets.daemon,remotePtySocket:sockets.pty})}async function answers(socketPath){let client=new RoveDaemonClient(socketPath);try{return await client.request("hello",{}),!0}catch{return!1}finally{client.close()}}async function machineTasks(entry){let socketPath=await machineDaemonSocket(entry);if(!socketPath)return null;let client=new RoveDaemonClient(socketPath);try{return(await client.request("task.list")).tasks??[]}catch{return null}finally{client.close()}}function requireMachine(alias){let entry=listMachines().find((m)=>m.alias===alias);if(entry)return entry;throw new ApiError(`no machine named "${alias}"`,"UNKNOWN_MACHINE",{hint:"list the registered machines and retry with one of their aliases, or register it first",nextCommandArgs:["machine","list"]})}var AUTO_MACHINE="auto",busy=(tasks)=>tasks.filter((task)=>task.status==="in_progress").length;async function pickMachine(localTasks,readTasks=machineTasks,machines=dedupeMachines(listMachines()),hosts){let[local,...remote]=await Promise.all([localTasks().catch(()=>[]),...machines.map((entry)=>readTasks(entry))]),loads={[LOCAL_MACHINE]:busy(local)};machines.forEach((entry,i)=>{let tasks=remote[i];if(tasks)loads[entry.alias]=busy(tasks)});let pool=[null,...machines.filter((entry)=>loads[entry.alias]!==void 0)],skipped;if(hosts){let ok=await Promise.all(pool.map((entry)=>hosts(entry).catch(()=>!1)));if(ok.some(Boolean))skipped=pool.filter((_,i)=>!ok[i]).map((entry)=>entry?.alias??LOCAL_MACHINE),pool=pool.filter((_,i)=>ok[i])}let best=null,bestLoad=Number.POSITIVE_INFINITY;for(let entry of pool){let load=loads[entry?.alias??LOCAL_MACHINE]??0;if(load<bestLoad)best=entry,bestLoad=load}return{entry:best,loads,...skipped?{skipped}:{}}}function repoWanted(repo){if(typeof repo!=="string")return null;let ref=parseRepoRef(repo);if(ref.kind==="id")return{origin:ref.id};return isLocalCheckout(ref.path)?repoIdentity(ref.path):null}function repoHosts(params){if(flagOn(params.clone))return;let want=repoWanted(params.repo);if(!want||!want.origin&&!want.root)return;return async(entry)=>{if(entry===null){if(typeof params.repo==="string"&&parseRepoRef(params.repo).kind==="path")return!0;return getSavedRepos().some((path)=>findCheckouts(want,[{path,identity:repoIdentity(path)??{}}]).length)}let socket=await machineDaemonSocket(entry);if(!socket)return!1;let client=new RoveDaemonClient(socket);try{let res=await client.request("api.invoke",{verb:"repo-match",params:{...want.origin?{origin:want.origin}:{},...want.root?{root:want.root}:{}},forwarded:!0,from:hostname()});return res.ok&&Boolean(res.result.repo)}finally{client.close()}}}async function routeTarget(args){if(args.machine===AUTO_MACHINE){let hosts=args.params?repoHosts(args.params):void 0;return(await pickMachine(args.localTasks??(async()=>[]),machineTasks,void 0,hosts)).entry}if(args.machine!==void 0&&args.machine!==LOCAL_MACHINE)return requireMachine(args.machine);if(args.machine===LOCAL_MACHINE||!args.taskId)return null;let machines=dedupeMachines(listMachines());if(machines.length===0||await args.isLocalTask(args.taskId))return null;for(let entry of machines)if((await machineTasks(entry))?.some((task)=>task.id===args.taskId))return entry;return null}async function forwardVerb(entry,verb,params,socketPath){let target=socketPath??await machineDaemonSocket(entry);if(!target)return offline(entry.alias);let client=new RoveDaemonClient(target);try{let repo=await translateRepo(client,entry.alias,params);if(repo.failure)return repo.failure;if(verb==="land"&&params["dry-run"]===void 0){let published=await client.request("api.invoke",{verb:"publish",params:{"task-id":params["task-id"]},forwarded:!0,from:hostname()});if(published.ok||published.error.code!=="UNKNOWN_VERB")return published}let envelope=await client.request("api.invoke",{verb,params:repo.params,forwarded:!0,from:hostname()});return await recordHere(verb,params,envelope),envelope}catch(err){let message=errorMessage(err);if(/unknown daemon request:/i.test(message))return tooOld(entry.alias);return failure("MACHINE_UNREACHABLE",`machine "${entry.alias}": ${message}`,{hint:"check the machine is up and reachable over ssh, then retry",nextCommandArgs:["machine","list"]})}finally{client.close()}}async function recordHere(verb,params,envelope){if(verb!=="send"&&verb!=="dispatch"||!envelope.ok||typeof params.prompt!=="string")return;let sent=envelope.result,taskId=typeof sent.childTaskId==="string"?sent.childTaskId:sent.taskId;if(typeof taskId!=="string")return;let tabId=typeof sent.tabId==="string"?sent.tabId:typeof sent.session==="string"?sent.session.split("::")[1]:void 0,local=await connectIfRunning().catch(()=>null);if(!local)return;try{await recordSend(local,{taskId,...tabId?{tabId}:{},prompt:params.prompt,delivered:sent.delivered===!0})}finally{local.close()}}async function translateRepo(client,alias,params){let repo=params.repo;if(typeof repo!=="string")return{params};let ref=parseRepoRef(repo),local=ref.kind==="path"&&isLocalCheckout(ref.path);if(ref.kind==="path"&&!local)return{params};let identity=ref.kind==="id"?{origin:ref.id}:repoIdentity(repo);if(!identity?.origin&&!identity?.root)return{params};let allowFork=flagOn(params["allow-fork"]),ask=await client.request("api.invoke",{verb:"repo-match",params:{...identity.origin?{origin:identity.origin}:{},...identity.root?{root:identity.root}:{},...allowFork?{"allow-fork":!0}:{}},forwarded:!0,from:hostname()});if(!ask.ok)return ask.error.code==="UNKNOWN_VERB"?{params}:{failure:ask};let answer=ask.result,found=answer.repo;if(found){let base=params["base-branch"],unpushed=local&&typeof base==="string"?unpushedCommits(repo,base):null;if(typeof base==="string"&&unpushed)return{failure:failure("UNPUSHED_BASE",`${base} has ${unpushed} commit${unpushed===1?"":"s"} no remote has; ${alias} cannot base a task on it`,{hint:`push it first (\`git -C ${repo} push -u origin ${base}\`), then dispatch again`})};return{params:{...params,repo:found}}}if(answer.fork)return{failure:failure("FORK_MATCH",`machine "${alias}" only has ${answer.fork}, a fork of ${identity.origin??repo} (same history, other origin)`,{hint:"pass --allow-fork to use it, or --clone with the repo id to fetch the repo itself there"})};if(flagOn(params.clone)&&identity.origin)return{params:{...params,repo:ref.kind==="id"?repo:identity.origin}};return{failure:failure("NO_REMOTE_CHECKOUT",`machine "${alias}" has no checkout of ${identity.origin??repo}`,{hint:identity.origin?`pass --clone to clone it on ${alias}, or --repo with ${alias}'s own path`:`clone it on ${alias} (and open it there once so Rove knows it), or pass --repo with ${alias}'s own path`})}}var flagOn=(value)=>value===!0||value==="true";function offline(alias){return failure("MACHINE_UNREACHABLE",`machine "${alias}" did not answer`,{hint:`check \`ssh ${alias} true\` works without a prompt, then retry`,nextCommandArgs:["machine","list"]})}function tooOld(alias){return failure("MACHINE_VERSION_SKEW",`machine "${alias}" runs a Rove too old to accept forwarded verbs`,{hint:`upgrade Rove on ${alias} (\`rove update\` there), then retry`})}function failure(code,message,data){return{apiVersion:ROVE_API_VERSION,ok:!1,error:{message,code,...data}}}async function invokeRouted(args){if(args.forwarded)return args.runHere();let taskId=args.params["task-id"],target;try{target=await routeTarget({machine:args.machine,taskId:typeof taskId==="string"?taskId:void 0,isLocalTask:args.isLocalTask,...args.localTasks?{localTasks:args.localTasks}:{},params:args.params})}catch(err){if(!(err instanceof ApiError))throw err;return failure(err.code,err.message,err.data??{})}return target?forwardVerb(target,args.verb,args.params):args.runHere()}async function machinePtySocket(alias){let entry=requireMachine(alias);if(!await machineDaemonSocket(entry))throw new ApiError(`machine "${alias}" did not answer`,"MACHINE_UNREACHABLE");return localPtySocketPath(alias)}
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{publishRoveTerminalTitle}from"./chunk-3aqhytyy.js";import{ensureGlobalRoveHooks}from"./chunk-dkx7s5rk.js";import{LAST_RUN_VERSION_KEY,enforceResetGate}from"./chunk-byh32qnx.js";import"./chunk-17e2tgyg.js";import"./chunk-2bgmby1d.js";import"./chunk-n0rdn2fn.js";import"./chunk-j1z9kgyd.js";import"./chunk-e58kj4gm.js";import"./chunk-0a65j1zj.js";import"./chunk-bcyex84w.js";import"./chunk-4gjvmfa9.js";import"./chunk-2tfnscxk.js";import"./chunk-hzz020xx.js";import"./chunk-w7zv47mp.js";import"./chunk-rfp0ayra.js";import"./chunk-40dgv828.js";import{CURRENT_VERSION,compareSemver}from"./chunk-2wrt80ft.js";import"./chunk-zsfyj36g.js";import"./chunk-2brn8e1y.js";import"./chunk-7mwvvj46.js";import{detectShell}from"./chunk-np398f2a.js";import"./chunk-eege1x0a.js";import"./chunk-qqr4tfhk.js";import{loadStateFile,patchStateFile}from"./chunk-32j6y5d1.js";import"./chunk-nqwza8b0.js";import"./chunk-ve76p2mx.js";import"./chunk-cmp64q4j.js";import"./chunk-qzzmd63x.js";import"./chunk-96kvwzj8.js";import"./chunk-7bakcyes.js";import"./chunk-8qkyd589.js";import"./chunk-7kysxgbq.js";import"./chunk-hz1x3hak.js";import"./chunk-x1jxxmtc.js";import"./chunk-xy8be792.js";import"./chunk-h2m7em1e.js";import"./chunk-7ghwdjdc.js";import"./chunk-8t7qbr1e.js";import"./chunk-bejhca6z.js";import"./chunk-br8r72bf.js";import"./chunk-yt15y085.js";import"./chunk-z1nj22va.js";import"./chunk-zt7wfnva.js";import"./chunk-0g4wc808.js";import"./chunk-pn5ygqn6.js";import"./chunk-eqdbfgq5.js";import"./chunk-f8gg8cqh.js";import"./chunk-nc46gddg.js";import{__require}from"./chunk-91sykzqq.js";var WELCOMED_KEY="welcomed";function shouldWelcome(state){if(state[WELCOMED_KEY]===!0)return!1;return typeof state[LAST_RUN_VERSION_KEY]!=="string"}function takeWelcome(){if(!shouldWelcome(loadStateFile()))return null;try{patchStateFile({[WELCOMED_KEY]:!0})}catch{}return{shell:detectShell()}}var WHATS_NEW_SEEN_KEY="app.whatsNewSeenVersion";function whatsNewFromVersion(state,current=CURRENT_VERSION){let stamped=state[WHATS_NEW_SEEN_KEY]??state[LAST_RUN_VERSION_KEY];if(typeof stamped!=="string"||stamped.length===0)return null;return compareSemver(current,stamped)>0?stamped:null}function takeWhatsNew(){let from=whatsNewFromVersion(loadStateFile());try{patchStateFile({[WHATS_NEW_SEEN_KEY]:CURRENT_VERSION})}catch{}return from}async function startTui(){let whatsNewFrom=takeWhatsNew(),welcome=takeWelcome();enforceResetGate(),publishRoveTerminalTitle(),await ensureGlobalRoveHooks();let{loadPluginEngines}=await import("./chunk-6mg5zp8y.js");loadPluginEngines();let{startSkillStatus}=await import("./chunk-hmsvt5nj.js");startSkillStatus();let{startWorkspaceHost}=await import("./chunk-3a7x7jg7.js");await startWorkspaceHost({whatsNewFrom,welcome})}export{startTui};
2
+ import{publishRoveTerminalTitle}from"./chunk-3aqhytyy.js";import{ensureGlobalRoveHooks}from"./chunk-dkx7s5rk.js";import{LAST_RUN_VERSION_KEY,enforceResetGate}from"./chunk-5swmvt20.js";import"./chunk-17e2tgyg.js";import"./chunk-2bgmby1d.js";import"./chunk-n0rdn2fn.js";import"./chunk-j1z9kgyd.js";import"./chunk-e58kj4gm.js";import"./chunk-0a65j1zj.js";import"./chunk-bcyex84w.js";import"./chunk-4gjvmfa9.js";import"./chunk-2tfnscxk.js";import"./chunk-hzz020xx.js";import"./chunk-w7zv47mp.js";import"./chunk-rfp0ayra.js";import"./chunk-40dgv828.js";import{CURRENT_VERSION,compareSemver}from"./chunk-yn01fet6.js";import"./chunk-zsfyj36g.js";import"./chunk-2brn8e1y.js";import"./chunk-7mwvvj46.js";import{detectShell}from"./chunk-bbt217yd.js";import"./chunk-eege1x0a.js";import"./chunk-qqr4tfhk.js";import{loadStateFile,patchStateFile}from"./chunk-32j6y5d1.js";import"./chunk-a5xbsvx2.js";import"./chunk-9vwb9f7q.js";import"./chunk-cmp64q4j.js";import"./chunk-qzzmd63x.js";import"./chunk-96kvwzj8.js";import"./chunk-7bakcyes.js";import"./chunk-8qkyd589.js";import"./chunk-7kysxgbq.js";import"./chunk-hz1x3hak.js";import"./chunk-x1jxxmtc.js";import"./chunk-xy8be792.js";import"./chunk-h2m7em1e.js";import"./chunk-7ghwdjdc.js";import"./chunk-8t7qbr1e.js";import"./chunk-bejhca6z.js";import"./chunk-br8r72bf.js";import"./chunk-yt15y085.js";import"./chunk-z1nj22va.js";import"./chunk-zt7wfnva.js";import"./chunk-0g4wc808.js";import"./chunk-pn5ygqn6.js";import"./chunk-eqdbfgq5.js";import"./chunk-f8gg8cqh.js";import"./chunk-nc46gddg.js";import{__require}from"./chunk-91sykzqq.js";var WELCOMED_KEY="welcomed";function shouldWelcome(state){if(state[WELCOMED_KEY]===!0)return!1;return typeof state[LAST_RUN_VERSION_KEY]!=="string"}function takeWelcome(){if(!shouldWelcome(loadStateFile()))return null;try{patchStateFile({[WELCOMED_KEY]:!0})}catch{}return{shell:detectShell()}}var WHATS_NEW_SEEN_KEY="app.whatsNewSeenVersion";function whatsNewFromVersion(state,current=CURRENT_VERSION){let stamped=state[WHATS_NEW_SEEN_KEY]??state[LAST_RUN_VERSION_KEY];if(typeof stamped!=="string"||stamped.length===0)return null;return compareSemver(current,stamped)>0?stamped:null}function takeWhatsNew(){let from=whatsNewFromVersion(loadStateFile());try{patchStateFile({[WHATS_NEW_SEEN_KEY]:CURRENT_VERSION})}catch{}return from}async function startTui(){let whatsNewFrom=takeWhatsNew(),welcome=takeWelcome();enforceResetGate(),publishRoveTerminalTitle(),await ensureGlobalRoveHooks();let{loadPluginEngines}=await import("./chunk-6mg5zp8y.js");loadPluginEngines();let{startSkillStatus}=await import("./chunk-rqm2kn0c.js");startSkillStatus();let{startWorkspaceHost}=await import("./chunk-hnxs5shy.js");await startWorkspaceHost({whatsNewFrom,welcome})}export{startTui};
@@ -0,0 +1,3 @@
1
+ // @bun
2
+ import{isDev}from"./chunk-z1nj22va.js";import{fileURLToPath}from"url";var package_default={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.281",description:"Rove \u2014 the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{rove:"dist/cli/rove.js"},files:["dist/cli","dist/completions","dist/web","dist/web-ui","dist/skills","dist/voice","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:mock":"ROVE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts","build:rubric":"bun run scripts/build-tier-rubric.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"ROVE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"ROVE_INCLUDE_SOCKET=1 ROVE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"ROVE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun scripts/render-track.mjs",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0",react:"^19.2.8","smol-toml":"^1.7.1",ws:"^8.21.3"},optionalDependencies:{"node-pty":"^1.1.0"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/rove-daemon":"0.7.23","@tsconfig/bun":"1.0.11","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1","rove-bridge":"0.0.42","rove-weixin":"0.0.42",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var CURRENT_VERSION=package_default.version,PACKAGE_NAME=package_default.name;function repoSlug(){let url=package_default.repository?.url;if(!url)return null;let m=url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);if(!m||!m[1]||!m[2])return null;return`${m[1]}/${m[2]}`}var UPDATE_SCRIPT_URL="https://raw.githubusercontent.com/Sma1lboy/rove/main/scripts/update.sh",UPDATE_COMMAND=`curl -fsSL ${UPDATE_SCRIPT_URL} | sh`;function updateCommandFor(target){return target===void 0||target===DEFAULT_RELEASE_CHANNEL?UPDATE_COMMAND:`${UPDATE_COMMAND} -s -- ${target}`}function owningNpmPrefix(modulePath=fileURLToPath(import.meta.url)){let at=modulePath.indexOf("/lib/node_modules/");if(at<=0)return null;return modulePath.slice(0,at)}function recommendedGlobalInstallCommand(prefix=owningNpmPrefix()){let target=`${PACKAGE_NAME}@latest`;return prefix===null?`npm install -g ${target}`:`npm install -g --prefix ${prefix} ${target}`}var BREAKING_VERSIONS=[];function breakingVersionsCrossed(from,to,breaking=BREAKING_VERSIONS){let[lo,hi]=compareSemver(from,to)<=0?[from,to]:[to,from];return breaking.filter((b)=>compareSemver(b,lo)>0&&compareSemver(b,hi)<=0)}var FETCH_TIMEOUT_MS=3000,PACKUMENT_TIMEOUT_MS=15000,RELEASE_CHANNELS=["latest","next","nightly"],DEFAULT_RELEASE_CHANNEL="latest";function channelOf(version=CURRENT_VERSION){let tag=prereleaseOf(version)?.split(".")[0];return tag==="nightly"||tag==="next"?tag:DEFAULT_RELEASE_CHANNEL}async function fetchLatestFromRegistry(packageName,channel){let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let encoded=packageName.replace("/","%2F"),res=await fetch(`https://registry.npmjs.org/${encoded}/${channel}`,{signal:ctrl.signal,headers:{accept:"application/json"}});if(!res.ok)return null;let body=await res.json();if(typeof body.version!=="string")return null;return body.version}catch{return null}finally{clearTimeout(timer)}}async function fetchPublishedAt(packageName,version){let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),PACKUMENT_TIMEOUT_MS);try{let encoded=packageName.replace("/","%2F"),res=await fetch(`https://registry.npmjs.org/${encoded}`,{signal:ctrl.signal,headers:{accept:"application/json"}});if(!res.ok)return null;let stamp=(await res.json()).time?.[version],ms=typeof stamp==="string"?Date.parse(stamp):Number.NaN;return Number.isFinite(ms)?ms:null}catch{return null}finally{clearTimeout(timer)}}function isNewerSemver(latest,current){let core=compareSemver(latest,current);if(core!==0)return core>0;return comparePrerelease(prereleaseOf(latest),prereleaseOf(current))>0}function prereleaseOf(version){let dash=version.indexOf("-");return dash===-1?void 0:version.slice(dash+1)||void 0}function comparePrerelease(a,b){if(a===b)return 0;if(a===void 0)return 1;if(b===void 0)return-1;let aParts=a.split("."),bParts=b.split(".");for(let i=0;i<Math.max(aParts.length,bParts.length);i++){let av=aParts[i],bv=bParts[i];if(av===void 0)return-1;if(bv===void 0)return 1;if(av===bv)continue;let an=/^\d+$/.test(av)?Number.parseInt(av,10):null,bn=/^\d+$/.test(bv)?Number.parseInt(bv,10):null;if(an!==null&&bn!==null)return an>bn?1:-1;if(an!==null)return-1;if(bn!==null)return 1;return av>bv?1:-1}return 0}function compareSemver(aVersion,bVersion){let norm=(v)=>v.split("-")[0]??v,a=norm(aVersion).split(".").map((s)=>Number.parseInt(s,10)),b=norm(bVersion).split(".").map((s)=>Number.parseInt(s,10));for(let i=0;i<3;i++){let av=a[i]??0,bv=b[i]??0;if(Number.isNaN(av)||Number.isNaN(bv))return 0;if(av>bv)return 1;if(av<bv)return-1}return 0}async function checkLatestVersion(opts={}){let channel=opts.channel??channelOf(),fake=process.env.ROVE_FAKE_UPDATE;if(fake)return{current:CURRENT_VERSION,latest:fake,hasUpdate:isNewerSemver(fake,CURRENT_VERSION),channel};if(isDev()&&!opts.force)return null;let latest=await fetchLatestFromRegistry(PACKAGE_NAME,channel);if(!latest)return null;let hasUpdate=isNewerSemver(latest,CURRENT_VERSION),minAgeMs=(opts.minAgeHours??0)*3600000;if(hasUpdate&&minAgeMs>0){let publishedAt=await fetchPublishedAt(PACKAGE_NAME,latest),now=(opts.now??Date.now)();if(publishedAt!==null&&now-publishedAt<minAgeMs)hasUpdate=!1}return{current:CURRENT_VERSION,latest,hasUpdate,channel}}function versionFromTagName(tagName){if(typeof tagName!=="string")return null;return tagName.match(/^v(\d+\.\d+\.\d+)$/)?.[1]??null}var ZH_NOTES_ASSET="release-notes.zh.md";async function notesBody(release,locale,signal){if(locale!=="zh"||!Array.isArray(release.assets))return release.body;let asset=release.assets.find((a)=>a.name===ZH_NOTES_ASSET);if(typeof asset?.browser_download_url!=="string")return release.body;try{let res=await fetch(asset.browser_download_url,{signal}),text=res.ok?await res.text():"";return text.trim()?text:release.body}catch{return release.body}}async function fetchReleaseNotes(version,locale="en"){let slug=repoSlug();if(!slug)return null;let tag=`v${version}`,ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(`https://api.github.com/repos/${slug}/releases/tags/${tag}`,{signal:ctrl.signal,headers:{accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!res.ok)return null;let body=await res.json();if(typeof body.body!=="string"||typeof body.html_url!=="string")return null;return{body:await notesBody({...body,body:body.body},locale,ctrl.signal),url:body.html_url,version}}catch{return null}finally{clearTimeout(timer)}}async function fetchReleaseNotesRange(args){let slug=repoSlug();if(!slug)return[];let limit=args.limit??100,ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(`https://api.github.com/repos/${slug}/releases?per_page=${limit}`,{signal:ctrl.signal,headers:{accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!res.ok)return[];let body=await res.json();if(!Array.isArray(body))return[];let inRange=body.flatMap((release)=>{let version=versionFromTagName(release.tag_name);if(!version||typeof release.html_url!=="string"||typeof release.body!=="string")return[];if(compareSemver(version,args.current)<=0)return[];if(compareSemver(version,args.latest)>0)return[];return[{version,url:release.html_url,release:{...release,body:release.body}}]});return await Promise.all(inRange.map(async({version,url,release})=>({version,url,body:await notesBody(release,args.locale??"en",ctrl.signal)})))}catch{return[]}finally{clearTimeout(timer)}}async function fetchReleaseSummaries(limit=12){let slug=repoSlug();if(!slug)return[];let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(`https://api.github.com/repos/${slug}/releases?per_page=${limit}`,{signal:ctrl.signal,headers:{accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!res.ok)return[];let body=await res.json();if(!Array.isArray(body))return[];return body.map((release)=>{let version=versionFromTagName(release.tag_name);if(!version||typeof release.html_url!=="string")return null;return{version,url:release.html_url}}).filter((release)=>release!==null)}catch{return[]}finally{clearTimeout(timer)}}function releasePageUrl(version){let slug=repoSlug();if(!slug)return null;return`https://github.com/${slug}/releases/tag/v${version}`}
3
+ export{package_default,CURRENT_VERSION,PACKAGE_NAME,repoSlug,UPDATE_SCRIPT_URL,UPDATE_COMMAND,updateCommandFor,owningNpmPrefix,recommendedGlobalInstallCommand,BREAKING_VERSIONS,breakingVersionsCrossed,RELEASE_CHANNELS,DEFAULT_RELEASE_CHANNEL,channelOf,isNewerSemver,compareSemver,checkLatestVersion,ZH_NOTES_ASSET,fetchReleaseNotes,fetchReleaseNotesRange,fetchReleaseSummaries,releasePageUrl};
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{Orchestrator}from"./chunk-bv050ssb.js";import"./chunk-1wwqf6kx.js";import"./chunk-c1dz3qg3.js";import"./chunk-3x5g06mw.js";import"./chunk-4dt1gfb3.js";import"./chunk-xmwp6je4.js";import{PLACEHOLDER_TASK_TITLE}from"./chunk-2xsxcdp6.js";import"./chunk-1vxtd8rh.js";import"./chunk-gq36nzg8.js";import"./chunk-mqfvzdpr.js";import"./chunk-hpay0ax8.js";import"./chunk-catrr0z3.js";import"./chunk-895yqjjz.js";import"./chunk-sak1tphp.js";import"./chunk-y1xnjx8x.js";import"./chunk-2tfnscxk.js";import"./chunk-9rj2fx6v.js";import"./chunk-rfp0ayra.js";import"./chunk-t7j8g9f6.js";import"./chunk-dmzg9zdc.js";import"./chunk-40dgv828.js";import"./chunk-6xppe9bj.js";import"./chunk-2brn8e1y.js";import"./chunk-7mwvvj46.js";import"./chunk-32j6y5d1.js";import"./chunk-cmp64q4j.js";import"./chunk-7kysxgbq.js";import"./chunk-x1jxxmtc.js";import"./chunk-xy8be792.js";import"./chunk-h2m7em1e.js";import"./chunk-bejhca6z.js";import"./chunk-br8r72bf.js";import"./chunk-yt15y085.js";import"./chunk-z1nj22va.js";import"./chunk-zt7wfnva.js";import"./chunk-pn5ygqn6.js";import"./chunk-eqdbfgq5.js";import"./chunk-f8gg8cqh.js";import"./chunk-nc46gddg.js";import"./chunk-91sykzqq.js";export{PLACEHOLDER_TASK_TITLE,Orchestrator};
2
+ import{Orchestrator}from"./chunk-k3h4cpdy.js";import"./chunk-zr058kbe.js";import"./chunk-c1dz3qg3.js";import"./chunk-3x5g06mw.js";import"./chunk-4dt1gfb3.js";import"./chunk-xmwp6je4.js";import{PLACEHOLDER_TASK_TITLE}from"./chunk-2xsxcdp6.js";import"./chunk-1vxtd8rh.js";import"./chunk-gq36nzg8.js";import"./chunk-mqfvzdpr.js";import"./chunk-hpay0ax8.js";import"./chunk-catrr0z3.js";import"./chunk-895yqjjz.js";import"./chunk-sak1tphp.js";import"./chunk-y1xnjx8x.js";import"./chunk-2tfnscxk.js";import"./chunk-9rj2fx6v.js";import"./chunk-rfp0ayra.js";import"./chunk-t7j8g9f6.js";import"./chunk-dmzg9zdc.js";import"./chunk-40dgv828.js";import"./chunk-6xppe9bj.js";import"./chunk-2brn8e1y.js";import"./chunk-7mwvvj46.js";import"./chunk-32j6y5d1.js";import"./chunk-cmp64q4j.js";import"./chunk-7kysxgbq.js";import"./chunk-x1jxxmtc.js";import"./chunk-xy8be792.js";import"./chunk-h2m7em1e.js";import"./chunk-bejhca6z.js";import"./chunk-br8r72bf.js";import"./chunk-yt15y085.js";import"./chunk-z1nj22va.js";import"./chunk-zt7wfnva.js";import"./chunk-pn5ygqn6.js";import"./chunk-eqdbfgq5.js";import"./chunk-f8gg8cqh.js";import"./chunk-nc46gddg.js";import"./chunk-91sykzqq.js";export{PLACEHOLDER_TASK_TITLE,Orchestrator};
@@ -0,0 +1,3 @@
1
+ // @bun
2
+ import{canonicalize}from"./chunk-1vxtd8rh.js";import{applyJitter,exponentialBackoff}from"./chunk-2tfnscxk.js";import{getRemoteRepoConfig,isRemoteRepoKey,resolveRepoRoot}from"./chunk-rfp0ayra.js";var canonPath=canonicalize;function randomDirTaskSuffix(){return Math.random().toString(36).slice(2,6).padEnd(4,"0")}function titleFromRepo(repo){let segs=repo.split(/[/\\]/).filter(Boolean);return segs.length>0?segs[segs.length-1]??repo:repo}function normalizeMainRepo(repo){let normalized=resolveRepoRoot(repo);return{repo:normalized,key:isRemoteRepoKey(normalized)?normalized:canonPath(normalized)}}function repoWorkingDir(repo){return getRemoteRepoConfig(repo)?.basePath??repo}var GH_PR_VIEW_FIELDS="number,url,title,state,baseRefName,headRefOid,reviewDecision,mergeable,statusCheckRollup,additions,deletions";function lifecycleFromState(state,reviewDecision){switch((state??"").toUpperCase()){case"MERGED":return"merged";case"CLOSED":return"closed";case"OPEN":return(reviewDecision??"").toUpperCase()==="APPROVED"?"ready_to_merge":"open";default:return"unknown"}}function entryCheckState(entry){let status=(entry.status??"").toUpperCase(),conclusion=(entry.conclusion??"").toUpperCase(),contextState=(entry.state??"").toUpperCase();if(status&&status!=="COMPLETED")return"pending";if(conclusion){if(conclusion==="SUCCESS"||conclusion==="NEUTRAL"||conclusion==="SKIPPED")return"passing";if(conclusion==="CANCELLED")return"cancelled";return"failing"}if(contextState==="SUCCESS")return"passing";if(contextState==="PENDING"||contextState==="EXPECTED")return"pending";if(contextState==="FAILURE"||contextState==="ERROR")return"failing";return"unknown"}function checkStateFromRollup(rollup){if(!rollup||rollup.length===0)return"none";let sawPending=!1,sawUnknown=!1,sawVerdict=!1;for(let entry of rollup){let s=entryCheckState(entry);if(s==="failing")return"failing";if(s==="cancelled")continue;if(sawVerdict=!0,s==="pending")sawPending=!0;else if(s==="unknown")sawUnknown=!0}if(sawPending)return"pending";if(sawUnknown)return"unknown";return sawVerdict?"passing":"none"}function mapGhPrView(view,at){if(!view||typeof view.number!=="number")return null;return{provider:"github",lifecycle:lifecycleFromState(view.state,view.reviewDecision),checkState:checkStateFromRollup(view.statusCheckRollup),number:view.number,url:view.url,title:view.title,...typeof view.additions==="number"?{additions:view.additions}:{},...typeof view.deletions==="number"?{deletions:view.deletions}:{},baseRef:view.baseRefName,reviewDecision:view.reviewDecision||void 0,mergeable:view.mergeable||void 0,...view.headRefOid?{headSha:view.headRefOid}:{},lastCheckedAt:at}}function samePrStatus(a,b){if(a===b)return!0;if(!a||!b)return!1;return a.provider===b.provider&&a.lifecycle===b.lifecycle&&a.checkState===b.checkState&&a.number===b.number&&a.url===b.url&&a.title===b.title&&a.additions===b.additions&&a.deletions===b.deletions&&a.baseRef===b.baseRef&&a.reviewDecision===b.reviewDecision&&a.mergeable===b.mergeable&&a.headSha===b.headSha}var NO_REMOTE_PATTERNS=["none of the git remotes","no git remote","to use github cli in a github repository","not a github repository"],AUTH_PATTERNS=["gh auth login","authentication required","not logged in","http 401","bad credentials","requires authentication"],NETWORK_PATTERNS=["could not resolve host","no such host","dial tcp","connection refused","connection reset","network is unreachable","i/o timeout","timeout","tls handshake","http 5","rate limit","try again"];function matchesAny(haystack,needles){return needles.some((n)=>haystack.includes(n))}function classifyGhFailure(s){if(s.parseError)return{kind:"error",error:"parse"};if(s.timedOut)return{kind:"error",error:"timeout"};if(s.spawnError)return{kind:"error",error:"missing-binary"};let err=(s.stderr??"").toLowerCase();if(matchesAny(err,NO_REMOTE_PATTERNS))return{kind:"error",error:"no-remote"};if(matchesAny(err,AUTH_PATTERNS))return{kind:"error",error:"auth"};if(matchesAny(err,NETWORK_PATTERNS))return{kind:"error",error:"network"};return{kind:"error",error:"unknown"}}function nextPrPoll(outcome,prevFailures,now,cfg,rand=Math.random){let at=(delay)=>now+applyJitter(delay,cfg.jitterRatio,rand);if(outcome.kind==="pr")return{nextAllowedAt:at(outcome.settled?cfg.settledMs:cfg.tickMs),failures:0};if(outcome.kind==="empty")return{nextAllowedAt:at(cfg.noPrMs),failures:0};if(outcome.error==="no-remote")return{nextAllowedAt:at(cfg.noRemoteMs),failures:0};let failures=prevFailures+1;return{nextAllowedAt:at(exponentialBackoff(cfg.failureBaseMs,failures-1,cfg.failureCapMs)),failures}}function checkResolutionNotify(prev,next){if(prev!=="pending")return null;if(next==="passing"||next==="failing")return next;return null}
3
+ export{GH_PR_VIEW_FIELDS,mapGhPrView,samePrStatus,classifyGhFailure,nextPrPoll,checkResolutionNotify,canonPath,randomDirTaskSuffix,titleFromRepo,normalizeMainRepo,repoWorkingDir};
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import{isPathLikeArg}from"./chunk-qc9sj5wh.js";import{matchPathGlob}from"./chunk-kg11c1py.js";import{CURRENT_VERSION}from"./chunk-2wrt80ft.js";import{switchFlag}from"./chunk-zsfyj36g.js";import{expandTilde}from"./chunk-2r1fgng9.js";import{errorMessage}from"./chunk-2brn8e1y.js";import{rejectionReason}from"./chunk-7mwvvj46.js";import{BUILTIN_VENDORS,coerceVendorId,isBuiltinVendor}from"./chunk-h2m7em1e.js";import"./chunk-z1nj22va.js";import{ROVE_PRODUCT_NAME}from"./chunk-zt7wfnva.js";import"./chunk-pn5ygqn6.js";import"./chunk-f8gg8cqh.js";import"./chunk-nc46gddg.js";import{__require}from"./chunk-91sykzqq.js";import{resolve}from"path";var DYNAMIC_COMMANDS=new Map([["completions",async(args)=>{let{runCompletionsSubcommand}=await import("./chunk-fkgemeww.js");await runCompletionsSubcommand(args)}],["export",async(args)=>{let{runExportSubcommand}=await import("./chunk-aqwckba5.js");await runExportSubcommand(args)}],["repo",async(args)=>{let{runRepoSubcommand}=await import("./chunk-tjhw09bz.js");await runRepoSubcommand(args)}],["api",async(args)=>{let{runApiSubcommand}=await import("./chunk-7fya3y0n.js");await runApiSubcommand(args)}],["update",async(args)=>{let{runUpdateSubcommand}=await import("./chunk-amq030gb.js");await runUpdateSubcommand(args)}],["theme",async(args)=>{let{runThemeSubcommand}=await import("./chunk-ggjmcqpt.js");await runThemeSubcommand(args)}],["feedback",async(args)=>{let{runFeedbackSubcommand}=await import("./chunk-xmjknfy2.js");await runFeedbackSubcommand(args)}],["daemon",async(args)=>{let{runDaemonSubcommand}=await import("./chunk-r98h6b42.js");await runDaemonSubcommand(args)}],["machine",async(args)=>{let{runMachineSubcommand}=await import("./chunk-wz307hf6.js");await runMachineSubcommand(args)}],["weixin",async(args)=>{let{runChannelCli}=await import("./chunk-5kgq379z.js");await runChannelCli("weixin",args)}],["doctor",async(args)=>{let{runDoctorSubcommand}=await import("./chunk-jk4f0xsh.js");await runDoctorSubcommand(args)}],["config",async(args)=>{let{runConfigSubcommand}=await import("./chunk-w7v71t7e.js");await runConfigSubcommand(args)}],["reset",async(args)=>{let{runResetSubcommand}=await import("./chunk-jkgp816v.js");await runResetSubcommand(args)}],["pty-host",async(args)=>{let{runPtyHostSubcommand}=await import("./chunk-bw2y7670.js");await runPtyHostSubcommand(args)}],["skill",async(args)=>{let{runSkillSubcommand}=await import("./chunk-ebdp0z1d.js");await runSkillSubcommand(args)}],["plugin",async(args)=>{let{runPluginSubcommand}=await import("./chunk-6mk6ceb6.js");await runPluginSubcommand(args)}],["hook",async(args)=>{let{runHookSubcommand}=await import("./chunk-j19av093.js");await runHookSubcommand(args)}]]);function topLevelUsage(cliName=ROVE_PRODUCT_NAME){return[`${cliName} ${CURRENT_VERSION}`,"",`Usage: ${cliName} [command] [options]`,"","Run with no command to launch PureTUI.",`Run \`${cliName} .\` (or \`${cliName} <path>\`) to open a directory as a standalone task.`,"","Commands:"," completions <shell> Generate shell completion script (bash/zsh/fish)"," add [path] Save a repo path for the new-task picker"," remove [path] Forget a saved project (inverse of add; non-destructive)"," adopt [glob] Import existing git worktrees as tasks"," export [--csv|--json] Print the task list (json/csv/table; daemon-free)"," repo <verb> Per-repo init script + first prompt (show|set|unset)",` api <verb> Scriptable RPC surface for agents (see \`${cliName} api --help\`)`," daemon <verb> Manage the daemon (start|stop|status|restart)"," machine <verb> Other computers running Rove (add|doctor|remove|list)"," weixin <verb> Talk to Rove from WeChat (login|logout|status|allow|deny|send|agent)"," doctor [--report|--fix] Diagnose daemon/PTY/engines/git; --fix walks the remedies",` config [--path] Open ${cliName}'s config file (state.json) in your editor`," reset [--hard] Stop runtimes; optionally wipe task/UI state"," theme <verb> Manage user themes (list|add|remove)",` skill <verb> Install the ${cliName} agent skill (install|status|command|print)`," plugin <verb> Install and run plugins (install|link|list|action|\u2026)"," feedback Send feedback to GitHub Discussions",` update [version|channel|list] Self-update ${cliName}, switch channel, or list versions`,"","Options:"," -v, --version Print version"," -h, --help Print this help"," --skill Print the agent skill file and exit"," --web [bridge flags] Serve the browser client on 127.0.0.1 and print its link"].join(`
4
- `)}var bridge=()=>import("./chunk-mqvkd2g1.js"),CLI_NAME=ROVE_PRODUCT_NAME,ADD_USAGE=[`Usage: ${CLI_NAME} add [path]`,` ${CLI_NAME} add --remote --host <host> --user <user> --path <basePath> [--port N] [--key [path] | --password]`,"","Save a repo for the new-task picker. With --remote, register an SSH-backed","project whose worktrees live on <host>; engine launch over SSH is pending.",""].join(`
3
+ import{isPathLikeArg}from"./chunk-j5y7hhc2.js";import{matchPathGlob}from"./chunk-kg11c1py.js";import{CURRENT_VERSION}from"./chunk-yn01fet6.js";import{switchFlag}from"./chunk-zsfyj36g.js";import{expandTilde}from"./chunk-2r1fgng9.js";import{errorMessage}from"./chunk-2brn8e1y.js";import{rejectionReason}from"./chunk-7mwvvj46.js";import{BUILTIN_VENDORS,coerceVendorId,isBuiltinVendor}from"./chunk-h2m7em1e.js";import"./chunk-z1nj22va.js";import{ROVE_PRODUCT_NAME}from"./chunk-zt7wfnva.js";import"./chunk-pn5ygqn6.js";import"./chunk-f8gg8cqh.js";import"./chunk-nc46gddg.js";import{__require}from"./chunk-91sykzqq.js";import{resolve}from"path";var DYNAMIC_COMMANDS=new Map([["completions",async(args)=>{let{runCompletionsSubcommand}=await import("./chunk-q2nhmaq9.js");await runCompletionsSubcommand(args)}],["export",async(args)=>{let{runExportSubcommand}=await import("./chunk-aqwckba5.js");await runExportSubcommand(args)}],["repo",async(args)=>{let{runRepoSubcommand}=await import("./chunk-tjhw09bz.js");await runRepoSubcommand(args)}],["api",async(args)=>{let{runApiSubcommand}=await import("./chunk-c9ptxthj.js");await runApiSubcommand(args)}],["update",async(args)=>{let{runUpdateSubcommand}=await import("./chunk-3qj9yw5c.js");await runUpdateSubcommand(args)}],["theme",async(args)=>{let{runThemeSubcommand}=await import("./chunk-x94e0rw4.js");await runThemeSubcommand(args)}],["feedback",async(args)=>{let{runFeedbackSubcommand}=await import("./chunk-f23manyf.js");await runFeedbackSubcommand(args)}],["daemon",async(args)=>{let{runDaemonSubcommand}=await import("./chunk-k2rmj0v3.js");await runDaemonSubcommand(args)}],["machine",async(args)=>{let{runMachineSubcommand}=await import("./chunk-k9xabf0t.js");await runMachineSubcommand(args)}],["weixin",async(args)=>{let{runChannelCli}=await import("./chunk-b5fdqynx.js");await runChannelCli("weixin",args)}],["doctor",async(args)=>{let{runDoctorSubcommand}=await import("./chunk-03fv92ya.js");await runDoctorSubcommand(args)}],["config",async(args)=>{let{runConfigSubcommand}=await import("./chunk-w7v71t7e.js");await runConfigSubcommand(args)}],["reset",async(args)=>{let{runResetSubcommand}=await import("./chunk-q72w3dsx.js");await runResetSubcommand(args)}],["pty-host",async(args)=>{let{runPtyHostSubcommand}=await import("./chunk-j2e7jgh8.js");await runPtyHostSubcommand(args)}],["skill",async(args)=>{let{runSkillSubcommand}=await import("./chunk-95kk75wh.js");await runSkillSubcommand(args)}],["plugin",async(args)=>{let{runPluginSubcommand}=await import("./chunk-9qpea48c.js");await runPluginSubcommand(args)}],["hook",async(args)=>{let{runHookSubcommand}=await import("./chunk-j19av093.js");await runHookSubcommand(args)}]]);function topLevelUsage(cliName=ROVE_PRODUCT_NAME){return[`${cliName} ${CURRENT_VERSION}`,"",`Usage: ${cliName} [command] [options]`,"","Run with no command to launch PureTUI.",`Run \`${cliName} .\` (or \`${cliName} <path>\`) to open a directory as a standalone task.`,"","Commands:"," completions <shell> Generate shell completion script (bash/zsh/fish)"," add [path] Save a repo path for the new-task picker"," remove [path] Forget a saved project (inverse of add; non-destructive)"," adopt [glob] Import existing git worktrees as tasks"," export [--csv|--json] Print the task list (json/csv/table; daemon-free)"," repo <verb> Per-repo init script + first prompt (show|set|unset)",` api <verb> Scriptable RPC surface for agents (see \`${cliName} api --help\`)`," daemon <verb> Manage the daemon (start|stop|status|restart)"," machine <verb> Other computers running Rove (add|doctor|remove|list)"," weixin <verb> Talk to Rove from WeChat (login|logout|status|allow|deny|send|agent)"," doctor [--report|--fix] Diagnose daemon/PTY/engines/git; --fix walks the remedies",` config [--path] Open ${cliName}'s config file (state.json) in your editor`," reset [--hard] Stop runtimes; optionally wipe task/UI state"," theme <verb> Manage user themes (list|add|remove)",` skill <verb> Install the ${cliName} agent skill (install|status|command|print)`," plugin <verb> Install and run plugins (install|link|list|action|\u2026)"," feedback File a GitHub issue on the Rove repo",` update [version|channel|list] Self-update ${cliName}, switch channel, or list versions`,"","Options:"," -v, --version Print version"," -h, --help Print this help"," --skill Print the agent skill file and exit"," --web [bridge flags] Serve the browser client on 127.0.0.1 and print its link"].join(`
4
+ `)}var bridge=()=>import("./chunk-xqvyzv62.js"),CLI_NAME=ROVE_PRODUCT_NAME,ADD_USAGE=[`Usage: ${CLI_NAME} add [path]`,` ${CLI_NAME} add --remote --host <host> --user <user> --path <basePath> [--port N] [--key [path] | --password]`,"","Save a repo for the new-task picker. With --remote, register an SSH-backed","project whose worktrees live on <host>; engine launch over SSH is pending.",""].join(`
5
5
  `);async function runAddSubcommand(rest){let arg=rest[0];if(arg==="--help"||arg==="-h"||arg==="help"){process.stdout.write(ADD_USAGE);return}if(arg==="--remote"){let{runAddRemote}=await import("./chunk-dsdtq087.js");await runAddRemote(rest.slice(1));return}if(arg?.startsWith("-"))process.stderr.write(`${CLI_NAME} add: unknown flag "${arg}"
6
6
 
7
7
  ${ADD_USAGE}`),process.exit(2);let target=resolve(process.cwd(),expandTilde(arg&&arg.length>0?arg:".")),{addSavedRepo}=await import("./chunk-hv79ys32.js"),result=addSavedRepo(target);if(result.rejected)process.stderr.write(`${CLI_NAME} add: "${arg&&arg.length>0?arg:"."}" cannot be a project \u2014 ${rejectionReason(result.rejected)} (resolved to ${result.path}).
@@ -23,4 +23,4 @@ ${ADOPT_USAGE}
23
23
  pass a path glob to adopt, e.g. ${CLI_NAME} adopt '${repo}/*' --yes`);return}let matched=worktrees.filter(isMatch);if(matched.length===0){console.log(`
24
24
  no worktrees match glob ${JSON.stringify(glob)}`);return}if(!yes){console.log(`
25
25
  ${matched.length} worktree(s) match \u2014 re-run with --yes to adopt them`);return}await adoptWorktreesInto(orch,repo,matched,vendor),console.log(`done \u2014 adopted ${matched.length} worktree(s)`)}function printTopLevelUsage(out){out.write(`${topLevelUsage()}
26
- `)}var COMMANDS=new Map([["add",runAddSubcommand],["remove",runRemoveSubcommand],["adopt",runAdoptSubcommand],...DYNAMIC_COMMANDS]);async function main(){let[,,...rawArgs]=process.argv,[subcommand,...rest]=rawArgs;if(subcommand==="--version"||subcommand==="-v"||subcommand==="version"){let{CURRENT_VERSION:CURRENT_VERSION2}=await import("./chunk-f262ywvg.js");console.log(`${CLI_NAME} ${CURRENT_VERSION2}`);return}if(subcommand==="--help"||subcommand==="-h"||subcommand==="help"){printTopLevelUsage(process.stdout);return}if(subcommand==="--skill"){let{runSkillSubcommand}=await import("./chunk-ebdp0z1d.js");await runSkillSubcommand(["print"]);return}if(subcommand==="--web"){let{runWebSubcommand}=await import("./chunk-12wn9jtf.js");await runWebSubcommand(rest);return}let handler=subcommand!==void 0?COMMANDS.get(subcommand):void 0;if(handler){await handler(rest);return}if(subcommand!==void 0&&isPathLikeArg(subcommand)){let{runOpenDirectory}=await import("./chunk-02x0ahxk.js");await runOpenDirectory(subcommand);return}if(subcommand!==void 0)console.error(`${CLI_NAME}: unknown command '${subcommand}'`),printTopLevelUsage(process.stderr),process.exit(2);let{publishRoveTerminalTitle}=await import("./chunk-g8wrdp7n.js");publishRoveTerminalTitle();let{launchTui}=await import("./chunk-azt0c2b5.js");await launchTui()}var cliDone=main().catch(async(err)=>{let{formatCliFailure}=await import("./chunk-qwwt4ch2.js");console.error(formatCliFailure(err,{cliName:CLI_NAME,argv:process.argv,cwd:process.cwd(),env:process.env})),process.exit(1)});export{cliDone};
26
+ `)}var COMMANDS=new Map([["add",runAddSubcommand],["remove",runRemoveSubcommand],["adopt",runAdoptSubcommand],...DYNAMIC_COMMANDS]);async function main(){let[,,...rawArgs]=process.argv,[subcommand,...rest]=rawArgs;if(subcommand==="--version"||subcommand==="-v"||subcommand==="version"){let{CURRENT_VERSION:CURRENT_VERSION2}=await import("./chunk-7911jkq6.js");console.log(`${CLI_NAME} ${CURRENT_VERSION2}`);return}if(subcommand==="--help"||subcommand==="-h"||subcommand==="help"){printTopLevelUsage(process.stdout);return}if(subcommand==="--skill"){let{runSkillSubcommand}=await import("./chunk-95kk75wh.js");await runSkillSubcommand(["print"]);return}if(subcommand==="--web"){let{runWebSubcommand}=await import("./chunk-3pgp6z3k.js");await runWebSubcommand(rest);return}let handler=subcommand!==void 0?COMMANDS.get(subcommand):void 0;if(handler){await handler(rest);return}if(subcommand!==void 0&&isPathLikeArg(subcommand)){let{runOpenDirectory}=await import("./chunk-kp43j420.js");await runOpenDirectory(subcommand);return}if(subcommand!==void 0)console.error(`${CLI_NAME}: unknown command '${subcommand}'`),printTopLevelUsage(process.stderr),process.exit(2);let{publishRoveTerminalTitle}=await import("./chunk-g8wrdp7n.js");publishRoveTerminalTitle();let{launchTui}=await import("./chunk-xekk4k54.js");await launchTui()}var cliDone=main().catch(async(err)=>{let{formatCliFailure}=await import("./chunk-x119xqa6.js");console.error(formatCliFailure(err,{cliName:CLI_NAME,argv:process.argv,cwd:process.cwd(),env:process.env})),process.exit(1)});export{cliDone};
package/dist/cli/rove.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{join as l}from"node:path";import{createInterface as a}from"node:readline/promises";import{pathToFileURL as ss}from"node:url";import{spawnSync as Q}from"node:child_process";import{constants as e,accessSync as V}from"node:fs";import{homedir as R}from"node:os";import{basename as o,delimiter as I,dirname as U,join as y}from"node:path";import{fileURLToPath as T}from"node:url";var H={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.280",description:"Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{rove:"dist/cli/rove.js"},files:["dist/cli","dist/completions","dist/web","dist/web-ui","dist/skills","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:mock":"ROVE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts","build:rubric":"bun run scripts/build-tier-rubric.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"ROVE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"ROVE_INCLUDE_SOCKET=1 ROVE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"ROVE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun scripts/render-track.mjs",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0",react:"^19.2.8","smol-toml":"^1.7.1",ws:"^8.21.3"},optionalDependencies:{"node-pty":"^1.1.0"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/rove-daemon":"0.7.23","@tsconfig/bun":"1.0.11","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1","rove-bridge":"0.0.41","rove-weixin":"0.0.41",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var q="rove";var k=q;var z="ROVE_BUN",_="ROVE_NO_BUN_BOOTSTRAP",F="ROVE_SKIP_BUN_CHECK",$=H.engines.bun.match(/\d+\.\d+\.\d+/)?.[0]??"0.0.0",L=(s)=>{try{return V(s,e.X_OK),!0}catch{return!1}},B=(s)=>s==="win32"?"bun.exe":"bun",f=(s)=>{let t=Q(s,["--version"],{encoding:"utf8",timeout:5000});if(t.error||t.status!==0)return null;return t.stdout?.split(`
3
- `)[0]?.trim()??""};function J(s){return s.trim().match(/^v?(\d+\.\d+\.\d+)/)?.[1]??null}function X(s,t=$){let c=J(s),W=J(t);if(!c||!W)return!0;let b=c.split(".").map(Number),g=W.split(".").map(Number);for(let p=0;p<3;p++){let r=b[p]??0,i=g[p]??0;if(r!==i)return r>i}return!0}function Y(s=process.env){return s[F]==="1"}function A(s={}){let t=s.env??process.env,c=s.platform??process.platform,W=s.home??t.HOME??t.USERPROFILE??R(),b=B(c),g=[],p=t[z]?.trim();if(p)g.push(p);for(let G of(t.PATH??t.Path??"").split(I))if(G)g.push(y(G,b));let r=t.BUN_INSTALL?.trim();if(r)g.push(y(r,"bin",b));if(W)g.push(y(W,".bun","bin",b));let i=s.launcherDir;if(i)g.push(y(i,"..","..","node_modules","bun","bin",b)),g.push(y(i,"..","..","..","bun","bin",b));return g}function N(s={}){let t=s.isExecutable??L;return A(s).find((c)=>t(c))??null}function Z(s={}){let t=s.isExecutable??L,c=s.bunVersionOf??f;if(Y(s.env??process.env))return{bun:N(s),stale:null,unusable:null};let W=null,b=null;for(let g of A(s)){if(!t(g))continue;let p=c(g);if(p===null){b??=g;continue}if(X(p))return{bun:g,stale:W,unusable:b};W??={path:g,version:J(p)??p}}return{bun:null,stale:W,unusable:b}}function h(s){return U(T(s))}function C(s){return o(T(s)).replace(/\.[cm]?[jt]s$/,"")}function n(s=process.platform){return s==="win32"?["powershell","-NoProfile","-Command","irm bun.sh/install.ps1 | iex"]:["bash","-c","curl -fsSL https://bun.sh/install | bash"]}function D(s=k,t=process.platform){let c=t==="win32"?'powershell -c "irm bun.sh/install.ps1 | iex"':"curl -fsSL https://bun.sh/install | bash";return[`${s}: Rove runs on the Bun runtime, and no Bun was found on this machine.`,"","Install Bun, then run this command again:",` ${c}`," npm install -g bun # any platform, if you already have npm","","Or install Bun and Rove together in one step:"," curl -fsSL https://rove.run/install.sh | sh","",`Already have Bun somewhere unusual? Point Rove at it: ${z}=/path/to/bun`,""].join(`
2
+ import{join as l}from"node:path";import{createInterface as a}from"node:readline/promises";import{pathToFileURL as ss}from"node:url";import{spawnSync as Q}from"node:child_process";import{constants as P,accessSync as V}from"node:fs";import{homedir as o}from"node:os";import{basename as R,delimiter as I,dirname as U,join as y}from"node:path";import{fileURLToPath as T}from"node:url";var H={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.281",description:"Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{rove:"dist/cli/rove.js"},files:["dist/cli","dist/completions","dist/web","dist/web-ui","dist/skills","dist/voice","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:mock":"ROVE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts","build:rubric":"bun run scripts/build-tier-rubric.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"ROVE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"ROVE_INCLUDE_SOCKET=1 ROVE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"ROVE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun scripts/render-track.mjs",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0",react:"^19.2.8","smol-toml":"^1.7.1",ws:"^8.21.3"},optionalDependencies:{"node-pty":"^1.1.0"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/rove-daemon":"0.7.23","@tsconfig/bun":"1.0.11","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1","rove-bridge":"0.0.42","rove-weixin":"0.0.42",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var q="rove";var k=q;var z="ROVE_BUN",_="ROVE_NO_BUN_BOOTSTRAP",F="ROVE_SKIP_BUN_CHECK",$=H.engines.bun.match(/\d+\.\d+\.\d+/)?.[0]??"0.0.0",L=(s)=>{try{return V(s,P.X_OK),!0}catch{return!1}},B=(s)=>s==="win32"?"bun.exe":"bun",f=(s)=>{let t=Q(s,["--version"],{encoding:"utf8",timeout:5000});if(t.error||t.status!==0)return null;return t.stdout?.split(`
3
+ `)[0]?.trim()??""};function J(s){return s.trim().match(/^v?(\d+\.\d+\.\d+)/)?.[1]??null}function X(s,t=$){let c=J(s),W=J(t);if(!c||!W)return!0;let b=c.split(".").map(Number),g=W.split(".").map(Number);for(let i=0;i<3;i++){let r=b[i]??0,x=g[i]??0;if(r!==x)return r>x}return!0}function Y(s=process.env){return s[F]==="1"}function A(s={}){let t=s.env??process.env,c=s.platform??process.platform,W=s.home??t.HOME??t.USERPROFILE??o(),b=B(c),g=[],i=t[z]?.trim();if(i)g.push(i);for(let G of(t.PATH??t.Path??"").split(I))if(G)g.push(y(G,b));let r=t.BUN_INSTALL?.trim();if(r)g.push(y(r,"bin",b));if(W)g.push(y(W,".bun","bin",b));let x=s.launcherDir;if(x)g.push(y(x,"..","..","node_modules","bun","bin",b)),g.push(y(x,"..","..","..","bun","bin",b));return g}function N(s={}){let t=s.isExecutable??L;return A(s).find((c)=>t(c))??null}function Z(s={}){let t=s.isExecutable??L,c=s.bunVersionOf??f;if(Y(s.env??process.env))return{bun:N(s),stale:null,unusable:null};let W=null,b=null;for(let g of A(s)){if(!t(g))continue;let i=c(g);if(i===null){b??=g;continue}if(X(i))return{bun:g,stale:W,unusable:b};W??={path:g,version:J(i)??i}}return{bun:null,stale:W,unusable:b}}function h(s){return U(T(s))}function C(s){return R(T(s)).replace(/\.[cm]?[jt]s$/,"")}function n(s=process.platform){return s==="win32"?["powershell","-NoProfile","-Command","irm bun.sh/install.ps1 | iex"]:["bash","-c","curl -fsSL https://bun.sh/install | bash"]}function D(s=k,t=process.platform){let c=t==="win32"?'powershell -c "irm bun.sh/install.ps1 | iex"':"curl -fsSL https://bun.sh/install | bash";return[`${s}: Rove runs on the Bun runtime, and no Bun was found on this machine.`,"","Install Bun, then run this command again:",` ${c}`," npm install -g bun # any platform, if you already have npm","","Or install Bun and Rove together in one step:"," curl -fsSL https://rove.run/install.sh | sh","",`Already have Bun somewhere unusual? Point Rove at it: ${z}=/path/to/bun`,""].join(`
4
4
  `)}function j(s,t,c=k){return[`${c}: this machine's Bun is too old for Rove — found ${t} at ${s}, need ${$} or newer.`,"","Rove's terminals are built on Bun's PTY API. An older Bun ignores it silently,","so every terminal and engine tab would open empty and stay empty.","","Upgrade Bun, then run this command again:"," bun upgrade # Bun installed itself (~/.bun)"," brew upgrade bun # Homebrew"," npm install -g bun@latest # npm-managed Bun","",`Have a newer Bun elsewhere? Point Rove at it: ${z}=/path/to/bun`,`Run on this one anyway (unsupported, terminals will be blank): ${F}=1`,""].join(`
5
5
  `)}function v(s,t=k){return[`${t}: the Bun at ${s} could not be run — \`${s} --version\` failed or never returned.`,"","Rove skipped it rather than starting through it, which would hang or fail","with a message from that binary instead of this one.","","Reinstall Bun, then run this command again:"," curl -fsSL https://bun.sh/install | bash","",`Have a working Bun elsewhere? Point Rove at it: ${z}=/path/to/bun`,""].join(`
6
6
  `)}function w(s=process.env,t=process.stdin,c=process.stdout){if(s[_]==="1")return!1;if(s.CI==="true"||s.CI==="1")return!1;return Boolean(t.isTTY&&c.isTTY)}function M(s={},t=Q){let c=s.platform??process.platform,[W,...b]=n(c);if(!W)return null;let g=t(W,b,{stdio:"inherit"});if(g.error||g.status!==0)return null;return Z(s).bun}function u(s){if(typeof s.status==="number")return s.status;if(s.signal)return 128+(Object.hasOwn(K,s.signal)?K[s.signal]:0);return 1}var K={SIGINT:2,SIGQUIT:3,SIGKILL:9,SIGTERM:15};function S(s,t,c,W=Q){let b=W(s,[t,...c],{stdio:"inherit"});if(b.error)return process.stderr.write(`rove: could not start Bun at ${s}: ${b.error.message}. Check that this path is executable, or reinstall Bun from https://bun.sh.
7
- `),1;return u(b)}var d=h(import.meta.url),x=C(import.meta.url),m=l(d,`${x}-run.js`);async function ts(s){let t=a({input:process.stdin,output:process.stdout});try{let c=(await t.question(s)).trim().toLowerCase();return c===""||c==="y"||c==="yes"}finally{t.close()}}async function cs(){let s={launcherDir:d},t=Z(s);if(t.bun||!w())return t;let c=t.stale?`${x}: Bun ${t.stale.version} is too old for Rove (needs ${$}+). Install the latest Bun now? [Y/n] `:`${x}: Rove runs on the Bun runtime, and none is installed. Install Bun now? [Y/n] `;if(!await ts(c))return t;return{bun:M(s),stale:t.stale,unusable:t.unusable}}var O=globalThis.Bun;if(O){let s=O.version;if(s&&!Y()&&!X(s))process.stderr.write(j(process.execPath,s,x)),process.exit(1);await import(ss(m).href)}else{let{bun:s,stale:t,unusable:c}=await cs();if(!s){let W=t?j(t.path,t.version,x):c?v(c,x):D(x);process.stderr.write(W),process.exit(1)}process.exit(S(s,m,process.argv.slice(2)))}
7
+ `),1;return u(b)}var O=h(import.meta.url),p=C(import.meta.url),d=l(O,`${p}-run.js`);async function ts(s){let t=a({input:process.stdin,output:process.stdout});try{let c=(await t.question(s)).trim().toLowerCase();return c===""||c==="y"||c==="yes"}finally{t.close()}}async function cs(){let s={launcherDir:O},t=Z(s);if(t.bun||!w())return t;let c=t.stale?`${p}: Bun ${t.stale.version} is too old for Rove (needs ${$}+). Install the latest Bun now? [Y/n] `:`${p}: Rove runs on the Bun runtime, and none is installed. Install Bun now? [Y/n] `;if(!await ts(c))return t;return{bun:M(s),stale:t.stale,unusable:t.unusable}}var m=globalThis.Bun;if(m){let s=m.version;if(s&&!Y()&&!X(s))process.stderr.write(j(process.execPath,s,p)),process.exit(1);await import(ss(d).href)}else{let{bun:s,stale:t,unusable:c}=await cs();if(!s){let W=t?j(t.path,t.version,p):c?v(c,p):D(p);process.stderr.write(W),process.exit(1)}process.exit(S(s,d,process.argv.slice(2)))}
@@ -3,7 +3,7 @@ name: rove
3
3
  description: Use when controlling Rove tasks, parallel coding attempts, hosted agent sessions, task lifecycle, or the daemon-owned issue tracker from a shell. Also the ONLY channel for messaging another agent session on this machine — `rove api send`, never a peer/MCP side channel.
4
4
  ---
5
5
 
6
- <!-- rove-skill-version: 108 — bump in lockstep with ROVE_SKILL_VERSION (src/lib/skill-install.ts). -->
6
+ <!-- rove-skill-version: 109 — bump in lockstep with ROVE_SKILL_VERSION (src/lib/skill-install.ts). -->
7
7
 
8
8
  # Rove shell control
9
9
 
@@ -309,9 +309,11 @@ Find your section by what you were ASKED, not by Rove's noun for it:
309
309
  | a prompt ends `[ROVE] … report with: rove api report`; tick a task unattended and want proof it reported, or skip a tick the task can't use | drive (`report`, `send --expect-report`, `send --if-runnable`) / read (`deliveries`, `should-run`) |
310
310
  | make / rename / retarget / land / close a task | create / edit / lifecycle |
311
311
  | materialize or adopt a checkout | worktree |
312
- | which engines exist, send Rove product feedback | discover / feedback |
312
+ | which engines exist, report a Rove problem or idea | discover / feedback |
313
313
  | an error code came back, `nextCommandArgs` | Error codes |
314
314
 
315
+ When the user says anything in Rove itself is awkward, broken or missing, offer to file it right away: draft a short title and body, show it, and on their OK run `rove feedback --kind bug|idea|question --title … --body-file -` (body on stdin), then share the URL. If it prints a prefilled link instead, hand that over and say to run `gh auth login`. Only for Rove; another project's bug is a `send` to its main task.
316
+
315
317
  Reach for `schema` when the binary and that file disagree.
316
318
 
317
319
  ```bash
@@ -433,7 +433,7 @@ schema --verb --group --all
433
433
  engine-list (none)
434
434
  repo-match --origin --root --allow-fork
435
435
  repo-list (none)
436
- feedback --title(REQ) --body(REQ) --category(feedback)
436
+ feedback --title(REQ) --body(REQ) --kind(bug)
437
437
  ```
438
438
  <!-- generated:end -->
439
439
 
@@ -452,9 +452,12 @@ resolves to (`{ repo, fork?, checkouts, searched }`); `repo-list` lists a
452
452
  machine's repos by id (`{ repos: [{ id, primary, checkouts }] }`) — pass
453
453
  `--machine <alias>` to ask another machine.
454
454
 
455
- `feedback` opens a GitHub Discussion in the Rove repo through `gh` — Rove's own
456
- product feedback channel, not a way to file work for another project (that is
457
- a `send` to their main task; see SKILL.md).
455
+ `feedback` files a GitHub issue on the Rove repo through `gh` (`--kind` picks the
456
+ label: bug, idea → enhancement, question) and appends an environment block.
457
+ It returns `{ filed: true, url, number }`; with no usable `gh` it returns
458
+ `{ filed: false, url, hint }` where `url` is a prefilled new-issue link — hand
459
+ it to the user. Rove's own product feedback channel, not a way to file work
460
+ for another project (that is a `send` to their main task; see SKILL.md).
458
461
 
459
462
  ## Error codes
460
463
 
@@ -0,0 +1,318 @@
1
+ """Dictation endpoint for rove's web client.
2
+
3
+ The phone records 16 kHz mono PCM, rove-bridge POSTs the WAV here with the text of the
4
+ conversation being dictated into, and this process returns the transcript. The recognizer is
5
+ local Whisper unless `~/.rove/voice/config.json` names a cloud endpoint (`engine.py`); the
6
+ vocabulary is `vocab.py`, sound-alike fixes `phonetic.py`. Everything this process keeps lives
7
+ under `~/.rove/voice`.
8
+
9
+ Run: `uv run --project scripts/voice --extra local scripts/voice/asr_server.py` (or Settings → Voice).
10
+ Binds 127.0.0.1 only; the bridge is the one public surface.
11
+
12
+ POST /transcribe {wav: hex, conversation?, context?, language?} -> {text, raw, language, ...}
13
+ POST /stream {session, seq, pcm: hex int16, final?, conversation?, context?, language?}
14
+ -> {partial} per chunk, the /transcribe reply at final (live.py)
15
+ GET /health
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import io
21
+ import json
22
+ import os
23
+ import secrets
24
+ import sys
25
+ import threading
26
+ import time
27
+ import wave
28
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ import numpy as np
33
+
34
+ import engine
35
+ import learn
36
+ import live
37
+ import phonetic
38
+ import polish
39
+ import vocab
40
+
41
+ PORT = int(os.environ.get("ROVE_VOICE_PORT", "8791"))
42
+ MIN_AUDIO_SEC = 0.25
43
+ SILENCE_RMS = 0.004 # 16-bit speech sits far above this; fan noise and dead mics do not.
44
+ MAX_AUDIO_SEC = 240
45
+
46
+ _ready = threading.Event()
47
+ _warm_ms: float | None = None
48
+ _error: str | None = None
49
+ _backend: engine.Backend | None = None
50
+ _config: dict[str, Any] = {}
51
+
52
+
53
+ def read_token() -> str:
54
+ """One shared secret so a stray local process cannot drive the microphone path."""
55
+ path = engine.HOME / "token"
56
+ engine.HOME.mkdir(parents=True, exist_ok=True)
57
+ if not path.exists():
58
+ path.write_text(secrets.token_hex(24))
59
+ path.chmod(0o600)
60
+ return path.read_text().strip()
61
+
62
+
63
+ def decode_wav(data: bytes) -> np.ndarray:
64
+ with wave.open(io.BytesIO(data)) as wav:
65
+ if wav.getnchannels() != 1:
66
+ raise ValueError("expected mono audio")
67
+ width = wav.getsampwidth()
68
+ rate = wav.getframerate()
69
+ frames = wav.readframes(wav.getnframes())
70
+ if width != 2:
71
+ raise ValueError(f"expected 16-bit samples, got {width * 8}-bit")
72
+ if rate != engine.SAMPLE_RATE:
73
+ raise ValueError(f"expected {engine.SAMPLE_RATE} Hz, got {rate}")
74
+ return np.frombuffer(frames, dtype="<i2").astype(np.float32) / 32768.0
75
+
76
+
77
+ def silent(pcm: np.ndarray) -> bool:
78
+ return not len(pcm) or float(np.sqrt(np.mean(np.square(pcm)))) < SILENCE_RMS
79
+
80
+
81
+ def dictate(
82
+ backend: engine.Backend,
83
+ pcm: np.ndarray,
84
+ *,
85
+ conversation: str | None,
86
+ draft: str | None,
87
+ language: str | None,
88
+ config: dict[str, Any],
89
+ final: bool = True,
90
+ ) -> dict[str, Any]:
91
+ """One dictation: rank the vocabulary, recognize, normalize the script, then fix the English:
92
+ stored mishearings first, then whatever lands close to a term the recognizer was given. A final
93
+ take is then cleaned up by a chat model when config.json asks for it (polish.py); a live take's
94
+ partials never are."""
95
+ stored = vocab.load()["terms"]
96
+ terms = vocab.rank(conversation, draft, stored, backend.max_terms)
97
+ # Measured on large-v3: terms in the prompt cost a term when they were not said (68 -> 67 with the
98
+ # conversation's, 68 -> 64 with 15 learned ones) and gain less (70) than using them only to correct
99
+ # (72, and 68 when not said). Cloud recognizers take the prompt without that cost, so they get it by default.
100
+ in_prompt = config.get("promptContext", not isinstance(backend, engine.LocalWhisper))
101
+ hinted = terms if in_prompt else vocab.rank(None, draft, stored, backend.max_terms, rest=False)
102
+ raw, detected = backend.transcribe(pcm, hinted, language)
103
+ text = engine.simplified(raw) if config.get("simplified", True) else raw
104
+ text = vocab.correct(vocab.apply_spoken(text, stored), terms, fuzzy=config.get("fuzzy", True))
105
+ # Bench: +1 to +3 terms when the conversation holds them, no change otherwise (phonetic.py).
106
+ if config.get("phonetic", True):
107
+ text = phonetic.correct(text, terms)
108
+ out = {"text": text, "raw": raw, "language": detected, "terms": len(terms)}
109
+ if final and polish.enabled(config):
110
+ started = time.monotonic()
111
+ polished, error = polish.polish(text, terms, config)
112
+ out |= {"text": polished, "unpolished": text, "polishMs": round((time.monotonic() - started) * 1000)}
113
+ if error:
114
+ out["polishError"] = error
115
+ sys.stderr.write(f"voice: polish failed, kept the recognizer's text: {error}\n")
116
+ return out
117
+
118
+
119
+ def warm() -> None:
120
+ """A cold graph costs seconds; pay it at boot, not on the first press."""
121
+ global _warm_ms, _error, _backend, _config
122
+ started = time.monotonic()
123
+ try:
124
+ _config = engine.load_config()
125
+ _backend = engine.backend(_config)
126
+ if isinstance(_backend, engine.LocalWhisper):
127
+ _backend.transcribe(np.zeros(engine.SAMPLE_RATE, dtype=np.float32), [], "en")
128
+ engine.simplified("說")
129
+ except Exception as err: # noqa: BLE001 - surfaced on /health, the server stays up
130
+ _error = str(err)
131
+ sys.stderr.write(f"voice: backend failed: {err}\n")
132
+ return
133
+ _warm_ms = round((time.monotonic() - started) * 1000)
134
+ _ready.set()
135
+
136
+
137
+ class Handler(BaseHTTPRequestHandler):
138
+ token = ""
139
+
140
+ def log_message(self, fmt: str, *args: Any) -> None: # noqa: A003 - stdlib signature
141
+ sys.stderr.write("voice %s\n" % (fmt % args))
142
+
143
+ def reply(self, code: int, body: dict[str, Any]) -> None:
144
+ data = json.dumps(body, ensure_ascii=False).encode()
145
+ self.send_response(code)
146
+ self.send_header("content-type", "application/json")
147
+ self.send_header("content-length", str(len(data)))
148
+ self.end_headers()
149
+ self.wfile.write(data)
150
+
151
+ def do_GET(self) -> None: # noqa: N802 - stdlib signature
152
+ # /health stays open: the bridge probes it before the user says anything.
153
+ if self.path != "/health":
154
+ self.reply(404, {"error": "not_found"})
155
+ return
156
+ name = _backend.name if _backend else None
157
+ body = {"ready": _ready.is_set(), "model": name, "warm_ms": _warm_ms, "error": _error}
158
+ # Which copy answers: two bridges (release and a checkout) may each have started one.
159
+ self.reply(200, {**body, "pid": os.getpid(), "code": str(Path(__file__).resolve().parent)})
160
+
161
+ def body(self) -> dict[str, Any] | None:
162
+ if self.headers.get("x-voice-token", "") != self.token:
163
+ self.reply(401, {"error": "bad_token"})
164
+ return None
165
+ length = int(self.headers.get("content-length") or 0)
166
+ if length <= 0 or length > 32 * 1024 * 1024:
167
+ self.reply(400, {"error": "bad_size"})
168
+ return None
169
+ try:
170
+ return json.loads(self.rfile.read(length))
171
+ except ValueError:
172
+ self.reply(400, {"error": "bad_json"})
173
+ return None
174
+
175
+ def do_POST(self) -> None: # noqa: N802 - stdlib signature
176
+ routes = {
177
+ "/transcribe": self.transcribe,
178
+ "/stream": self.stream,
179
+ "/learn": self.learn_edit,
180
+ "/unlearn": self.unlearn_edit,
181
+ }
182
+ route = routes.get(self.path)
183
+ if route is None:
184
+ self.reply(404, {"error": "not_found"})
185
+ return
186
+ request = self.body()
187
+ if request is not None:
188
+ route(request)
189
+
190
+ def transcribe(self, request: dict[str, Any]) -> None:
191
+ try:
192
+ pcm = decode_wav(bytes.fromhex(request["wav"]))
193
+ except Exception as err: # noqa: BLE001 - every failure is the caller's audio
194
+ self.reply(400, {"error": f"bad_audio: {err}"})
195
+ return
196
+ self.finish_take(pcm, request.get("conversation"), request.get("context"), request.get("language"))
197
+
198
+ def finish_take(self, pcm: np.ndarray, conversation: str | None, draft: str | None, language: str | None) -> None:
199
+ seconds = round(len(pcm) / engine.SAMPLE_RATE, 2)
200
+ if seconds < MIN_AUDIO_SEC:
201
+ self.reply(200, {"text": "", "reason": "too_short", "seconds": seconds})
202
+ return
203
+ if seconds > MAX_AUDIO_SEC:
204
+ self.reply(200, {"text": "", "reason": "too_long", "seconds": seconds})
205
+ return
206
+ if silent(pcm):
207
+ self.reply(200, {"text": "", "reason": "silent", "seconds": seconds})
208
+ return
209
+ if _backend is None or not _ready.is_set():
210
+ self.reply(503, {"error": _error or "warming up"})
211
+ return
212
+ started = time.monotonic()
213
+ try:
214
+ out = dictate(_backend, pcm, conversation=conversation, draft=draft, language=language, config=_config)
215
+ except Exception as err: # noqa: BLE001 - report, never take the server down
216
+ self.reply(500, {"error": f"transcribe: {err}"})
217
+ return
218
+ ms = round((time.monotonic() - started) * 1000)
219
+ # Counts only, never the words: enough to see the conversation arrived and what a take cost.
220
+ heard = len(conversation or "")
221
+ terms = out["terms"]
222
+ sys.stderr.write(f"voice: {seconds}s audio, {heard} chars of conversation, {terms} terms, {ms} ms\n")
223
+ self.reply(200, {**out, "seconds": seconds, "ms": ms})
224
+
225
+ def stream(self, request: dict[str, Any]) -> None:
226
+ """One chunk of a live take (live.py): the text so far while it grows, the transcript at `final`."""
227
+ session, seq = request.get("session"), request.get("seq")
228
+ if not isinstance(session, str) or not isinstance(seq, int):
229
+ self.reply(400, {"error": "bad_args"})
230
+ return
231
+ try:
232
+ raw = bytes.fromhex(request.get("pcm") or "")
233
+ pcm = np.frombuffer(raw[: len(raw) // 2 * 2], dtype="<i2").astype(np.float32) / 32768.0
234
+ take = live.append(session, seq, pcm, request)
235
+ except live.Gap as err:
236
+ self.reply(409, {"error": f"gap: {err}"})
237
+ return
238
+ except ValueError as err:
239
+ self.reply(400, {"error": f"bad_audio: {err}"})
240
+ return
241
+ if request.get("final"):
242
+ live.finish(session)
243
+ self.finish_take(take.pcm(), take.conversation, take.draft, take.language)
244
+ return
245
+ backend = _backend
246
+ if backend is None or not _ready.is_set() or not live.due(take, backend):
247
+ self.reply(200, {"partial": None})
248
+ return
249
+ # Never queue behind another decode: the next chunk asks again.
250
+ if not engine.GPU.acquire(blocking=False):
251
+ self.reply(200, {"partial": None})
252
+ return
253
+ try:
254
+ take.decoded = take.samples
255
+ pcm = take.pcm()
256
+ text = (
257
+ ""
258
+ if silent(pcm)
259
+ else dictate(
260
+ backend,
261
+ pcm,
262
+ conversation=take.conversation,
263
+ draft=take.draft,
264
+ language=take.language,
265
+ config=_config,
266
+ final=False,
267
+ )["text"]
268
+ )
269
+ except Exception as err: # noqa: BLE001 - a failed partial only means no partial
270
+ sys.stderr.write(f"voice: partial failed: {err}\n")
271
+ text = None
272
+ finally:
273
+ engine.GPU.release()
274
+ self.reply(200, {"partial": text})
275
+
276
+ def learn_edit(self, request: dict[str, Any]) -> None:
277
+ """A sent message against the draft its takes left: the fixes inside them join the vocabulary."""
278
+ base, sent, spans = request.get("base"), request.get("sent"), request.get("spans")
279
+ if not isinstance(base, str) or not isinstance(sent, str) or not isinstance(spans, list):
280
+ self.reply(400, {"error": "bad_args"})
281
+ return
282
+ if not _config.get("learn", True):
283
+ self.reply(200, {"learned": []})
284
+ return
285
+ pairs = [(int(s[0]), int(s[1])) for s in spans if isinstance(s, list) and len(s) == 2]
286
+ added = learn.remember(learn.fixes(base, pairs, sent))
287
+ sys.stderr.write(f"voice: learned {len(added)} from an edit\n")
288
+ self.reply(200, {"learned": added})
289
+
290
+ def unlearn_edit(self, request: dict[str, Any]) -> None:
291
+ learned = request.get("learned")
292
+ if not isinstance(learned, list):
293
+ self.reply(400, {"error": "bad_args"})
294
+ return
295
+ self.reply(200, {"undone": learn.forget([x for x in learned if isinstance(x, dict)])})
296
+
297
+
298
+ def main() -> None:
299
+ Handler.token = read_token()
300
+ try:
301
+ server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler)
302
+ except OSError as err:
303
+ # Another sidecar already answers here; it serves every bridge on this machine.
304
+ sys.stderr.write(f"voice: port {PORT} is taken ({err}); leaving it to that process\n")
305
+ raise SystemExit(0) from err
306
+ pidfile = engine.HOME / "sidecar.pid"
307
+ pidfile.write_text(str(os.getpid()))
308
+ threading.Thread(target=warm, daemon=True).start()
309
+ sys.stderr.write(f"voice listening on http://127.0.0.1:{PORT}\n")
310
+ try:
311
+ server.serve_forever()
312
+ finally:
313
+ if pidfile.exists() and pidfile.read_text().strip() == str(os.getpid()):
314
+ pidfile.unlink()
315
+
316
+
317
+ if __name__ == "__main__":
318
+ main()