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 +171 -11
- package/dist/index.cjs +1 -1
- package/package.json +1 -1
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} --
|
|
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
|
|
613
|
-
process.stdout.write(`
|
|
614
|
-
process.stdout.write(`
|
|
615
|
-
process.stdout.write(`
|
|
616
|
-
process.stdout.write(`
|
|
617
|
-
process.stdout.write(` ngrok http ${port}\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.
|
|
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})+`
|