vibelet 0.1.22 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/vibelet.mjs CHANGED
@@ -25,6 +25,7 @@ const stdoutLogPath = join(logDir, 'daemon.stdout.log');
25
25
  const stderrLogPath = join(logDir, 'daemon.stderr.log');
26
26
  const pidFilePath = join(vibeletDir, 'daemon.pid');
27
27
  const relayConfigPath = join(vibeletDir, 'relay.json');
28
+ const tunnelStatePath = join(vibeletDir, 'tunnel.json');
28
29
  const updateCheckPath = join(vibeletDir, 'update-check.json');
29
30
  const OFFICIAL_SITE_URL = 'https://vibelet.icu';
30
31
  const UPDATE_CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
@@ -595,7 +596,9 @@ function printHelp() {
595
596
  process.stdout.write(`Usage:\n`);
596
597
  process.stdout.write(` npx ${packageJson.name} Install/start the daemon and print a pairing QR code\n`);
597
598
  process.stdout.write(` npx ${packageJson.name} start Same as above\n`);
598
- process.stdout.write(` npx ${packageJson.name} --relay <url> Use a tunnel URL for remote access\n`);
599
+ process.stdout.write(` npx ${packageJson.name} --tunnel Auto-start a Cloudflare Tunnel for remote access\n`);
600
+ process.stdout.write(` npx ${packageJson.name} --tunnel --force Force a new tunnel (discard existing)\n`);
601
+ process.stdout.write(` npx ${packageJson.name} --relay <url> Use a custom tunnel URL for remote access\n`);
599
602
  process.stdout.write(` npx ${packageJson.name} --host <ip> Set the primary host/IP address\n`);
600
603
  process.stdout.write(` npx ${packageJson.name} --fallback-hosts <ips> Comma-separated fallback IPs\n`);
601
604
  process.stdout.write(` npx ${packageJson.name} stop Stop the daemon\n`);
@@ -609,20 +612,29 @@ function printHelp() {
609
612
  process.stdout.write(`You can also invoke the published alias with:\n`);
610
613
  process.stdout.write(` npx ${packageJson.name === 'vibelet' ? '@vibelet/cli' : 'vibelet'}\n`);
611
614
  process.stdout.write(` vibelet\n\n`);
612
- process.stdout.write(`Remote access (relay):\n`);
613
- process.stdout.write(` Start a tunnel with one of these tools, then copy the generated URL:\n\n`);
614
- process.stdout.write(` # Cloudflare Tunnel (free, no account needed)\n`);
615
- process.stdout.write(` npx cloudflared tunnel --protocol http2 --url http://localhost:${port}\n\n`);
616
- process.stdout.write(` # ngrok (free, requires sign-up)\n`);
617
- process.stdout.write(` ngrok http ${port}\n\n`);
618
- process.stdout.write(` Then start vibelet with the tunnel URL:\n`);
615
+ process.stdout.write(`Remote access:\n`);
616
+ process.stdout.write(` # Easiest one command, powered by Cloudflare Tunnel (free, no account)\n`);
617
+ process.stdout.write(` npx ${packageJson.name} --tunnel\n\n`);
618
+ process.stdout.write(` # Or bring your own tunnel and pass the URL manually:\n`);
619
+ process.stdout.write(` npx cloudflared tunnel --protocol http2 --url http://localhost:${port}\n`);
620
+ process.stdout.write(` ngrok http ${port}\n`);
619
621
  process.stdout.write(` npx ${packageJson.name} --relay=https://<your-tunnel-url>\n\n`);
620
622
  process.stdout.write(` # Tailscale (P2P VPN, no tunnel needed)\n`);
621
- process.stdout.write(` Install Tailscale on both your computer and phone, then use your\n`);
622
- process.stdout.write(` Tailscale IP directly — no relay required:\n`);
623
623
  process.stdout.write(` npx ${packageJson.name} --host=<tailscale-ip>\n`);
624
624
  }
625
625
 
626
+ function consumeFlag(name) {
627
+ const idx = process.argv.indexOf(`--${name}`);
628
+ if (idx === -1) return false;
629
+ process.argv.splice(idx, 1);
630
+ return true;
631
+ }
632
+
633
+ function readNpmConfigFlag(name) {
634
+ const value = process.env[`npm_config_${name.replace(/-/g, '_')}`];
635
+ return value === '' || value === 'true';
636
+ }
637
+
626
638
  function parseNamedArg(name, errorHint) {
627
639
  const inlinePrefix = `--${name}=`;
628
640
  const inlineArg = process.argv.find((arg) => arg.startsWith(inlinePrefix));
@@ -664,15 +676,153 @@ function clearRelayConfig() {
664
676
  rmSync(relayConfigPath, { force: true });
665
677
  }
666
678
 
679
+ // ─── Tunnel management ──────────────────────────────────────────────────────────
680
+
681
+ function loadTunnelState() {
682
+ try {
683
+ return JSON.parse(readFileSync(tunnelStatePath, 'utf8'));
684
+ } catch {
685
+ return null;
686
+ }
687
+ }
688
+
689
+ function saveTunnelState(pid, url) {
690
+ mkdirSync(vibeletDir, { recursive: true });
691
+ writeFileSync(tunnelStatePath, JSON.stringify({ pid, url }, null, 2) + '\n', 'utf8');
692
+ }
693
+
694
+ function clearTunnelState() {
695
+ rmSync(tunnelStatePath, { force: true });
696
+ }
697
+
698
+ function stopTunnel() {
699
+ const state = loadTunnelState();
700
+ if (state?.pid && isProcessAlive(state.pid)) {
701
+ try { process.kill(state.pid, 'SIGTERM'); } catch { /* already dead */ }
702
+ }
703
+ clearTunnelState();
704
+ }
705
+
706
+ function getAliveTunnel() {
707
+ const state = loadTunnelState();
708
+ if (state?.pid && state?.url && isProcessAlive(state.pid)) {
709
+ return state;
710
+ }
711
+ return null;
712
+ }
713
+
714
+ function resolveCloudflaredBin() {
715
+ // 1. Check if cloudflared is in PATH
716
+ const which = spawnSync('which', ['cloudflared'], { encoding: 'utf8' });
717
+ if (which.status === 0 && which.stdout.trim()) {
718
+ return which.stdout.trim();
719
+ }
720
+
721
+ // 2. Ensure npx has it installed (this downloads if needed, but is fast if cached)
722
+ const result = spawnSync('npx', ['--yes', '--package=cloudflared', 'node', '-e',
723
+ "console.log(require('path').join(require('path').dirname(require.resolve('cloudflared')), 'bin', 'cloudflared'))"],
724
+ { encoding: 'utf8', timeout: 30_000 });
725
+ if (result.status === 0 && result.stdout.trim()) {
726
+ const binPath = result.stdout.trim();
727
+ if (existsSync(binPath)) return binPath;
728
+ }
729
+
730
+ return null;
731
+ }
732
+
733
+ function startTunnel() {
734
+ return new Promise((resolve, reject) => {
735
+ const logPath = join(logDir, 'tunnel.stderr.log');
736
+ mkdirSync(logDir, { recursive: true });
737
+
738
+ const cfBin = resolveCloudflaredBin();
739
+ if (!cfBin) {
740
+ reject(new Error('cloudflared not found. Install it with: brew install cloudflared'));
741
+ return;
742
+ }
743
+
744
+ // Strategy: start cloudflared with output to log files (so it survives detach),
745
+ // then tail the log to capture the URL.
746
+ // Truncate log so we don't match a stale URL from a previous run.
747
+ writeFileSync(logPath, '', 'utf8');
748
+ const logFd = openSync(logPath, 'a');
749
+ const child = spawn(cfBin, ['tunnel', '--protocol', 'http2', '--url', `http://localhost:${port}`], {
750
+ detached: true,
751
+ stdio: ['ignore', logFd, logFd],
752
+ });
753
+ child.unref();
754
+
755
+ const pid = child.pid;
756
+ let url = null;
757
+
758
+ const timeout = setTimeout(() => {
759
+ if (!url) {
760
+ try { process.kill(pid, 'SIGTERM'); } catch { /* */ }
761
+ reject(new Error('Timed out waiting for tunnel URL (30s). Check network/proxy settings.'));
762
+ }
763
+ }, 30_000);
764
+
765
+ // Poll the log file for the tunnel URL
766
+ const poll = setInterval(() => {
767
+ try {
768
+ const content = readFileSync(logPath, 'utf8');
769
+ const match = content.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
770
+ if (match) {
771
+ url = match[0];
772
+ clearInterval(poll);
773
+ clearTimeout(timeout);
774
+ saveTunnelState(pid, url);
775
+ resolve({ pid, url });
776
+ return;
777
+ }
778
+ // Check if process died before producing URL
779
+ if (!isProcessAlive(pid)) {
780
+ clearInterval(poll);
781
+ clearTimeout(timeout);
782
+ reject(new Error('cloudflared exited before producing a tunnel URL. Check ~/.vibelet/logs/tunnel.stderr.log'));
783
+ }
784
+ } catch { /* file not ready yet */ }
785
+ }, 300);
786
+
787
+ child.on('error', (err) => {
788
+ clearInterval(poll);
789
+ clearTimeout(timeout);
790
+ reject(new Error(`Failed to start cloudflared: ${err.message}\nInstall it with: brew install cloudflared`));
791
+ });
792
+ });
793
+ }
794
+
667
795
  async function main() {
668
796
  // Update check: read cached result (sync, instant) and spawn background fetch.
669
797
  checkForUpdateFromCache();
670
798
  fetchLatestVersionInBackground();
671
799
 
800
+ const tunnelFlag = consumeFlag('tunnel') || readNpmConfigFlag('tunnel');
801
+ const forceFlag = consumeFlag('force') || readNpmConfigFlag('force');
672
802
  const relayArg = parseRelayArg();
673
803
  const hostArg = parseNamedArg('host', '100.x.x.x');
674
804
  const fallbackHostsArg = parseNamedArg('fallback-hosts', '100.x.x.x,192.168.1.x');
675
- const hasExplicitConfigOverrides = relayArg !== null || Boolean(hostArg) || Boolean(fallbackHostsArg);
805
+ const hasExplicitConfigOverrides = relayArg !== null || Boolean(hostArg) || Boolean(fallbackHostsArg) || tunnelFlag;
806
+
807
+ // --tunnel: auto-start or reuse a Cloudflare Tunnel
808
+ if (tunnelFlag) {
809
+ const existing = forceFlag ? null : getAliveTunnel();
810
+ if (existing) {
811
+ process.stdout.write(`Reusing tunnel: ${existing.url} (pid ${existing.pid})\n`);
812
+ saveRelayConfig(existing.url);
813
+ } else {
814
+ if (forceFlag) stopTunnel();
815
+ process.stdout.write('Starting Cloudflare Tunnel...\n');
816
+ try {
817
+ const tunnel = await startTunnel();
818
+ process.stdout.write(`Tunnel ready: ${tunnel.url}\n`);
819
+ saveRelayConfig(tunnel.url);
820
+ } catch (err) {
821
+ fail(err.message);
822
+ }
823
+ }
824
+ }
825
+
676
826
  // --relay "" clears saved relay; --relay <url> saves it; omitted uses saved value
677
827
  if (relayArg !== null) {
678
828
  if (relayArg) {
@@ -722,6 +872,12 @@ async function main() {
722
872
  if (stillAlive) {
723
873
  fail('Daemon did not stop in time.');
724
874
  }
875
+ // Also stop tunnel if running
876
+ const tunnelState = getAliveTunnel();
877
+ if (tunnelState) {
878
+ stopTunnel();
879
+ process.stdout.write('Tunnel stopped.\n');
880
+ }
725
881
  process.stdout.write('Daemon stopped.\n');
726
882
  return;
727
883
  }
@@ -729,6 +885,10 @@ async function main() {
729
885
  if (command === 'status') {
730
886
  process.stdout.write(`Service (${backend.name}): ${backend.statusLabel()}\n`);
731
887
  process.stdout.write(`Runtime: ${existsSync(runtimeDaemonEntryPath) ? runtimeDaemonEntryPath : 'not installed'}\n`);
888
+ const tunnelState = getAliveTunnel();
889
+ if (tunnelState) {
890
+ process.stdout.write(`Tunnel: ${tunnelState.url} (pid ${tunnelState.pid})\n`);
891
+ }
732
892
  const savedRelay = loadRelayConfig();
733
893
  if (savedRelay) {
734
894
  process.stdout.write(`Relay: ${savedRelay}\n`);
package/dist/index.cjs CHANGED
@@ -83,7 +83,7 @@ process.stdin.resume();
83
83
  `)}function ng(t){return Array.isArray(t)?t.map(e=>!e||typeof e!="object"?"":typeof e.text=="string"?e.text:typeof e.content=="string"?e.content:"").filter(Boolean).join(`
84
84
  `):""}function rg(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&typeof e=="object"&&e.type==="tool_reference")}function sg(t){if(typeof t=="string")return t;let e=ng(t);return e||(rg(t)?"":JSON.stringify(t))}function ig(t){return/requested permissions|haven't granted/i.test(t)}function dc(t){return t.replace(eg,"").replace(/\s+/g," ").trim()}function og(t){let e=t.toLowerCase();return e==="error"||e==="failed"||e==="unknown error"}function ag(t){return/\brate limit\b/i.test(t)||/\busage limit\b/i.test(t)||/\bquota\b/i.test(t)||/\btoo many requests\b/i.test(t)||/\bcredit balance\b/i.test(t)||/\bcredits? remaining\b/i.test(t)||/\bmax(?:imum)? usage\b/i.test(t)}function lg(t){return t.toLowerCase().startsWith("claude ")?t:`Claude usage limit reached. ${t}`}function fc(t){let e=dc(t.resultText??""),n=(t.stderrLines??[]).map(dc).filter(Boolean).slice(-3),r=[e,...n].find(i=>!!i&&ag(i));if(r)return lg(r);let s=[e,...n].find(i=>!!i&&!og(i));return s?t.exitCode!=null&&s!==e?`Claude exited with code ${t.exitCode}: ${s}`:s:t.exitCode!=null?`Claude exited with code ${t.exitCode}`:"Claude returned an error result."}function or(t){return t.startsWith(gc)}var on=class{proc=null;handler=null;sessionId="";buffer="";cwd="";approvalMode;sawFinalResult=!1;interrupted=!1;exitHandler=null;lastStderr=[];pendingPermissionDescriptions=new Map;emittedToolCallIds=new Set;replayPhase=!1;hookPort=null;hookSecret=null;hookFiles=null;buildClaudeEnv(){let e=v.buildSanitizedEnv();for(let n of Object.keys(e))n.startsWith("CMUX_")&&delete e[n];return e}async start(e,n,r){return this.cwd=e,this.approvalMode=r,n&&(this.sessionId=n),this.sessionId||(this.sessionId=`pending_${Date.now()}`),te.info({sessionId:this.sessionId,cwd:e},"session initialized"),_.emit("driver.spawn",{agent:"claude",sessionId:this.sessionId,cwd:e}),this.sessionId}configureHookBridge(e,n){this.hookPort=e,this.hookSecret=n}sendPrompt(e){let n=["-p",e,"--output-format","stream-json","--verbose","--include-partial-messages"];this.sessionId&&!this.sessionId.startsWith("pending_")&&n.push("--resume",this.sessionId),ir(this.hookFiles),this.hookFiles=null,this.approvalMode!=="acceptEdits"&&this.approvalMode!=="autoApprove"&&this.hookPort&&this.hookSecret&&(this.hookFiles=uc(this.hookPort,this.hookSecret),n.push("--settings",this.hookFiles.settingsPath)),this.approvalMode==="acceptEdits"&&n.push("--permission-mode","acceptEdits"),this.approvalMode==="autoApprove"&&n.push("--dangerously-skip-permissions");let r=this.sessionId.startsWith("pending_")?"(new)":`(resume ${this.sessionId.slice(0,8)})`;te.info({sessionId:this.sessionId,label:r,promptPreview:e.slice(0,50)},"running claude"),Ys(`New message: ${e.slice(0,60)}`),this.sawFinalResult=!1,this.interrupted=!1,this.lastStderr=[],this.pendingPermissionDescriptions.clear(),this.emittedToolCallIds.clear(),this.replayPhase=!0,this.proc=(0,pc.spawn)(v.claudePath,n,{cwd:this.cwd||void 0,stdio:["ignore","pipe","pipe"],env:this.buildClaudeEnv()}),this.proc.on("error",s=>{let i=s.message;s.code==="ENOENT"&&this.cwd&&!(0,hc.existsSync)(this.cwd)&&(i=`Working directory does not exist: ${this.cwd}`),te.error({sessionId:this.sessionId,error:i,cwd:this.cwd},"spawn error"),_.emit("driver.error",{agent:"claude",sessionId:this.sessionId,error:i}),this.handler?.({type:"error",sessionId:this.sessionId,message:i})}),this.buffer="",this.proc.stdout.on("data",s=>{this.buffer+=s.toString();let i=this.buffer.split(`
85
85
  `);this.buffer=i.pop();for(let o of i)if(o.trim())try{this.handleRaw(JSON.parse(o))}catch{te.warn({sessionId:this.sessionId,linePreview:o.slice(0,100)},"failed to parse stdout line")}}),this.proc.stderr.on("data",s=>{let i=s.toString().trim();i&&(te.debug({sessionId:this.sessionId,stderr:i},"stderr"),this.lastStderr.push(i),this.lastStderr.length>10&&this.lastStderr.shift())}),this.proc.on("exit",(s,i)=>{te.info({sessionId:this.sessionId,exitCode:s,signal:i},"process exited");let o=this.interrupted;this.proc=null,ir(this.hookFiles),this.hookFiles=null,this.interrupted=!1,o&&!this.sawFinalResult?this.handler?.({type:"session.interrupted",sessionId:this.sessionId}):s&&s!==0&&!this.sawFinalResult&&(te.error({sessionId:this.sessionId,exitCode:s,lastStderr:this.lastStderr.slice(-3)},"abnormal exit"),this.handler?.({type:"error",sessionId:this.sessionId,message:fc({stderrLines:this.lastStderr,exitCode:s})})),this.exitHandler?.(s)})}respondApproval(e,n){if(te.info({sessionId:this.sessionId,requestId:e,approved:n},"approval response"),!this.proc?.stdin?.writable)return te.error({sessionId:this.sessionId},"cannot send approval: stdin not writable"),!1;let r=JSON.stringify({type:"control_response",request_id:e,permission_granted:n});return this.proc.stdin.write(r+`
86
- `),!0}setApprovalMode(e){this.approvalMode=e,te.info({sessionId:this.sessionId,approvalMode:e},"approval mode updated")}interrupt(){this.proc&&!this.proc.killed&&(this.interrupted=!0,this.proc.kill("SIGTERM"),te.info({sessionId:this.sessionId},"interrupted"))}stop(){if(!this.proc)return;this.proc.kill("SIGTERM");let e=this.proc;setTimeout(()=>{e.killed||e.kill("SIGKILL")},5e3).unref(),this.proc=null,ir(this.hookFiles),this.hookFiles=null}onMessage(e){this.handler=e}onExit(e){this.exitHandler=e}handleRaw(e){let n=e.type;if(n==="system"&&e.subtype==="init"){let r=e.session_id??"";r&&r!==this.sessionId&&(te.info({oldSessionId:this.sessionId,newSessionId:r},"session ID resolved"),_.emit("driver.init",{agent:"claude",sessionId:r}),this.sessionId=r);return}if(this.handler)switch(n){case"assistant":{if(this.replayPhase)break;let r=e.message?.content;if(!Array.isArray(r))break;for(let s of r)s.type==="tool_use"&&!this.emittedToolCallIds.has(s.id)&&(this.emittedToolCallIds.add(s.id),this.handler({type:"tool.call",sessionId:this.sessionId,toolName:s.name,input:s.input??{},toolCallId:s.id}));break}case"user":{if(this.replayPhase)break;let r=e.message?.content;if(!Array.isArray(r))break;for(let s of r)if(s.type==="tool_result"){let i=sg(s.content);if(!i)continue;if(s.is_error===!0&&typeof s.tool_use_id=="string"&&ig(i)){this.pendingPermissionDescriptions.set(s.tool_use_id,i);continue}this.handler({type:"tool.result",sessionId:this.sessionId,toolCallId:s.tool_use_id,output:i})}break}case"control_request":{this.replayPhase=!1;let r=e.request;r?.subtype==="can_use_tool"&&(_.emit("approval.request",{agent:"claude",sessionId:this.sessionId,toolName:r.tool_name}),this.handler({type:"approval.request",sessionId:this.sessionId,requestId:e.request_id,toolName:r.tool_name??"unknown",input:r.input??{},description:r.description??r.title??""}));break}case"stream_event":{this.replayPhase=!1;let r=e.event;r?.type==="content_block_delta"&&r?.delta?.type==="text_delta"&&r?.delta?.text&&this.handler({type:"text.delta",sessionId:this.sessionId,content:r.delta.text});break}case"result":{this.replayPhase=!1,this.sawFinalResult=!0;let r=typeof e.result=="string"?e.result:"";if(e.is_error){Ys("Claude failed."),this.handler({type:"error",sessionId:this.sessionId,message:fc({resultText:r,stderrLines:this.lastStderr})});break}let s=Array.isArray(e.permission_denials)?e.permission_denials:[];if(s.length>0){let a=s[0]??{},l=typeof a.tool_use_id=="string"?a.tool_use_id:`missing_${Date.now()}`,c=typeof a.tool_name=="string"?a.tool_name:"unknown",u=a.tool_input&&typeof a.tool_input=="object"?a.tool_input:{},d=this.pendingPermissionDescriptions.get(l)??`Claude requested permissions to use ${c}.`;_.emit("approval.request",{agent:"claude",sessionId:this.sessionId,toolName:c}),this.handler({type:"approval.request",sessionId:this.sessionId,requestId:`${gc}${l}`,toolName:c,input:u,description:d})}this.pendingPermissionDescriptions.clear();let i=e.total_cost_usd,o=e.usage?{inputTokens:e.usage.input_tokens,outputTokens:e.usage.output_tokens}:void 0;Ys("Claude finished. Run `claude --continue` to continue on desktop."),_.emit("session.done",{agent:"claude",sessionId:this.sessionId,cost:i,usage:o}),this.handler({type:"session.done",sessionId:this.sessionId,cost:i,usage:o});break}}}};var vc=require("child_process"),_c=require("fs");var Js=class{counters=new Map;timers=new Map;gauges=new Map;startTime=Date.now();logInterval=null;increment(e,n={}){this.counters.has(e)||this.counters.set(e,[]);let r=this.counters.get(e),s=JSON.stringify(n),i=r.find(o=>JSON.stringify(o.labels)===s);i?i.value++:r.push({value:1,labels:n})}gauge(e,n){this.gauges.set(e,n)}startTimer(e){let n=performance.now();return()=>{let r=Math.round(performance.now()-n),s=this.timers.get(e)??{count:0,totalMs:0,minMs:1/0,maxMs:0,lastMs:0};return s.count++,s.totalMs+=r,s.minMs=Math.min(s.minMs,r),s.maxMs=Math.max(s.maxMs,r),s.lastMs=r,this.timers.set(e,s),r}}snapshot(){let e={};for(let[s,i]of this.counters)e[s]=i.map(o=>({...o}));let n={};for(let[s,i]of this.timers)n[s]={...i,minMs:i.minMs===1/0?0:i.minMs};let r={};for(let[s,i]of this.gauges)r[s]=i;return{uptimeMs:Date.now()-this.startTime,counters:e,timers:n,gauges:r}}startPeriodicLog(e=6e4){this.logInterval||(this.logInterval=setInterval(()=>{let n=this.snapshot();P.info({metrics:n},"periodic metrics snapshot")},e),this.logInterval.unref())}stopPeriodicLog(){this.logInterval&&(clearInterval(this.logInterval),this.logInterval=null)}},w=new Js;var mc=require("node:fs"),Ks=require("node:path"),cg="@vibelet/cli";function ug(t){try{let e=JSON.parse((0,mc.readFileSync)(t,"utf8"));if(e.name===cg&&typeof e.version=="string"&&e.version.length>0)return e.version}catch{}return null}function dg(){return"0.1.22"}var yt=dg();var x=P.child({module:"codex"});function fg(t){switch(t.kind){case"request-user-input-approval":return{provider:"codex",kind:t.kind,rpcId:t.rpcId,questionId:t.questionId,approveLabel:t.approveLabel,denyLabel:t.denyLabel};default:return{provider:"codex",kind:t.kind,rpcId:t.rpcId}}}function pg(t){let e=t.approvalContext;if(!e||e.provider!=="codex")return null;switch(e.kind){case"command-execution":return{kind:e.kind,responseKind:"v2",rpcId:e.rpcId,toolName:t.toolName,input:t.input};case"file-change":return{kind:e.kind,responseKind:"v2",rpcId:e.rpcId,toolName:t.toolName,input:t.input};case"request-user-input-approval":return!e.questionId||!e.approveLabel||!e.denyLabel?null:{kind:e.kind,rpcId:e.rpcId,questionId:e.questionId,approveLabel:e.approveLabel,denyLabel:e.denyLabel,toolName:t.toolName,input:t.input};case"exec-command-legacy":return{kind:e.kind,rpcId:e.rpcId,toolName:t.toolName,input:t.input};case"apply-patch-legacy":return{kind:e.kind,rpcId:e.rpcId,toolName:t.toolName,input:t.input};default:return null}}function $(t){return!t||typeof t!="object"||Array.isArray(t)?null:t}function k(t){return typeof t=="string"&&t.trim().length>0?t.trim():null}var hg=new Set(["aborted","interrupted","cancelled","canceled"]);function gg(t){let e=k(t);return e?e.toLowerCase():null}function mg(t){let e=k(t);return e?e.replace(/[^a-z0-9]/gi,"").toLowerCase():null}function Xs(t){return t?k(t.itemId)??k(t.id)??k(t.callId)??k(t.call_id):null}function Zs(t,e){let n={};for(let[r,s]of Object.entries(t))e.includes(r)||(n[r]=s);return n}function bc(t){return/\bapprove\b|\ballow\b|\baccept\b|\byes\b|\bcontinue\b|\bproceed\b|\brun\b|\bapply\b/i.test(t)}function wc(t){return/\bdeny\b|\breject\b|\bdecline\b|\bno\b|\bcancel\b|\babort\b|\bstop\b/i.test(t)}function yg(t){if(!Array.isArray(t))return null;for(let e of t){let n=$(e);if(!n)continue;let r=k(n.id),s=Array.isArray(n.options)?n.options.map(l=>$(l)).filter(l=>!!l):[];if(!r||s.length===0)continue;let i=s.map(l=>k(l.label)).filter(l=>!!l);if(i.length<2)continue;let o=i.find(l=>bc(l))??i[0],a=i.find(l=>wc(l))??i[i.length-1];if(!(!o||!a||o===a))return{questionId:r,approveLabel:o,denyLabel:a}}return null}function Sg(t){if(!Array.isArray(t)||t.length===0)return!1;let e=t.map(s=>$(s)).filter(s=>!!s).flatMap(s=>(Array.isArray(s.options)?s.options:[]).map(o=>$(o)).filter(o=>!!o).map(o=>k(o.label)).filter(o=>!!o)),n=e.some(s=>bc(s)),r=e.some(s=>wc(s));return n&&r}function yc(t){let e=mg(t.type??t.itemType);if(e==="commandexecution")return{toolName:"Bash",input:Zs(t,["id","itemId","type","itemType","stdout","stderr","exitCode","exit_code","status","success","error"])};if(e==="filechange")return{toolName:"Patch",input:Zs(t,["id","itemId","type","itemType","stdout","stderr","exitCode","exit_code","status","success","error"])};if(e==="mcptoolcall"){let n=k(t.server),r=k(t.tool)??k(t.name);return r?{toolName:n?`mcp__${n}__${r}`:r,input:$(t.arguments)??$(t.input)??{}}:null}return null}function vg(t){if(!Array.isArray(t))return"";for(let e of t){let n=$(e);if(!n)continue;let r=k(n.question)??k(n.header);if(r)return r}return""}function Sc(t,e){return t==="autoApprove"?{approvalPolicy:"never",sandbox:"danger-full-access",sandboxPolicy:{type:"dangerFullAccess"}}:{approvalPolicy:"on-request",sandbox:"workspace-write",sandboxPolicy:{type:"workspaceWrite",writableRoots:[e],readOnlyAccess:{type:"fullAccess"},networkAccess:!0,excludeTmpdirEnvVar:!1,excludeSlashTmp:!1}}}var ar=class{proc=null;handler=null;exitHandler=null;buffer="";rpcId=0;pending=new Map;threadId="";lastStderr=[];approvalRequests=new Map;toolContextByCallId=new Map;turnStartInFlight=!1;interruptRequestedDuringTurnStart=!1;approvalMode;cwd="";async start(e,n,r){this.approvalMode=r,this.cwd=e,this.approvalRequests.clear(),this.toolContextByCallId.clear(),this.turnStartInFlight=!1,this.interruptRequestedDuringTurnStart=!1;let s=v.codexPath,i,o=this.buildSpawnArgs();if(v.isTransientPath(s)){let c=v.execViaLoginShell("codex",o);i=c.command,o=c.args,x.info({spawnCmd:i,argsPreview:o.slice(0,2)},"spawning via login shell")}else i=s,x.info({spawnCmd:i,args:o},"spawning");_.emit("driver.spawn",{agent:"codex",cwd:e,resumeSessionId:n}),this.lastStderr=[],this.proc=(0,vc.spawn)(i,o,{cwd:e||void 0,stdio:["pipe","pipe","pipe"],env:v.buildSanitizedEnv()}),this.proc.on("error",c=>{let u=c.message;c.code==="ENOENT"&&e&&!(0,_c.existsSync)(e)&&(u=`Working directory does not exist: ${e}`),x.error({error:u,cwd:e},"spawn error"),_.emit("driver.error",{agent:"codex",error:u})}),this.proc.stdout.on("data",c=>{this.buffer+=c.toString();let u=this.buffer.split(`
86
+ `),!0}setApprovalMode(e){this.approvalMode=e,te.info({sessionId:this.sessionId,approvalMode:e},"approval mode updated")}interrupt(){this.proc&&!this.proc.killed&&(this.interrupted=!0,this.proc.kill("SIGTERM"),te.info({sessionId:this.sessionId},"interrupted"))}stop(){if(!this.proc)return;this.proc.kill("SIGTERM");let e=this.proc;setTimeout(()=>{e.killed||e.kill("SIGKILL")},5e3).unref(),this.proc=null,ir(this.hookFiles),this.hookFiles=null}onMessage(e){this.handler=e}onExit(e){this.exitHandler=e}handleRaw(e){let n=e.type;if(n==="system"&&e.subtype==="init"){let r=e.session_id??"";r&&r!==this.sessionId&&(te.info({oldSessionId:this.sessionId,newSessionId:r},"session ID resolved"),_.emit("driver.init",{agent:"claude",sessionId:r}),this.sessionId=r);return}if(this.handler)switch(n){case"assistant":{if(this.replayPhase)break;let r=e.message?.content;if(!Array.isArray(r))break;for(let s of r)s.type==="tool_use"&&!this.emittedToolCallIds.has(s.id)&&(this.emittedToolCallIds.add(s.id),this.handler({type:"tool.call",sessionId:this.sessionId,toolName:s.name,input:s.input??{},toolCallId:s.id}));break}case"user":{if(this.replayPhase)break;let r=e.message?.content;if(!Array.isArray(r))break;for(let s of r)if(s.type==="tool_result"){let i=sg(s.content);if(!i)continue;if(s.is_error===!0&&typeof s.tool_use_id=="string"&&ig(i)){this.pendingPermissionDescriptions.set(s.tool_use_id,i);continue}this.handler({type:"tool.result",sessionId:this.sessionId,toolCallId:s.tool_use_id,output:i})}break}case"control_request":{this.replayPhase=!1;let r=e.request;r?.subtype==="can_use_tool"&&(_.emit("approval.request",{agent:"claude",sessionId:this.sessionId,toolName:r.tool_name}),this.handler({type:"approval.request",sessionId:this.sessionId,requestId:e.request_id,toolName:r.tool_name??"unknown",input:r.input??{},description:r.description??r.title??""}));break}case"stream_event":{this.replayPhase=!1;let r=e.event;r?.type==="content_block_delta"&&r?.delta?.type==="text_delta"&&r?.delta?.text&&this.handler({type:"text.delta",sessionId:this.sessionId,content:r.delta.text});break}case"result":{this.replayPhase=!1,this.sawFinalResult=!0;let r=typeof e.result=="string"?e.result:"";if(e.is_error){Ys("Claude failed."),this.handler({type:"error",sessionId:this.sessionId,message:fc({resultText:r,stderrLines:this.lastStderr})});break}let s=Array.isArray(e.permission_denials)?e.permission_denials:[];if(s.length>0){let a=s[0]??{},l=typeof a.tool_use_id=="string"?a.tool_use_id:`missing_${Date.now()}`,c=typeof a.tool_name=="string"?a.tool_name:"unknown",u=a.tool_input&&typeof a.tool_input=="object"?a.tool_input:{},d=this.pendingPermissionDescriptions.get(l)??`Claude requested permissions to use ${c}.`;_.emit("approval.request",{agent:"claude",sessionId:this.sessionId,toolName:c}),this.handler({type:"approval.request",sessionId:this.sessionId,requestId:`${gc}${l}`,toolName:c,input:u,description:d})}this.pendingPermissionDescriptions.clear();let i=e.total_cost_usd,o=e.usage?{inputTokens:e.usage.input_tokens,outputTokens:e.usage.output_tokens}:void 0;Ys("Claude finished. Run `claude --continue` to continue on desktop."),_.emit("session.done",{agent:"claude",sessionId:this.sessionId,cost:i,usage:o}),this.handler({type:"session.done",sessionId:this.sessionId,cost:i,usage:o});break}}}};var vc=require("child_process"),_c=require("fs");var Js=class{counters=new Map;timers=new Map;gauges=new Map;startTime=Date.now();logInterval=null;increment(e,n={}){this.counters.has(e)||this.counters.set(e,[]);let r=this.counters.get(e),s=JSON.stringify(n),i=r.find(o=>JSON.stringify(o.labels)===s);i?i.value++:r.push({value:1,labels:n})}gauge(e,n){this.gauges.set(e,n)}startTimer(e){let n=performance.now();return()=>{let r=Math.round(performance.now()-n),s=this.timers.get(e)??{count:0,totalMs:0,minMs:1/0,maxMs:0,lastMs:0};return s.count++,s.totalMs+=r,s.minMs=Math.min(s.minMs,r),s.maxMs=Math.max(s.maxMs,r),s.lastMs=r,this.timers.set(e,s),r}}snapshot(){let e={};for(let[s,i]of this.counters)e[s]=i.map(o=>({...o}));let n={};for(let[s,i]of this.timers)n[s]={...i,minMs:i.minMs===1/0?0:i.minMs};let r={};for(let[s,i]of this.gauges)r[s]=i;return{uptimeMs:Date.now()-this.startTime,counters:e,timers:n,gauges:r}}startPeriodicLog(e=6e4){this.logInterval||(this.logInterval=setInterval(()=>{let n=this.snapshot();P.info({metrics:n},"periodic metrics snapshot")},e),this.logInterval.unref())}stopPeriodicLog(){this.logInterval&&(clearInterval(this.logInterval),this.logInterval=null)}},w=new Js;var mc=require("node:fs"),Ks=require("node:path"),cg="@vibelet/cli";function ug(t){try{let e=JSON.parse((0,mc.readFileSync)(t,"utf8"));if(e.name===cg&&typeof e.version=="string"&&e.version.length>0)return e.version}catch{}return null}function dg(){return"0.1.23"}var yt=dg();var x=P.child({module:"codex"});function fg(t){switch(t.kind){case"request-user-input-approval":return{provider:"codex",kind:t.kind,rpcId:t.rpcId,questionId:t.questionId,approveLabel:t.approveLabel,denyLabel:t.denyLabel};default:return{provider:"codex",kind:t.kind,rpcId:t.rpcId}}}function pg(t){let e=t.approvalContext;if(!e||e.provider!=="codex")return null;switch(e.kind){case"command-execution":return{kind:e.kind,responseKind:"v2",rpcId:e.rpcId,toolName:t.toolName,input:t.input};case"file-change":return{kind:e.kind,responseKind:"v2",rpcId:e.rpcId,toolName:t.toolName,input:t.input};case"request-user-input-approval":return!e.questionId||!e.approveLabel||!e.denyLabel?null:{kind:e.kind,rpcId:e.rpcId,questionId:e.questionId,approveLabel:e.approveLabel,denyLabel:e.denyLabel,toolName:t.toolName,input:t.input};case"exec-command-legacy":return{kind:e.kind,rpcId:e.rpcId,toolName:t.toolName,input:t.input};case"apply-patch-legacy":return{kind:e.kind,rpcId:e.rpcId,toolName:t.toolName,input:t.input};default:return null}}function $(t){return!t||typeof t!="object"||Array.isArray(t)?null:t}function k(t){return typeof t=="string"&&t.trim().length>0?t.trim():null}var hg=new Set(["aborted","interrupted","cancelled","canceled"]);function gg(t){let e=k(t);return e?e.toLowerCase():null}function mg(t){let e=k(t);return e?e.replace(/[^a-z0-9]/gi,"").toLowerCase():null}function Xs(t){return t?k(t.itemId)??k(t.id)??k(t.callId)??k(t.call_id):null}function Zs(t,e){let n={};for(let[r,s]of Object.entries(t))e.includes(r)||(n[r]=s);return n}function bc(t){return/\bapprove\b|\ballow\b|\baccept\b|\byes\b|\bcontinue\b|\bproceed\b|\brun\b|\bapply\b/i.test(t)}function wc(t){return/\bdeny\b|\breject\b|\bdecline\b|\bno\b|\bcancel\b|\babort\b|\bstop\b/i.test(t)}function yg(t){if(!Array.isArray(t))return null;for(let e of t){let n=$(e);if(!n)continue;let r=k(n.id),s=Array.isArray(n.options)?n.options.map(l=>$(l)).filter(l=>!!l):[];if(!r||s.length===0)continue;let i=s.map(l=>k(l.label)).filter(l=>!!l);if(i.length<2)continue;let o=i.find(l=>bc(l))??i[0],a=i.find(l=>wc(l))??i[i.length-1];if(!(!o||!a||o===a))return{questionId:r,approveLabel:o,denyLabel:a}}return null}function Sg(t){if(!Array.isArray(t)||t.length===0)return!1;let e=t.map(s=>$(s)).filter(s=>!!s).flatMap(s=>(Array.isArray(s.options)?s.options:[]).map(o=>$(o)).filter(o=>!!o).map(o=>k(o.label)).filter(o=>!!o)),n=e.some(s=>bc(s)),r=e.some(s=>wc(s));return n&&r}function yc(t){let e=mg(t.type??t.itemType);if(e==="commandexecution")return{toolName:"Bash",input:Zs(t,["id","itemId","type","itemType","stdout","stderr","exitCode","exit_code","status","success","error"])};if(e==="filechange")return{toolName:"Patch",input:Zs(t,["id","itemId","type","itemType","stdout","stderr","exitCode","exit_code","status","success","error"])};if(e==="mcptoolcall"){let n=k(t.server),r=k(t.tool)??k(t.name);return r?{toolName:n?`mcp__${n}__${r}`:r,input:$(t.arguments)??$(t.input)??{}}:null}return null}function vg(t){if(!Array.isArray(t))return"";for(let e of t){let n=$(e);if(!n)continue;let r=k(n.question)??k(n.header);if(r)return r}return""}function Sc(t,e){return t==="autoApprove"?{approvalPolicy:"never",sandbox:"danger-full-access",sandboxPolicy:{type:"dangerFullAccess"}}:{approvalPolicy:"on-request",sandbox:"workspace-write",sandboxPolicy:{type:"workspaceWrite",writableRoots:[e],readOnlyAccess:{type:"fullAccess"},networkAccess:!0,excludeTmpdirEnvVar:!1,excludeSlashTmp:!1}}}var ar=class{proc=null;handler=null;exitHandler=null;buffer="";rpcId=0;pending=new Map;threadId="";lastStderr=[];approvalRequests=new Map;toolContextByCallId=new Map;turnStartInFlight=!1;interruptRequestedDuringTurnStart=!1;approvalMode;cwd="";async start(e,n,r){this.approvalMode=r,this.cwd=e,this.approvalRequests.clear(),this.toolContextByCallId.clear(),this.turnStartInFlight=!1,this.interruptRequestedDuringTurnStart=!1;let s=v.codexPath,i,o=this.buildSpawnArgs();if(v.isTransientPath(s)){let c=v.execViaLoginShell("codex",o);i=c.command,o=c.args,x.info({spawnCmd:i,argsPreview:o.slice(0,2)},"spawning via login shell")}else i=s,x.info({spawnCmd:i,args:o},"spawning");_.emit("driver.spawn",{agent:"codex",cwd:e,resumeSessionId:n}),this.lastStderr=[],this.proc=(0,vc.spawn)(i,o,{cwd:e||void 0,stdio:["pipe","pipe","pipe"],env:v.buildSanitizedEnv()}),this.proc.on("error",c=>{let u=c.message;c.code==="ENOENT"&&e&&!(0,_c.existsSync)(e)&&(u=`Working directory does not exist: ${e}`),x.error({error:u,cwd:e},"spawn error"),_.emit("driver.error",{agent:"codex",error:u})}),this.proc.stdout.on("data",c=>{this.buffer+=c.toString();let u=this.buffer.split(`
87
87
  `);this.buffer=u.pop();for(let d of u)if(d.trim())try{this.handleRaw(JSON.parse(d))}catch{x.warn({linePreview:d.slice(0,200)},"failed to parse stdout line")}}),this.proc.stderr.on("data",c=>{let u=c.toString().trim();u&&(x.debug({stderr:u},"stderr"),this.lastStderr.push(u),this.lastStderr.length>10&&this.lastStderr.shift())}),this.proc.on("exit",c=>{x.info({threadId:this.threadId,exitCode:c},"process exited"),c&&c!==0&&x.error({threadId:this.threadId,exitCode:c,lastStderr:this.lastStderr.slice(-3)},"abnormal exit"),this.proc=null,this.approvalRequests.clear(),this.toolContextByCallId.clear(),this.turnStartInFlight=!1,this.interruptRequestedDuringTurnStart=!1;for(let[,{reject:u}]of this.pending)u(new Error(`Codex process exited with code ${c}`));this.pending.clear(),this.exitHandler?.(c),c&&this.handler&&this.threadId&&this.handler({type:"error",sessionId:this.threadId,message:`Codex process exited with code ${c}`})});let a=w.startTimer("rpc.duration");await this.rpc("initialize",{clientInfo:{name:"@vibelet/cli",version:yt},capabilities:{experimentalApi:!0}}),a(),this.rpcNotify("initialized",{});let l=Sc(this.approvalMode,e);if(n){let c=await this.rpc("thread/resume",{threadId:n,approvalPolicy:l.approvalPolicy,sandbox:l.sandbox,persistExtendedHistory:!0});this.threadId=c?.thread?.id??n,x.info({threadId:this.threadId},"thread resumed"),_.emit("driver.init",{agent:"codex",sessionId:this.threadId})}else{let c=await this.rpc("thread/start",{cwd:e,approvalPolicy:l.approvalPolicy,sandbox:l.sandbox,experimentalRawEvents:!0,persistExtendedHistory:!0});this.threadId=c?.thread?.id??c?.threadId??"",x.info({threadId:this.threadId},"thread created"),_.emit("driver.init",{agent:"codex",sessionId:this.threadId})}return this.threadId}buildSpawnArgs(){return["app-server","--listen","stdio://"]}sendPrompt(e){let n=Sc(this.approvalMode,this.cwd||process.cwd());this.turnStartInFlight=!0,this.interruptRequestedDuringTurnStart=!1,x.info({threadId:this.threadId,promptPreview:e.slice(0,50)},"turn/start"),this.rpc("turn/start",{threadId:this.threadId,input:[{type:"text",text:e}],approvalPolicy:n.approvalPolicy,sandboxPolicy:n.sandboxPolicy}).then(r=>{x.debug({resultPreview:JSON.stringify(r).slice(0,200)},"turn/start result"),this.turnStartInFlight=!1,this.interruptRequestedDuringTurnStart&&(this.interruptRequestedDuringTurnStart=!1,this.requestTurnInterrupt())}).catch(r=>{this.turnStartInFlight=!1,this.interruptRequestedDuringTurnStart=!1,x.error({threadId:this.threadId,error:r.message},"sendPrompt error"),this.handler?.({type:"error",sessionId:this.threadId,message:r.message})})}respondApproval(e,n){if(!this.proc?.stdin?.writable)return!1;let r=this.approvalRequests.get(e),s=r?.rpcId??Number(e);if(!Number.isFinite(s))return!1;let i;if(!r)i=n?{decision:"approve"}:{decision:"deny",reason:"User denied from Vibelet"};else{switch(r.kind){case"command-execution":case"file-change":i={decision:n?"accept":"decline"};break;case"request-user-input-approval":i={answers:{[r.questionId]:{answers:[n?r.approveLabel:r.denyLabel]}}};break;case"exec-command-legacy":case"apply-patch-legacy":i={decision:n?"approved":"denied"};break;default:i={decision:n?"accept":"decline"};break}this.approvalRequests.delete(e)}return x.info({threadId:this.threadId,rpcId:s,approved:n},"approval response"),this.proc.stdin.write(JSON.stringify({jsonrpc:"2.0",id:s,result:i})+`
88
88
  `),!0}restorePendingApproval(e){let n=pg(e);n&&this.approvalRequests.set(e.requestId,n)}interrupt(){this.turnStartInFlight&&(this.interruptRequestedDuringTurnStart=!0),this.requestTurnInterrupt()}setApprovalMode(e){this.approvalMode=e,x.info({approvalMode:e},"approval mode updated (takes effect on subsequent turns)")}stop(){if(!this.proc)return;this.proc.kill("SIGTERM");let e=this.proc;setTimeout(()=>{e.killed||e.kill("SIGKILL")},5e3).unref(),this.proc=null,this.approvalRequests.clear(),this.toolContextByCallId.clear(),this.turnStartInFlight=!1,this.interruptRequestedDuringTurnStart=!1;for(let[,{reject:n}]of this.pending)n(new Error("Process stopped"));this.pending.clear()}onMessage(e){this.handler=e}onExit(e){this.exitHandler=e}rpc(e,n){let r=++this.rpcId,s=w.startTimer("rpc.duration");return new Promise((i,o)=>{if(this.pending.set(r,{resolve:a=>{s(),i(a)},reject:a=>{s(),o(a)}}),!this.proc?.stdin?.writable){this.pending.delete(r),s(),o(new Error("Process not available"));return}this.proc.stdin.write(JSON.stringify({jsonrpc:"2.0",method:e,id:r,params:n})+`
89
89
  `),setTimeout(()=>{this.pending.has(r)&&(this.pending.delete(r),x.error({method:e,rpcId:r,threadId:this.threadId},"RPC timeout"),s(),o(new Error(`RPC timeout: ${e}`)))},3e4).unref()})}requestTurnInterrupt(){this.rpc("turn/interrupt",{threadId:this.threadId}).catch(()=>{})}rpcNotify(e,n){this.proc?.stdin?.writable&&this.proc.stdin.write(JSON.stringify({jsonrpc:"2.0",method:e,params:n})+`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibelet",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "macOS CLI for installing and running the Vibelet daemon",
5
5
  "homepage": "https://vibelet.icu",
6
6
  "private": false,