tail-sim 0.1.1 → 0.1.3
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/tail-sim-bin +0 -0
- package/dist/tail-sim.js +8 -8
- package/package.json +1 -1
package/bin/tail-sim-bin
CHANGED
|
Binary file
|
package/dist/tail-sim.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import{execSync as
|
|
4
|
-
Checked: ${z}`)}function
|
|
5
|
-
`)){let V=parseInt(K,10);if(V!==
|
|
6
|
-
${Y}`:"Helper process failed to start";console.error(
|
|
7
|
-
==> ${
|
|
8
|
-
==> ${
|
|
9
|
-
[tail-sim] Shutting down...`);for(let[J,Z]of V){try{process.kill(Z.pid,"SIGTERM")}catch{}
|
|
3
|
+
import{execSync as M,spawn as h}from"child_process";import{existsSync as P,mkdirSync as zz,openSync as S,closeSync as v,readFileSync as E,unlinkSync as I,writeFileSync as Cz}from"fs";import{join as Gz,resolve as Jz}from"path";import{tmpdir as t}from"os";import{join as R}from"path";import{readdirSync as e}from"fs";var B=R(t(),"tail-sim"),_z=R(B,"server.json");function H(z){return R(B,`server-${z}.json`)}function _(){try{return e(B).filter((z)=>z.startsWith("server-")&&z.endsWith(".json")).map((z)=>R(B,z))}catch{return[]}}function m(){if(!P(B))zz(B,{recursive:!0})}function U(z){if(z)return j(H(z));for(let C of _()){let G=j(C);if(G)return G}return null}function j(z){try{if(!P(z))return null;let C=JSON.parse(E(z,"utf-8"));try{return process.kill(C.pid,0),C}catch{return I(z),null}}catch{return null}}function F(){let z=[];for(let C of _()){let G=j(C);if(G)z.push(G)}return z}function y(z){m(),Cz(H(z.device),JSON.stringify(z,null,2))}function k(z){if(z)try{I(H(z))}catch{}else for(let C of _())try{I(C)}catch{}}function Kz(){let z=Jz(import.meta.dir,"../bin/tail-sim-bin");if(P(z))return z;throw Error(`tail-sim-bin binary not found. Run 'bun run build:swift' first.
|
|
4
|
+
Checked: ${z}`)}function g(){try{let z=M("xcrun simctl list devices booted -j",{encoding:"utf-8"}),C=JSON.parse(z);for(let G of Object.values(C.devices))for(let K of G)if(K.state==="Booted")return K.udid}catch{}return null}function A(z){try{let C=M("xcrun simctl list devices -j",{encoding:"utf-8"}),G=JSON.parse(C);for(let K of Object.values(G.devices))for(let V of K)if(V.udid===z)return V.name}catch{}return null}function q(z){if(/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i.test(z))return z;try{let C=M("xcrun simctl list devices -j",{encoding:"utf-8"}),G=JSON.parse(C);for(let K of Object.values(G.devices))for(let V of K)if(V.name.toLowerCase()===z.toLowerCase())return V.udid}catch{}console.error(`Could not resolve device: ${z}`),process.exit(1)}function n(z){try{let C=M("xcrun simctl list devices -j",{encoding:"utf-8"}),G=JSON.parse(C);for(let K of Object.values(G.devices))for(let V of K)if(V.udid===z)return V.state==="Booted"}catch{}return!1}function T(z){try{return process.kill(z,0),!0}catch{return!1}}function D(z){try{process.kill(z,"SIGTERM")}catch{return}let C=Date.now()+500;while(Date.now()<C)try{process.kill(z,0),Bun.sleepSync(25)}catch{return}try{process.kill(z,"SIGKILL")}catch{}let G=Date.now()+500;while(Date.now()<G)try{process.kill(z,0),Bun.sleepSync(25)}catch{return}}function Qz(z){try{let C=M(`lsof -ti tcp:${z}`,{encoding:"utf-8",stdio:"pipe"}).trim();if(C){let G=process.pid;for(let K of C.split(`
|
|
5
|
+
`)){let V=parseInt(K,10);if(V!==G)try{process.kill(V,"SIGKILL")}catch{}}Bun.sleepSync(100)}}catch{}}function Vz(z){if(!n(z))try{M(`xcrun simctl boot ${z}`,{encoding:"utf-8",stdio:"pipe"})}catch(C){let G=(C.stderr??C.message??"").toLowerCase();if(!G.includes("booted")&&!G.includes("current state"))throw Error(`Failed to boot device ${z}: ${C.stderr||C.message}`)}try{M("open -ga Simulator",{encoding:"utf-8",stdio:"pipe"})}catch{}}async function p(z){let C=new Set(F().map((G)=>G.port));for(let G=z;G<z+100;G++){if(C.has(G))continue;try{return Bun.serve({port:G,fetch:()=>new Response("ok")}).stop(!0),G}catch{continue}}throw Error(`No available port found in range ${z}-${z+99}`)}async function Wz(z){Vz(z);try{M(`xcrun simctl bootstatus ${z} -b`,{encoding:"utf-8",stdio:"pipe",timeout:60000})}catch(C){if(!n(z))console.error(`Device ${z} failed to reach booted state: ${C.stderr||C.message}`),process.exit(1)}}async function u(z,C,G,K){let V=!1;for(let Q=0;Q<30;Q++){if(!K())break;try{if((await fetch(`${C}/health`)).ok){V=!0;break}}catch{}await new Promise((Y)=>setTimeout(Y,100))}if(V){let Q=Date.now()+8000;while(Date.now()<Q){if(await new Promise((Y)=>setTimeout(Y,200)),!K()){V=!1;break}try{if(E(G,"utf-8").includes("Capture started"))break}catch{}}}let W="";try{W=E(G,"utf-8").trim()}catch{}return{ready:V,log:W}}async function Yz(z){let{helperPath:C,udid:G,port:K,host:V,logFile:W}=z,Q=`http://${V}:${K}`;m();let Y=S(W,"w"),J=h(C,[G,"--port",String(K)],{detached:!0,stdio:["ignore",Y,Y]});J.unref(),v(Y);let Z=J.pid,X=!1;J.once("exit",()=>{X=!0});let{ready:$,log:L}=await u(Z,Q,W,()=>!X&&T(Z));return{ready:$,pid:Z,exited:X||!T(Z),log:L}}async function Zz(z){let{helperPath:C,udid:G,port:K,host:V,logFile:W}=z,Q=`http://${V}:${K}`;m();let Y=S(W,"w"),J=h(C,[G,"--port",String(K)],{detached:!1,stdio:["ignore",Y,Y]});v(Y);let Z=J.pid,X=!1;J.once("exit",()=>{X=!0});let{ready:$,log:L}=await u(Z,Q,W,()=>!X&&T(Z));return{ready:$,child:J,log:L}}async function c(z,C,G){await Wz(z);let K="127.0.0.1",V=Kz(),W=Gz(B,`server-${z}.log`),Q={helperPath:V,udid:z,port:C,host:K,logFile:W},Y="",J=2;for(let X=1;X<=J;X++){if(Qz(C),G.detach){let $=await Yz(Q);if($.ready){let L={pid:$.pid,port:C,device:z,url:`http://${K}:${C}`,streamUrl:`http://${K}:${C}/stream.mjpeg`,wsUrl:`ws://${K}:${C}/ws`};return y(L),{pid:$.pid}}D($.pid),Y=$.log}else{let $=await Zz(Q);if($.ready){let L={pid:$.child.pid,port:C,device:z,url:`http://${K}:${C}`,streamUrl:`http://${K}:${C}/stream.mjpeg`,wsUrl:`ws://${K}:${C}/ws`};return y(L),{pid:$.child.pid,child:$.child}}D($.child.pid),Y=$.log}if(X<J)await new Promise(($)=>setTimeout($,500))}let Z=Y?`Helper failed:
|
|
6
|
+
${Y}`:"Helper process failed to start";console.error(Z),process.exit(1)}async function $z(z,C,G){let K=z.length>0?z.map(q):(()=>{let J=g();if(!J)console.error("No device specified and no booted simulator found."),process.exit(1);return[J]})(),V=new Map,W=[],Q=C;for(let J of K){let Z=U(J);if(Z){if(!G){let b=A(J)??J;if(K.length>1)console.error(`
|
|
7
|
+
==> ${b} (${J}) <==`);console.error(` Already running on port ${Z.port}`),console.error(` Stream: ${Z.streamUrl}`),console.error(` WebSocket: ${Z.wsUrl}`)}W.push(Z);continue}Q=await p(Q);let{pid:X,child:$}=await c(J,Q,{detach:!1});if($)V.set(J,$);let L="127.0.0.1",O={pid:X,port:Q,device:J,url:`http://${L}:${Q}`,streamUrl:`http://${L}:${Q}/stream.mjpeg`,wsUrl:`ws://${L}:${Q}/ws`};if(W.push(O),!G){let b=A(J)??J;if(K.length>1)console.error(`
|
|
8
|
+
==> ${b} (${J}) <==`);console.error(` Stream: ${O.streamUrl}`),console.error(` WebSocket: ${O.wsUrl}`),console.error(` Port: ${Q}`)}Q++}if(W.length===1){let J=W[0];console.log(JSON.stringify({url:J.url,streamUrl:J.streamUrl,wsUrl:J.wsUrl,port:J.port,device:J.device}))}else console.log(JSON.stringify({devices:W.map((J)=>({url:J.url,streamUrl:J.streamUrl,wsUrl:J.wsUrl,port:J.port,device:J.device}))}));if(V.size===0)return;for(let[J,Z]of V)Z.on("exit",(X)=>{if(!G)console.error(`[${J}] Helper exited (code ${X})`);if(k(J),V.delete(J),V.size===0)process.exit(X??1)});let Y=()=>{if(!G)console.error(`
|
|
9
|
+
[tail-sim] Shutting down...`);for(let[J,Z]of V){try{process.kill(Z.pid,"SIGTERM")}catch{}k(J)}process.exit(0)};process.on("SIGINT",Y),process.on("SIGTERM",Y),await new Promise(()=>{})}async function Xz(z,C,G){let K=z.length>0?z.map(q):(()=>{let Q=g();if(!Q)console.error("No device specified and no booted simulator found."),process.exit(1);return[Q]})(),V=[],W=C;for(let Q of K){let Y=U(Q);if(Y){V.push(Y);continue}W=await p(W),await c(Q,W,{detach:!0});let J="127.0.0.1";V.push({pid:U(Q).pid,port:W,device:Q,url:`http://${J}:${W}`,streamUrl:`http://${J}:${W}/stream.mjpeg`,wsUrl:`ws://${J}:${W}/ws`}),W++}if(V.length===1){let Q=V[0];console.log(JSON.stringify({url:Q.url,streamUrl:Q.streamUrl,wsUrl:Q.wsUrl,port:Q.port,device:Q.device}))}else console.log(JSON.stringify({devices:V.map((Q)=>({url:Q.url,streamUrl:Q.streamUrl,wsUrl:Q.wsUrl,port:Q.port,device:Q.device}))}))}function Nz(z){if(z){let G=q(z),K=U(G);if(!K)console.log(JSON.stringify({running:!1,device:G}));else console.log(JSON.stringify({running:!0,url:K.url,streamUrl:K.streamUrl,wsUrl:K.wsUrl,port:K.port,device:K.device,pid:K.pid}));return}let C=F();if(C.length===0)console.log(JSON.stringify({running:!1}));else if(C.length===1){let G=C[0];console.log(JSON.stringify({running:!0,url:G.url,streamUrl:G.streamUrl,wsUrl:G.wsUrl,port:G.port,device:G.device,pid:G.pid}))}else console.log(JSON.stringify({running:!0,streams:C.map((G)=>({url:G.url,streamUrl:G.streamUrl,wsUrl:G.wsUrl,port:G.port,device:G.device,pid:G.pid}))}))}function Lz(z){if(z){let C=q(z),G=U(C);if(!G){console.log(JSON.stringify({disconnected:!0,device:C}));return}try{process.kill(G.pid,"SIGTERM")}catch{}k(C),console.log(JSON.stringify({disconnected:!0,device:G.device}))}else{let C=F();if(C.length===0){console.log(JSON.stringify({disconnected:!0,devices:[]}));return}let G=[];for(let K of C){try{process.kill(K.pid,"SIGTERM")}catch{}G.push(K.device)}k(),console.log(JSON.stringify({disconnected:!0,devices:G}))}}async function Bz(z){let C,G=[];for(let Q=0;Q<z.length;Q++)if(z[Q]==="--device"||z[Q]==="-d")C=z[++Q];else G.push(z[Q]);let K=U(C);if(!K)console.error("No tail-sim server running. Run `tail-sim` first."),process.exit(1);let V=G[0];if(!V)console.error("Usage: tail-sim gesture '<json>'"),console.error(`Example: tail-sim gesture '{"type":"begin","x":0.5,"y":0.5}'`),process.exit(1);let W;try{W=JSON.parse(V)}catch{console.error("Invalid JSON:",V),process.exit(1)}return new Promise((Q,Y)=>{let J=new WebSocket(K.wsUrl);J.binaryType="arraybuffer",J.onopen=()=>{let Z=new TextEncoder().encode(JSON.stringify(W)),X=new Uint8Array(1+Z.length);X[0]=3,X.set(Z,1),J.send(X),setTimeout(()=>{J.close(),Q()},50)},J.onerror=()=>{console.error("Failed to connect to tail-sim server at",K.wsUrl),Y(Error("WebSocket connection failed"))}})}async function Mz(z){let C,G=[];for(let W=0;W<z.length;W++)if(z[W]==="--device"||z[W]==="-d")C=z[++W];else G.push(z[W]);let K=U(C);if(!K)console.error("No tail-sim server running. Run `tail-sim` first."),process.exit(1);let V=G[0]??"home";return new Promise((W,Q)=>{let Y=new WebSocket(K.wsUrl);Y.binaryType="arraybuffer",Y.onopen=()=>{let J=new TextEncoder().encode(JSON.stringify({button:V})),Z=new Uint8Array(1+J.length);Z[0]=4,Z.set(J,1),Y.send(Z),setTimeout(()=>{Y.close(),W()},50)},Y.onerror=()=>{console.error("Failed to connect to tail-sim server at",K.wsUrl),Q(Error("WebSocket connection failed"))}})}function l(){console.log(`
|
|
10
10
|
tail-sim - Stream iOS Simulator to the browser
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
@@ -31,4 +31,4 @@ Examples:
|
|
|
31
31
|
tail-sim --kill Stop all streams
|
|
32
32
|
tail-sim gesture '{"type":"begin","x":0.5,"y":0.5}'
|
|
33
33
|
tail-sim button home -d <udid>
|
|
34
|
-
`)}var N=process.argv.slice(2);if(N[0]==="gesture")await
|
|
34
|
+
`)}var N=process.argv.slice(2);if(N[0]==="gesture")await Bz(N.slice(1)),process.exit(0);if(N[0]==="button")await Mz(N.slice(1)),process.exit(0);var w=3100,o=!1,x=!1,a=!1,r=!1,d=!1,f=[],s,i;for(let z=0;z<N.length;z++){let C=N[z];switch(C){case"--port":case"-p":w=parseInt(N[++z]??"3100",10);break;case"--detach":case"-d":o=!0;break;case"--quiet":case"-q":x=!0;break;case"--list":case"-l":if(a=!0,N[z+1]&&!N[z+1].startsWith("-"))s=N[++z];break;case"--kill":case"-k":if(r=!0,N[z+1]&&!N[z+1].startsWith("-"))i=N[++z];break;case"--help":case"-h":case"help":d=!0;break;default:if(!C.startsWith("-"))f.push(C);else console.error(`Unknown flag: ${C}`),l(),process.exit(1)}}if(d)l(),process.exit(0);if(a)Nz(s),process.exit(0);if(r)Lz(i),process.exit(0);if(o)await Xz(f,w,x);else await $z(f,w,x);
|