poke 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -37,7 +37,7 @@ tell application "System Events" to get value of property list item "CFBundleNam
37
37
  `;else n+=`${We(o)}: ${We(a)}\r
38
38
  `;!t["content-length"]&&!t["Content-Length"]&&(n+=`Content-Length: ${r.length}\r
39
39
  `),n+=`\r
40
- `;let s=Gi.encode(n);return yr(s,r)}async function zi(i,e,t){try{let{headerBytes:r,remainder:n}=await Ao(i),s=qi.decode(r),o=Co(s);o.body=await To(i,o.headers,n);let[a,l]=e.split(":"),h=l?parseInt(l,10):80,u=await ko(o,a,h);await i.write(u)}catch(r){t?.warn("proxy error",{error:String(r)});try{let n=Gi.encode("Bad Gateway"),s=Yi(502,"Bad Gateway",{},n);await i.write(s)}catch{}}finally{try{await i.close()}catch{}}}function ko(i,e,t){return new Promise((r,n)=>{let s={},o=new Set(["host","transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[l,h]of Object.entries(i.headers))o.has(l)||(s[l]=h);s.host=`${e}:${t}`,i.body&&i.body.length>0&&!s["content-length"]&&(s["content-length"]=String(i.body.length));let a=Vi.default.request({hostname:e,port:t,method:i.method,path:i.path,headers:s},l=>{let h=[],u=0;l.on("data",f=>{if(u+=f.length,u>vr){l.destroy(new Error("response body exceeds size limit"));return}h.push(f)}),l.on("end",()=>{let f=Buffer.concat(h),p={},w=new Set(["transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[g,E]of Object.entries(l.headers))E!==void 0&&!w.has(g)&&(p[g]=E);p["content-length"]=String(f.length);let m=Yi(l.statusCode??500,l.statusMessage??"Internal Server Error",p,new Uint8Array(f));r(m)}),l.on("error",n)});a.on("error",n),i.body&&i.body.length>0&&a.write(i.body),a.end()})}c();c();var de=class extends Error{constructor(e){super(e),this.name="PikoAuthError"}},ie=class extends Error{statusCode;constructor(e,t){super(e),this.name="PikoConnectionError",this.statusCode=t}};var Ki=100,Lo=15e3,_o=.3,Ro=new Set([400,401,403,404,405,410]);function Qi(i){if(i instanceof de)return!1;if(typeof i=="object"&&i!==null&&"statusCode"in i){let e=i.statusCode;if(typeof e=="number")return!Ro.has(e)}if(i instanceof Error){let e=i.message.toLowerCase();if(/\b401\b/.test(e)||/\b403\b/.test(e)||e.includes("unauthorized")||e.includes("forbidden"))return!1}return!0}function Xi(i){let e=Math.min(Ki*Math.pow(2,i),Lo),t=e*_o*(Math.random()*2-1);return Math.max(Ki,Math.round(e+t))}c();c();var Ja=S(Yn(),1),Za=S(Lr(),1),el=S(Nr(),1),is=S(vt(),1),tl=S(rs(),1);var ns=is.default;async function ss(i){let e=rl(i.upstreamUrl,i.endpointId);return new Promise((t,r)=>{if(i.signal?.aborted){r(new ie("aborted"));return}let n=new ns(e,{headers:{Authorization:`Bearer ${i.token}`}});n.binaryType="nodebuffer";let s=()=>{n.close(),r(new ie("aborted"))};i.signal?.addEventListener("abort",s,{once:!0}),n.on("open",()=>{i.signal?.removeEventListener("abort",s);let o=gr(n);t({transport:o,ws:n})}),n.on("error",o=>{i.signal?.removeEventListener("abort",s),r(new ie(o.message))}),n.on("unexpected-response",(o,a)=>{i.signal?.removeEventListener("abort",s),a.statusCode===401||a.statusCode===403?r(new de(`Authentication failed: HTTP ${a.statusCode}`)):r(new ie(`Unexpected HTTP ${a.statusCode} from upstream`,a.statusCode))})})}function rl(i,e){return`${i.replace(/\/$/,"").replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}/piko/v1/upstream/${e}`}var il={info(){},warn(){},error(){}},ze=class{opts;logger;abortController;session=null;running=!1;isConnected=!1;listeners=new Map;constructor(e){this.opts={...e,logger:e.logger??il},this.logger=this.opts.logger,this.abortController=new AbortController,e.signal&&e.signal.addEventListener("abort",()=>{this.abortController.abort()})}get connected(){return this.isConnected}on(e,t){let r=this.listeners.get(e);r||(r=new Set,this.listeners.set(e,r)),r.add(t)}off(e,t){this.listeners.get(e)?.delete(t)}async start(){this.running||(this.abortController.signal.aborted&&(this.abortController=new AbortController),this.running=!0,this.runLoop())}async stop(){this.running=!1,this.abortController.abort(),this.session&&(await this.session.close().catch(()=>{}),this.session=null),this.setConnected(!1)}async runLoop(){let e=0;for(;this.running&&!this.abortController.signal.aborted;)try{await this.connectAndServe(),e=0}catch(t){if(this.setConnected(!1),this.session=null,!this.running||this.abortController.signal.aborted)return;if(!Qi(t)){this.logger.error("Fatal error, not retrying"),this.emit("error",t),this.running=!1;return}e++;let r=Xi(e);this.logger.warn("Connection lost, reconnecting",{attempt:e,retryInMs:r}),this.emit("disconnected"),await this.sleep(r)}}async connectAndServe(){let{transport:e}=await ss({upstreamUrl:this.opts.upstreamUrl,endpointId:this.opts.endpointId,token:this.opts.token,signal:this.abortController.signal}),t=mr(e,{enableKeepAlive:!0,keepAliveInterval:3e4});this.session=t,this.setConnected(!0),this.emit("connected"),this.logger.info("Connected to upstream");try{for(;this.running&&!t.isClosed();){let r=await t.accept();zi(r,this.opts.localAddr,{warn:this.logger.warn.bind(this.logger)}).catch(n=>{this.logger.warn("Stream proxy error",{error:String(n)})})}}finally{t.isClosed()||await t.close().catch(()=>{})}}setConnected(e){this.isConnected=e}emit(e,...t){let r=this.listeners.get(e);if(r)for(let n of r)try{n(...t)}catch{}}sleep(e){return new Promise((t,r)=>{if(this.abortController.signal.aborted){t();return}let n=setTimeout(t,e);this.abortController.signal.addEventListener("abort",()=>{clearTimeout(n),t()},{once:!0})})}};function os(i){return new ze(i)}var bt=class{options;localUrl;listeners=new Map;pikoClient=null;syncTimer=null;connectionId=null;tunnelUrl=null;ge=!1;get info(){return!this.connectionId||!this.tunnelUrl?null:{connectionId:this.connectionId,tunnelUrl:this.tunnelUrl,localUrl:this.options.url,name:this.options.name}}get connected(){return this.ge}constructor(e){this.options=e;try{this.localUrl=new URL(e.url)}catch{throw new Error(`Invalid URL: ${e.url}`)}}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this}off(e,t){return this.listeners.get(e)?.delete(t),this}emit(e,...t){for(let r of this.listeners.get(e)??[])r(...t)}resolveToken(){let e=this.options.token??re()?.token;if(!e)throw new B("Not logged in. Run 'poke login'.");return e}fetchAuth({path:e,options:t}){return Me({path:e,options:t,token:this.resolveToken(),baseUrl:this.options.baseUrl})}async start(){let e={name:this.options.name,serverUrl:this.options.url,tunnel:!0};this.options.clientId&&(e.clientId=this.options.clientId),this.options.clientSecret&&(e.clientSecret=this.options.clientSecret);let t=await this.fetchAuth({path:"/mcp/connections/cli",options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}});if(!t.ok){let a=await t.text(),l=`HTTP ${t.status}`;try{l=JSON.parse(a).message??l}catch{}throw new Error(`Failed to create tunnel: ${l}`)}let r=await t.json();if(this.connectionId=r.id,this.tunnelUrl=r.serverUrl,!r.tunnel?.token||!r.tunnel?.upstreamUrl)throw new Error("Tunnel configuration not available.");this.pikoClient=os({endpointId:r.id,upstreamUrl:r.tunnel.upstreamUrl,token:r.tunnel.token,localAddr:this.localUrl.host,logger:{info:()=>{},warn:()=>{},error:()=>{}}}),this.pikoClient.on("error",a=>{let l=a instanceof Error?a:new Error(String(a));this.emit("error",l)}),this.pikoClient.on("disconnected",()=>{this.ge=!1,this.emit("disconnected")});let n=new Promise((a,l)=>{let h=setTimeout(()=>l(new Error("Connection timeout")),3e4);this.pikoClient.on("connected",()=>{clearTimeout(h),a()}),this.pikoClient.on("error",u=>{clearTimeout(h),l(u)})});await this.pikoClient.start(),await n,this.ge=!0,await this.activateTunnel();let s=this.options.syncIntervalMs??5*60*1e3;s>0&&(this.syncTimer=setInterval(()=>this.syncTools(),s));let o=this.info;return this.emit("connected",o),o}async stop(){if(this.syncTimer&&(clearInterval(this.syncTimer),this.syncTimer=null),(this.options.cleanupOnStop??!0)&&this.connectionId)try{await this.fetchAuth({path:`/mcp/connections/${this.connectionId}`,options:{method:"DELETE"}})}catch{}if(this.pikoClient){try{await this.pikoClient.stop()}catch{}this.pikoClient=null}this.ge=!1,this.connectionId=null,this.tunnelUrl=null}async createRecipe({name:e}={}){if(!this.connectionId)throw new Error("Tunnel is not started.");let t=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/create-recipe`,options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e??this.options.name})}});if(!t.ok)throw new Error(`Failed to create recipe (HTTP ${t.status}).`);return(await t.json()).link}async activateTunnel(){let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/activate-tunnel`,options:{method:"POST"}});if(e.ok){let t=await e.json();t.status==="oauth_required"&&t.authUrl&&this.emit("oauthRequired",{authUrl:t.authUrl})}else await this.syncTools()}async syncTools(){try{let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/sync-tools`,options:{method:"POST"}});if(e.ok){let t=await e.json();if(t.requiresOAuth&&t.oauthUrl)this.emit("oauthRequired",{authUrl:t.oauthUrl});else{let r=Array.isArray(t.tools)?t.tools.length:0;this.emit("toolsSynced",{toolCount:r})}}}catch{}}};var Ke=process.env.POKE_API??"https://poke.com/api/v1",_s=process.env.POKE_FRONTEND??"https://poke.com",fl=`
40
+ `;let s=Gi.encode(n);return yr(s,r)}async function zi(i,e,t){try{let{headerBytes:r,remainder:n}=await Ao(i),s=qi.decode(r),o=Co(s);o.body=await To(i,o.headers,n);let[a,l]=e.split(":"),h=l?parseInt(l,10):80,u=await ko(o,a,h);await i.write(u)}catch(r){t?.warn("proxy error",{error:String(r)});try{let n=Gi.encode("Bad Gateway"),s=Yi(502,"Bad Gateway",{},n);await i.write(s)}catch{}}finally{try{await i.close()}catch{}}}function ko(i,e,t){return new Promise((r,n)=>{let s={},o=new Set(["host","transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[l,h]of Object.entries(i.headers))o.has(l)||(s[l]=h);s.host=`${e}:${t}`,i.body&&i.body.length>0&&!s["content-length"]&&(s["content-length"]=String(i.body.length));let a=Vi.default.request({hostname:e,port:t,method:i.method,path:i.path,headers:s},l=>{let h=[],u=0;l.on("data",f=>{if(u+=f.length,u>vr){l.destroy(new Error("response body exceeds size limit"));return}h.push(f)}),l.on("end",()=>{let f=Buffer.concat(h),p={},w=new Set(["transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[g,E]of Object.entries(l.headers))E!==void 0&&!w.has(g)&&(p[g]=E);p["content-length"]=String(f.length);let m=Yi(l.statusCode??500,l.statusMessage??"Internal Server Error",p,new Uint8Array(f));r(m)}),l.on("error",n)});a.on("error",n),i.body&&i.body.length>0&&a.write(i.body),a.end()})}c();c();var de=class extends Error{constructor(e){super(e),this.name="PikoAuthError"}},ie=class extends Error{statusCode;constructor(e,t){super(e),this.name="PikoConnectionError",this.statusCode=t}};var Ki=100,Lo=15e3,_o=.3,Ro=new Set([400,401,403,404,405,410]);function Qi(i){if(i instanceof de)return!1;if(typeof i=="object"&&i!==null&&"statusCode"in i){let e=i.statusCode;if(typeof e=="number")return!Ro.has(e)}if(i instanceof Error){let e=i.message.toLowerCase();if(/\b401\b/.test(e)||/\b403\b/.test(e)||e.includes("unauthorized")||e.includes("forbidden"))return!1}return!0}function Xi(i){let e=Math.min(Ki*Math.pow(2,i),Lo),t=e*_o*(Math.random()*2-1);return Math.max(Ki,Math.round(e+t))}c();c();var Ja=S(Yn(),1),Za=S(Lr(),1),el=S(Nr(),1),is=S(vt(),1),tl=S(rs(),1);var ns=is.default;async function ss(i){let e=rl(i.upstreamUrl,i.endpointId);return new Promise((t,r)=>{if(i.signal?.aborted){r(new ie("aborted"));return}let n=new ns(e,{headers:{Authorization:`Bearer ${i.token}`}});n.binaryType="nodebuffer";let s=()=>{n.close(),r(new ie("aborted"))};i.signal?.addEventListener("abort",s,{once:!0}),n.on("open",()=>{i.signal?.removeEventListener("abort",s);let o=gr(n);t({transport:o,ws:n})}),n.on("error",o=>{i.signal?.removeEventListener("abort",s),r(new ie(o.message))}),n.on("unexpected-response",(o,a)=>{i.signal?.removeEventListener("abort",s),a.statusCode===401||a.statusCode===403?r(new de(`Authentication failed: HTTP ${a.statusCode}`)):r(new ie(`Unexpected HTTP ${a.statusCode} from upstream`,a.statusCode))})})}function rl(i,e){return`${i.replace(/\/$/,"").replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}/piko/v1/upstream/${e}`}var il={info(){},warn(){},error(){}},ze=class{opts;logger;abortController;session=null;running=!1;isConnected=!1;listeners=new Map;constructor(e){this.opts={...e,logger:e.logger??il},this.logger=this.opts.logger,this.abortController=new AbortController,e.signal&&e.signal.addEventListener("abort",()=>{this.abortController.abort()})}get connected(){return this.isConnected}on(e,t){let r=this.listeners.get(e);r||(r=new Set,this.listeners.set(e,r)),r.add(t)}off(e,t){this.listeners.get(e)?.delete(t)}async start(){this.running||(this.abortController.signal.aborted&&(this.abortController=new AbortController),this.running=!0,this.runLoop())}async stop(){this.running=!1,this.abortController.abort(),this.session&&(await this.session.close().catch(()=>{}),this.session=null),this.setConnected(!1)}async runLoop(){let e=0;for(;this.running&&!this.abortController.signal.aborted;)try{await this.connectAndServe(),e=0}catch(t){if(this.setConnected(!1),this.session=null,!this.running||this.abortController.signal.aborted)return;if(!Qi(t)){this.logger.error("Fatal error, not retrying"),this.emit("error",t),this.running=!1;return}e++;let r=Xi(e);this.logger.warn("Connection lost, reconnecting",{attempt:e,retryInMs:r}),this.emit("disconnected"),await this.sleep(r)}}async connectAndServe(){let{transport:e}=await ss({upstreamUrl:this.opts.upstreamUrl,endpointId:this.opts.endpointId,token:this.opts.token,signal:this.abortController.signal}),t=mr(e,{enableKeepAlive:!0,keepAliveInterval:3e4});this.session=t,this.setConnected(!0),this.emit("connected"),this.logger.info("Connected to upstream");try{for(;this.running&&!t.isClosed();){let r=await t.accept();zi(r,this.opts.localAddr,{warn:this.logger.warn.bind(this.logger)}).catch(n=>{this.logger.warn("Stream proxy error",{error:String(n)})})}}finally{t.isClosed()||await t.close().catch(()=>{})}}setConnected(e){this.isConnected=e}emit(e,...t){let r=this.listeners.get(e);if(r)for(let n of r)try{n(...t)}catch{}}sleep(e){return new Promise((t,r)=>{if(this.abortController.signal.aborted){t();return}let n=setTimeout(t,e);this.abortController.signal.addEventListener("abort",()=>{clearTimeout(n),t()},{once:!0})})}};function os(i){return new ze(i)}var bt=class{options;localUrl;listeners=new Map;pikoClient=null;syncTimer=null;connectionId=null;tunnelUrl=null;ge=!1;get info(){return!this.connectionId||!this.tunnelUrl?null:{connectionId:this.connectionId,tunnelUrl:this.tunnelUrl,localUrl:this.options.url,name:this.options.name}}get connected(){return this.ge}constructor(e){this.options=e;try{this.localUrl=new URL(e.url)}catch{throw new Error(`Invalid URL: ${e.url}`)}}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this}off(e,t){return this.listeners.get(e)?.delete(t),this}emit(e,...t){for(let r of this.listeners.get(e)??[])r(...t)}resolveToken(){let e=this.options.token??re()?.token;if(!e)throw new B("Not logged in. Run 'poke login'.");return e}fetchAuth({path:e,options:t}){return Me({path:e,options:t,token:this.resolveToken(),baseUrl:this.options.baseUrl})}async start(){let e={name:this.options.name,serverUrl:this.options.url,tunnel:!0};this.options.clientId&&(e.clientId=this.options.clientId),this.options.clientSecret&&(e.clientSecret=this.options.clientSecret);let t=await this.fetchAuth({path:"/mcp/connections/cli",options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}});if(!t.ok){let a=await t.text(),l=`HTTP ${t.status}`;try{l=JSON.parse(a).message??l}catch{}throw new Error(`Failed to create tunnel: ${l}`)}let r=await t.json();if(this.connectionId=r.id,this.tunnelUrl=r.serverUrl,!this.connectionId||!this.tunnelUrl)throw new Error("Server did not return a valid connection ID or tunnel URL.");if(!r.tunnel?.token||!r.tunnel?.upstreamUrl)throw new Error("Tunnel configuration not available.");this.pikoClient=os({endpointId:r.id,upstreamUrl:r.tunnel.upstreamUrl,token:r.tunnel.token,localAddr:this.localUrl.host,logger:{info:()=>{},warn:()=>{},error:()=>{}}}),this.pikoClient.on("error",a=>{let l=a instanceof Error?a:new Error(String(a));this.emit("error",l)}),this.pikoClient.on("disconnected",()=>{this.ge=!1,this.emit("disconnected")});let n=new Promise((a,l)=>{let h=setTimeout(()=>l(new Error("Connection timeout")),3e4);this.pikoClient.on("connected",()=>{clearTimeout(h),a()}),this.pikoClient.on("error",u=>{clearTimeout(h),l(u)})});await this.pikoClient.start(),await n,this.ge=!0,await this.activateTunnel();let s=this.options.syncIntervalMs??5*60*1e3;s>0&&(this.syncTimer=setInterval(()=>this.syncTools(),s));let o=this.info;if(!o)throw new Error("Tunnel connected but failed to retrieve connection info.");return this.emit("connected",o),o}async stop(){if(this.syncTimer&&(clearInterval(this.syncTimer),this.syncTimer=null),(this.options.cleanupOnStop??!0)&&this.connectionId)try{await this.fetchAuth({path:`/mcp/connections/${this.connectionId}`,options:{method:"DELETE"}})}catch{}if(this.pikoClient){try{await this.pikoClient.stop()}catch{}this.pikoClient=null}this.ge=!1,this.connectionId=null,this.tunnelUrl=null}async createRecipe({name:e}={}){if(!this.connectionId)throw new Error("Tunnel is not started.");let t=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/create-recipe`,options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e??this.options.name})}});if(!t.ok)throw new Error(`Failed to create recipe (HTTP ${t.status}).`);return(await t.json()).link}async activateTunnel(){let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/activate-tunnel`,options:{method:"POST"}});if(e.ok){let t=await e.json();t.status==="oauth_required"&&t.authUrl&&this.emit("oauthRequired",{authUrl:t.authUrl})}else await this.syncTools()}async syncTools(){try{let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/sync-tools`,options:{method:"POST"}});if(e.ok){let t=await e.json();if(t.requiresOAuth&&t.oauthUrl)this.emit("oauthRequired",{authUrl:t.oauthUrl});else{let r=Array.isArray(t.tools)?t.tools.length:0;this.emit("toolsSynced",{toolCount:r})}}}catch{}}};var Ke=process.env.POKE_API??"https://poke.com/api/v1",_s=process.env.POKE_FRONTEND??"https://poke.com",fl=`
41
41
  @@@@@@@ @@@@@@@@
42
42
  @@@@@@@@@@@@ @@@@@@@@@@@@@
43
43
  @@@@@@@@@@ @@@@@@@@@@
package/dist/index.cjs CHANGED
@@ -12,4 +12,4 @@ tell application "System Events" to get value of property list item "CFBundleNam
12
12
  `;else n+=`${Se(o)}: ${Se(a)}\r
13
13
  `;!t["content-length"]&&!t["Content-Length"]&&(n+=`Content-Length: ${s.length}\r
14
14
  `),n+=`\r
15
- `;let i=Kr.encode(n);return Lt(i,s)}async function Jr(r,e,t){try{let{headerBytes:s,remainder:n}=await $n(r),i=Yr.decode(s),o=jn(i);o.body=await qn(r,o.headers,n);let[a,l]=e.split(":"),c=l?parseInt(l,10):80,f=await zn(o,a,c);await r.write(f)}catch(s){t?.warn("proxy error",{error:String(s)});try{let n=Kr.encode("Bad Gateway"),i=Zr(502,"Bad Gateway",{},n);await r.write(i)}catch{}}finally{try{await r.close()}catch{}}}function zn(r,e,t){return new Promise((s,n)=>{let i={},o=new Set(["host","transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[l,c]of Object.entries(r.headers))o.has(l)||(i[l]=c);i.host=`${e}:${t}`,r.body&&r.body.length>0&&!i["content-length"]&&(i["content-length"]=String(r.body.length));let a=Vr.default.request({hostname:e,port:t,method:r.method,path:r.path,headers:i},l=>{let c=[],f=0;l.on("data",u=>{if(f+=u.length,f>Nt){l.destroy(new Error("response body exceeds size limit"));return}c.push(u)}),l.on("end",()=>{let u=Buffer.concat(c),m={},x=new Set(["transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[g,b]of Object.entries(l.headers))b!==void 0&&!x.has(g)&&(m[g]=b);m["content-length"]=String(u.length);let _=Zr(l.statusCode??500,l.statusMessage??"Internal Server Error",m,new Uint8Array(u));s(_)}),l.on("error",n)});a.on("error",n),r.body&&r.body.length>0&&a.write(r.body),a.end()})}h();h();var X=class extends Error{constructor(e){super(e),this.name="PikoAuthError"}},q=class extends Error{statusCode;constructor(e,t){super(e),this.name="PikoConnectionError",this.statusCode=t}};var Qr=100,Vn=15e3,Yn=.3,Kn=new Set([400,401,403,404,405,410]);function es(r){if(r instanceof X)return!1;if(typeof r=="object"&&r!==null&&"statusCode"in r){let e=r.statusCode;if(typeof e=="number")return!Kn.has(e)}if(r instanceof Error){let e=r.message.toLowerCase();if(/\b401\b/.test(e)||/\b403\b/.test(e)||e.includes("unauthorized")||e.includes("forbidden"))return!1}return!0}function ts(r){let e=Math.min(Qr*Math.pow(2,r),Vn),t=e*Yn*(Math.random()*2-1);return Math.max(Qr,Math.round(e+t))}h();h();var go=v(Xs(),1),yo=v(Ht(),1),wo=v(Yt(),1),an=v(et(),1),_o=v(on(),1);var ln=an.default;async function cn(r){let e=vo(r.upstreamUrl,r.endpointId);return new Promise((t,s)=>{if(r.signal?.aborted){s(new q("aborted"));return}let n=new ln(e,{headers:{Authorization:`Bearer ${r.token}`}});n.binaryType="nodebuffer";let i=()=>{n.close(),s(new q("aborted"))};r.signal?.addEventListener("abort",i,{once:!0}),n.on("open",()=>{r.signal?.removeEventListener("abort",i);let o=Ut(n);t({transport:o,ws:n})}),n.on("error",o=>{r.signal?.removeEventListener("abort",i),s(new q(o.message))}),n.on("unexpected-response",(o,a)=>{r.signal?.removeEventListener("abort",i),a.statusCode===401||a.statusCode===403?s(new X(`Authentication failed: HTTP ${a.statusCode}`)):s(new q(`Unexpected HTTP ${a.statusCode} from upstream`,a.statusCode))})})}function vo(r,e){return`${r.replace(/\/$/,"").replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}/piko/v1/upstream/${e}`}var So={info(){},warn(){},error(){}},Oe=class{opts;logger;abortController;session=null;running=!1;isConnected=!1;listeners=new Map;constructor(e){this.opts={...e,logger:e.logger??So},this.logger=this.opts.logger,this.abortController=new AbortController,e.signal&&e.signal.addEventListener("abort",()=>{this.abortController.abort()})}get connected(){return this.isConnected}on(e,t){let s=this.listeners.get(e);s||(s=new Set,this.listeners.set(e,s)),s.add(t)}off(e,t){this.listeners.get(e)?.delete(t)}async start(){this.running||(this.abortController.signal.aborted&&(this.abortController=new AbortController),this.running=!0,this.runLoop())}async stop(){this.running=!1,this.abortController.abort(),this.session&&(await this.session.close().catch(()=>{}),this.session=null),this.setConnected(!1)}async runLoop(){let e=0;for(;this.running&&!this.abortController.signal.aborted;)try{await this.connectAndServe(),e=0}catch(t){if(this.setConnected(!1),this.session=null,!this.running||this.abortController.signal.aborted)return;if(!es(t)){this.logger.error("Fatal error, not retrying"),this.emit("error",t),this.running=!1;return}e++;let s=ts(e);this.logger.warn("Connection lost, reconnecting",{attempt:e,retryInMs:s}),this.emit("disconnected"),await this.sleep(s)}}async connectAndServe(){let{transport:e}=await cn({upstreamUrl:this.opts.upstreamUrl,endpointId:this.opts.endpointId,token:this.opts.token,signal:this.abortController.signal}),t=At(e,{enableKeepAlive:!0,keepAliveInterval:3e4});this.session=t,this.setConnected(!0),this.emit("connected"),this.logger.info("Connected to upstream");try{for(;this.running&&!t.isClosed();){let s=await t.accept();Jr(s,this.opts.localAddr,{warn:this.logger.warn.bind(this.logger)}).catch(n=>{this.logger.warn("Stream proxy error",{error:String(n)})})}}finally{t.isClosed()||await t.close().catch(()=>{})}}setConnected(e){this.isConnected=e}emit(e,...t){let s=this.listeners.get(e);if(s)for(let n of s)try{n(...t)}catch{}}sleep(e){return new Promise((t,s)=>{if(this.abortController.signal.aborted){t();return}let n=setTimeout(t,e);this.abortController.signal.addEventListener("abort",()=>{clearTimeout(n),t()},{once:!0})})}};function hn(r){return new Oe(r)}var rt=class{options;localUrl;listeners=new Map;pikoClient=null;syncTimer=null;connectionId=null;tunnelUrl=null;_connected=!1;get info(){return!this.connectionId||!this.tunnelUrl?null:{connectionId:this.connectionId,tunnelUrl:this.tunnelUrl,localUrl:this.options.url,name:this.options.name}}get connected(){return this._connected}constructor(e){this.options=e;try{this.localUrl=new URL(e.url)}catch{throw new Error(`Invalid URL: ${e.url}`)}}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this}off(e,t){return this.listeners.get(e)?.delete(t),this}emit(e,...t){for(let s of this.listeners.get(e)??[])s(...t)}resolveToken(){let e=this.options.token??L()?.token;if(!e)throw new T("Not logged in. Run 'poke login'.");return e}fetchAuth({path:e,options:t}){return fr({path:e,options:t,token:this.resolveToken(),baseUrl:this.options.baseUrl})}async start(){let e={name:this.options.name,serverUrl:this.options.url,tunnel:!0};this.options.clientId&&(e.clientId=this.options.clientId),this.options.clientSecret&&(e.clientSecret=this.options.clientSecret);let t=await this.fetchAuth({path:"/mcp/connections/cli",options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}});if(!t.ok){let a=await t.text(),l=`HTTP ${t.status}`;try{l=JSON.parse(a).message??l}catch{}throw new Error(`Failed to create tunnel: ${l}`)}let s=await t.json();if(this.connectionId=s.id,this.tunnelUrl=s.serverUrl,!s.tunnel?.token||!s.tunnel?.upstreamUrl)throw new Error("Tunnel configuration not available.");this.pikoClient=hn({endpointId:s.id,upstreamUrl:s.tunnel.upstreamUrl,token:s.tunnel.token,localAddr:this.localUrl.host,logger:{info:()=>{},warn:()=>{},error:()=>{}}}),this.pikoClient.on("error",a=>{let l=a instanceof Error?a:new Error(String(a));this.emit("error",l)}),this.pikoClient.on("disconnected",()=>{this._connected=!1,this.emit("disconnected")});let n=new Promise((a,l)=>{let c=setTimeout(()=>l(new Error("Connection timeout")),3e4);this.pikoClient.on("connected",()=>{clearTimeout(c),a()}),this.pikoClient.on("error",f=>{clearTimeout(c),l(f)})});await this.pikoClient.start(),await n,this._connected=!0,await this.activateTunnel();let i=this.options.syncIntervalMs??5*60*1e3;i>0&&(this.syncTimer=setInterval(()=>this.syncTools(),i));let o=this.info;return this.emit("connected",o),o}async stop(){if(this.syncTimer&&(clearInterval(this.syncTimer),this.syncTimer=null),(this.options.cleanupOnStop??!0)&&this.connectionId)try{await this.fetchAuth({path:`/mcp/connections/${this.connectionId}`,options:{method:"DELETE"}})}catch{}if(this.pikoClient){try{await this.pikoClient.stop()}catch{}this.pikoClient=null}this._connected=!1,this.connectionId=null,this.tunnelUrl=null}async createRecipe({name:e}={}){if(!this.connectionId)throw new Error("Tunnel is not started.");let t=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/create-recipe`,options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e??this.options.name})}});if(!t.ok)throw new Error(`Failed to create recipe (HTTP ${t.status}).`);return(await t.json()).link}async activateTunnel(){let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/activate-tunnel`,options:{method:"POST"}});if(e.ok){let t=await e.json();t.status==="oauth_required"&&t.authUrl&&this.emit("oauthRequired",{authUrl:t.authUrl})}else await this.syncTools()}async syncTools(){try{let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/sync-tools`,options:{method:"POST"}});if(e.ok){let t=await e.json();if(t.requiresOAuth&&t.oauthUrl)this.emit("oauthRequired",{authUrl:t.oauthUrl});else{let s=Array.isArray(t.tools)?t.tools.length:0;this.emit("toolsSynced",{toolCount:s})}}}catch{}}};0&&(module.exports={Poke,PokeAuthError,PokeTunnel,getToken,isLoggedIn,login,logout});
15
+ `;let i=Kr.encode(n);return Lt(i,s)}async function Jr(r,e,t){try{let{headerBytes:s,remainder:n}=await $n(r),i=Yr.decode(s),o=jn(i);o.body=await qn(r,o.headers,n);let[a,l]=e.split(":"),c=l?parseInt(l,10):80,f=await zn(o,a,c);await r.write(f)}catch(s){t?.warn("proxy error",{error:String(s)});try{let n=Kr.encode("Bad Gateway"),i=Zr(502,"Bad Gateway",{},n);await r.write(i)}catch{}}finally{try{await r.close()}catch{}}}function zn(r,e,t){return new Promise((s,n)=>{let i={},o=new Set(["host","transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[l,c]of Object.entries(r.headers))o.has(l)||(i[l]=c);i.host=`${e}:${t}`,r.body&&r.body.length>0&&!i["content-length"]&&(i["content-length"]=String(r.body.length));let a=Vr.default.request({hostname:e,port:t,method:r.method,path:r.path,headers:i},l=>{let c=[],f=0;l.on("data",u=>{if(f+=u.length,f>Nt){l.destroy(new Error("response body exceeds size limit"));return}c.push(u)}),l.on("end",()=>{let u=Buffer.concat(c),m={},x=new Set(["transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[g,b]of Object.entries(l.headers))b!==void 0&&!x.has(g)&&(m[g]=b);m["content-length"]=String(u.length);let _=Zr(l.statusCode??500,l.statusMessage??"Internal Server Error",m,new Uint8Array(u));s(_)}),l.on("error",n)});a.on("error",n),r.body&&r.body.length>0&&a.write(r.body),a.end()})}h();h();var X=class extends Error{constructor(e){super(e),this.name="PikoAuthError"}},q=class extends Error{statusCode;constructor(e,t){super(e),this.name="PikoConnectionError",this.statusCode=t}};var Qr=100,Vn=15e3,Yn=.3,Kn=new Set([400,401,403,404,405,410]);function es(r){if(r instanceof X)return!1;if(typeof r=="object"&&r!==null&&"statusCode"in r){let e=r.statusCode;if(typeof e=="number")return!Kn.has(e)}if(r instanceof Error){let e=r.message.toLowerCase();if(/\b401\b/.test(e)||/\b403\b/.test(e)||e.includes("unauthorized")||e.includes("forbidden"))return!1}return!0}function ts(r){let e=Math.min(Qr*Math.pow(2,r),Vn),t=e*Yn*(Math.random()*2-1);return Math.max(Qr,Math.round(e+t))}h();h();var go=v(Xs(),1),yo=v(Ht(),1),wo=v(Yt(),1),an=v(et(),1),_o=v(on(),1);var ln=an.default;async function cn(r){let e=vo(r.upstreamUrl,r.endpointId);return new Promise((t,s)=>{if(r.signal?.aborted){s(new q("aborted"));return}let n=new ln(e,{headers:{Authorization:`Bearer ${r.token}`}});n.binaryType="nodebuffer";let i=()=>{n.close(),s(new q("aborted"))};r.signal?.addEventListener("abort",i,{once:!0}),n.on("open",()=>{r.signal?.removeEventListener("abort",i);let o=Ut(n);t({transport:o,ws:n})}),n.on("error",o=>{r.signal?.removeEventListener("abort",i),s(new q(o.message))}),n.on("unexpected-response",(o,a)=>{r.signal?.removeEventListener("abort",i),a.statusCode===401||a.statusCode===403?s(new X(`Authentication failed: HTTP ${a.statusCode}`)):s(new q(`Unexpected HTTP ${a.statusCode} from upstream`,a.statusCode))})})}function vo(r,e){return`${r.replace(/\/$/,"").replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}/piko/v1/upstream/${e}`}var So={info(){},warn(){},error(){}},Oe=class{opts;logger;abortController;session=null;running=!1;isConnected=!1;listeners=new Map;constructor(e){this.opts={...e,logger:e.logger??So},this.logger=this.opts.logger,this.abortController=new AbortController,e.signal&&e.signal.addEventListener("abort",()=>{this.abortController.abort()})}get connected(){return this.isConnected}on(e,t){let s=this.listeners.get(e);s||(s=new Set,this.listeners.set(e,s)),s.add(t)}off(e,t){this.listeners.get(e)?.delete(t)}async start(){this.running||(this.abortController.signal.aborted&&(this.abortController=new AbortController),this.running=!0,this.runLoop())}async stop(){this.running=!1,this.abortController.abort(),this.session&&(await this.session.close().catch(()=>{}),this.session=null),this.setConnected(!1)}async runLoop(){let e=0;for(;this.running&&!this.abortController.signal.aborted;)try{await this.connectAndServe(),e=0}catch(t){if(this.setConnected(!1),this.session=null,!this.running||this.abortController.signal.aborted)return;if(!es(t)){this.logger.error("Fatal error, not retrying"),this.emit("error",t),this.running=!1;return}e++;let s=ts(e);this.logger.warn("Connection lost, reconnecting",{attempt:e,retryInMs:s}),this.emit("disconnected"),await this.sleep(s)}}async connectAndServe(){let{transport:e}=await cn({upstreamUrl:this.opts.upstreamUrl,endpointId:this.opts.endpointId,token:this.opts.token,signal:this.abortController.signal}),t=At(e,{enableKeepAlive:!0,keepAliveInterval:3e4});this.session=t,this.setConnected(!0),this.emit("connected"),this.logger.info("Connected to upstream");try{for(;this.running&&!t.isClosed();){let s=await t.accept();Jr(s,this.opts.localAddr,{warn:this.logger.warn.bind(this.logger)}).catch(n=>{this.logger.warn("Stream proxy error",{error:String(n)})})}}finally{t.isClosed()||await t.close().catch(()=>{})}}setConnected(e){this.isConnected=e}emit(e,...t){let s=this.listeners.get(e);if(s)for(let n of s)try{n(...t)}catch{}}sleep(e){return new Promise((t,s)=>{if(this.abortController.signal.aborted){t();return}let n=setTimeout(t,e);this.abortController.signal.addEventListener("abort",()=>{clearTimeout(n),t()},{once:!0})})}};function hn(r){return new Oe(r)}var rt=class{options;localUrl;listeners=new Map;pikoClient=null;syncTimer=null;connectionId=null;tunnelUrl=null;_connected=!1;get info(){return!this.connectionId||!this.tunnelUrl?null:{connectionId:this.connectionId,tunnelUrl:this.tunnelUrl,localUrl:this.options.url,name:this.options.name}}get connected(){return this._connected}constructor(e){this.options=e;try{this.localUrl=new URL(e.url)}catch{throw new Error(`Invalid URL: ${e.url}`)}}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this}off(e,t){return this.listeners.get(e)?.delete(t),this}emit(e,...t){for(let s of this.listeners.get(e)??[])s(...t)}resolveToken(){let e=this.options.token??L()?.token;if(!e)throw new T("Not logged in. Run 'poke login'.");return e}fetchAuth({path:e,options:t}){return fr({path:e,options:t,token:this.resolveToken(),baseUrl:this.options.baseUrl})}async start(){let e={name:this.options.name,serverUrl:this.options.url,tunnel:!0};this.options.clientId&&(e.clientId=this.options.clientId),this.options.clientSecret&&(e.clientSecret=this.options.clientSecret);let t=await this.fetchAuth({path:"/mcp/connections/cli",options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}});if(!t.ok){let a=await t.text(),l=`HTTP ${t.status}`;try{l=JSON.parse(a).message??l}catch{}throw new Error(`Failed to create tunnel: ${l}`)}let s=await t.json();if(this.connectionId=s.id,this.tunnelUrl=s.serverUrl,!this.connectionId||!this.tunnelUrl)throw new Error("Server did not return a valid connection ID or tunnel URL.");if(!s.tunnel?.token||!s.tunnel?.upstreamUrl)throw new Error("Tunnel configuration not available.");this.pikoClient=hn({endpointId:s.id,upstreamUrl:s.tunnel.upstreamUrl,token:s.tunnel.token,localAddr:this.localUrl.host,logger:{info:()=>{},warn:()=>{},error:()=>{}}}),this.pikoClient.on("error",a=>{let l=a instanceof Error?a:new Error(String(a));this.emit("error",l)}),this.pikoClient.on("disconnected",()=>{this._connected=!1,this.emit("disconnected")});let n=new Promise((a,l)=>{let c=setTimeout(()=>l(new Error("Connection timeout")),3e4);this.pikoClient.on("connected",()=>{clearTimeout(c),a()}),this.pikoClient.on("error",f=>{clearTimeout(c),l(f)})});await this.pikoClient.start(),await n,this._connected=!0,await this.activateTunnel();let i=this.options.syncIntervalMs??5*60*1e3;i>0&&(this.syncTimer=setInterval(()=>this.syncTools(),i));let o=this.info;if(!o)throw new Error("Tunnel connected but failed to retrieve connection info.");return this.emit("connected",o),o}async stop(){if(this.syncTimer&&(clearInterval(this.syncTimer),this.syncTimer=null),(this.options.cleanupOnStop??!0)&&this.connectionId)try{await this.fetchAuth({path:`/mcp/connections/${this.connectionId}`,options:{method:"DELETE"}})}catch{}if(this.pikoClient){try{await this.pikoClient.stop()}catch{}this.pikoClient=null}this._connected=!1,this.connectionId=null,this.tunnelUrl=null}async createRecipe({name:e}={}){if(!this.connectionId)throw new Error("Tunnel is not started.");let t=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/create-recipe`,options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e??this.options.name})}});if(!t.ok)throw new Error(`Failed to create recipe (HTTP ${t.status}).`);return(await t.json()).link}async activateTunnel(){let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/activate-tunnel`,options:{method:"POST"}});if(e.ok){let t=await e.json();t.status==="oauth_required"&&t.authUrl&&this.emit("oauthRequired",{authUrl:t.authUrl})}else await this.syncTools()}async syncTools(){try{let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/sync-tools`,options:{method:"POST"}});if(e.ok){let t=await e.json();if(t.requiresOAuth&&t.oauthUrl)this.emit("oauthRequired",{authUrl:t.oauthUrl});else{let s=Array.isArray(t.tools)?t.tools.length:0;this.emit("toolsSynced",{toolCount:s})}}}catch{}}};0&&(module.exports={Poke,PokeAuthError,PokeTunnel,getToken,isLoggedIn,login,logout});
package/dist/index.mjs CHANGED
@@ -13,4 +13,4 @@ tell application "System Events" to get value of property list item "CFBundleNam
13
13
  `;else n+=`${_e(o)}: ${_e(a)}\r
14
14
  `;!t["content-length"]&&!t["Content-Length"]&&(n+=`Content-Length: ${s.length}\r
15
15
  `),n+=`\r
16
- `;let i=br.encode(n);return St(i,s)}async function Tr(r,e,t){try{let{headerBytes:s,remainder:n}=await Dn(r),i=xr.decode(s),o=$n(i);o.body=await jn(r,o.headers,n);let[a,l]=e.split(":"),c=l?parseInt(l,10):80,h=await Hn(o,a,c);await r.write(h)}catch(s){t?.warn("proxy error",{error:String(s)});try{let n=br.encode("Bad Gateway"),i=kr(502,"Bad Gateway",{},n);await r.write(i)}catch{}}finally{try{await r.close()}catch{}}}function Hn(r,e,t){return new Promise((s,n)=>{let i={},o=new Set(["host","transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[l,c]of Object.entries(r.headers))o.has(l)||(i[l]=c);i.host=`${e}:${t}`,r.body&&r.body.length>0&&!i["content-length"]&&(i["content-length"]=String(r.body.length));let a=Fn.request({hostname:e,port:t,method:r.method,path:r.path,headers:i},l=>{let c=[],h=0;l.on("data",f=>{if(h+=f.length,h>xt){l.destroy(new Error("response body exceeds size limit"));return}c.push(f)}),l.on("end",()=>{let f=Buffer.concat(c),d={},_=new Set(["transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[p,S]of Object.entries(l.headers))S!==void 0&&!_.has(p)&&(d[p]=S);d["content-length"]=String(f.length);let y=kr(l.statusCode??500,l.statusMessage??"Internal Server Error",d,new Uint8Array(f));s(y)}),l.on("error",n)});a.on("error",n),r.body&&r.body.length>0&&a.write(r.body),a.end()})}var Y=class extends Error{constructor(e){super(e),this.name="PikoAuthError"}},$=class extends Error{statusCode;constructor(e,t){super(e),this.name="PikoConnectionError",this.statusCode=t}};var Cr=100,zn=15e3,Vn=.3,Yn=new Set([400,401,403,404,405,410]);function Pr(r){if(r instanceof Y)return!1;if(typeof r=="object"&&r!==null&&"statusCode"in r){let e=r.statusCode;if(typeof e=="number")return!Yn.has(e)}if(r instanceof Error){let e=r.message.toLowerCase();if(/\b401\b/.test(e)||/\b403\b/.test(e)||e.includes("unauthorized")||e.includes("forbidden"))return!1}return!0}function Ar(r){let e=Math.min(Cr*Math.pow(2,r),zn),t=e*Vn*(Math.random()*2-1);return Math.max(Cr,Math.round(e+t))}var mo=ge(Es(),1),go=ge(Lt(),1),yo=ge(Wt(),1),Ns=ge(Ve(),1),wo=ge(Ls(),1);var Is=Ns.default;async function Ws(r){let e=_o(r.upstreamUrl,r.endpointId);return new Promise((t,s)=>{if(r.signal?.aborted){s(new $("aborted"));return}let n=new Is(e,{headers:{Authorization:`Bearer ${r.token}`}});n.binaryType="nodebuffer";let i=()=>{n.close(),s(new $("aborted"))};r.signal?.addEventListener("abort",i,{once:!0}),n.on("open",()=>{r.signal?.removeEventListener("abort",i);let o=_t(n);t({transport:o,ws:n})}),n.on("error",o=>{r.signal?.removeEventListener("abort",i),s(new $(o.message))}),n.on("unexpected-response",(o,a)=>{r.signal?.removeEventListener("abort",i),a.statusCode===401||a.statusCode===403?s(new Y(`Authentication failed: HTTP ${a.statusCode}`)):s(new $(`Unexpected HTTP ${a.statusCode} from upstream`,a.statusCode))})})}function _o(r,e){return`${r.replace(/\/$/,"").replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}/piko/v1/upstream/${e}`}var vo={info(){},warn(){},error(){}},Pe=class{opts;logger;abortController;session=null;running=!1;isConnected=!1;listeners=new Map;constructor(e){this.opts={...e,logger:e.logger??vo},this.logger=this.opts.logger,this.abortController=new AbortController,e.signal&&e.signal.addEventListener("abort",()=>{this.abortController.abort()})}get connected(){return this.isConnected}on(e,t){let s=this.listeners.get(e);s||(s=new Set,this.listeners.set(e,s)),s.add(t)}off(e,t){this.listeners.get(e)?.delete(t)}async start(){this.running||(this.abortController.signal.aborted&&(this.abortController=new AbortController),this.running=!0,this.runLoop())}async stop(){this.running=!1,this.abortController.abort(),this.session&&(await this.session.close().catch(()=>{}),this.session=null),this.setConnected(!1)}async runLoop(){let e=0;for(;this.running&&!this.abortController.signal.aborted;)try{await this.connectAndServe(),e=0}catch(t){if(this.setConnected(!1),this.session=null,!this.running||this.abortController.signal.aborted)return;if(!Pr(t)){this.logger.error("Fatal error, not retrying"),this.emit("error",t),this.running=!1;return}e++;let s=Ar(e);this.logger.warn("Connection lost, reconnecting",{attempt:e,retryInMs:s}),this.emit("disconnected"),await this.sleep(s)}}async connectAndServe(){let{transport:e}=await Ws({upstreamUrl:this.opts.upstreamUrl,endpointId:this.opts.endpointId,token:this.opts.token,signal:this.abortController.signal}),t=yt(e,{enableKeepAlive:!0,keepAliveInterval:3e4});this.session=t,this.setConnected(!0),this.emit("connected"),this.logger.info("Connected to upstream");try{for(;this.running&&!t.isClosed();){let s=await t.accept();Tr(s,this.opts.localAddr,{warn:this.logger.warn.bind(this.logger)}).catch(n=>{this.logger.warn("Stream proxy error",{error:String(n)})})}}finally{t.isClosed()||await t.close().catch(()=>{})}}setConnected(e){this.isConnected=e}emit(e,...t){let s=this.listeners.get(e);if(s)for(let n of s)try{n(...t)}catch{}}sleep(e){return new Promise((t,s)=>{if(this.abortController.signal.aborted){t();return}let n=setTimeout(t,e);this.abortController.signal.addEventListener("abort",()=>{clearTimeout(n),t()},{once:!0})})}};function Bs(r){return new Pe(r)}var Gt=class{options;localUrl;listeners=new Map;pikoClient=null;syncTimer=null;connectionId=null;tunnelUrl=null;_connected=!1;get info(){return!this.connectionId||!this.tunnelUrl?null:{connectionId:this.connectionId,tunnelUrl:this.tunnelUrl,localUrl:this.options.url,name:this.options.name}}get connected(){return this._connected}constructor(e){this.options=e;try{this.localUrl=new URL(e.url)}catch{throw new Error(`Invalid URL: ${e.url}`)}}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this}off(e,t){return this.listeners.get(e)?.delete(t),this}emit(e,...t){for(let s of this.listeners.get(e)??[])s(...t)}resolveToken(){let e=this.options.token??U()?.token;if(!e)throw new k("Not logged in. Run 'poke login'.");return e}fetchAuth({path:e,options:t}){return Xt({path:e,options:t,token:this.resolveToken(),baseUrl:this.options.baseUrl})}async start(){let e={name:this.options.name,serverUrl:this.options.url,tunnel:!0};this.options.clientId&&(e.clientId=this.options.clientId),this.options.clientSecret&&(e.clientSecret=this.options.clientSecret);let t=await this.fetchAuth({path:"/mcp/connections/cli",options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}});if(!t.ok){let a=await t.text(),l=`HTTP ${t.status}`;try{l=JSON.parse(a).message??l}catch{}throw new Error(`Failed to create tunnel: ${l}`)}let s=await t.json();if(this.connectionId=s.id,this.tunnelUrl=s.serverUrl,!s.tunnel?.token||!s.tunnel?.upstreamUrl)throw new Error("Tunnel configuration not available.");this.pikoClient=Bs({endpointId:s.id,upstreamUrl:s.tunnel.upstreamUrl,token:s.tunnel.token,localAddr:this.localUrl.host,logger:{info:()=>{},warn:()=>{},error:()=>{}}}),this.pikoClient.on("error",a=>{let l=a instanceof Error?a:new Error(String(a));this.emit("error",l)}),this.pikoClient.on("disconnected",()=>{this._connected=!1,this.emit("disconnected")});let n=new Promise((a,l)=>{let c=setTimeout(()=>l(new Error("Connection timeout")),3e4);this.pikoClient.on("connected",()=>{clearTimeout(c),a()}),this.pikoClient.on("error",h=>{clearTimeout(c),l(h)})});await this.pikoClient.start(),await n,this._connected=!0,await this.activateTunnel();let i=this.options.syncIntervalMs??5*60*1e3;i>0&&(this.syncTimer=setInterval(()=>this.syncTools(),i));let o=this.info;return this.emit("connected",o),o}async stop(){if(this.syncTimer&&(clearInterval(this.syncTimer),this.syncTimer=null),(this.options.cleanupOnStop??!0)&&this.connectionId)try{await this.fetchAuth({path:`/mcp/connections/${this.connectionId}`,options:{method:"DELETE"}})}catch{}if(this.pikoClient){try{await this.pikoClient.stop()}catch{}this.pikoClient=null}this._connected=!1,this.connectionId=null,this.tunnelUrl=null}async createRecipe({name:e}={}){if(!this.connectionId)throw new Error("Tunnel is not started.");let t=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/create-recipe`,options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e??this.options.name})}});if(!t.ok)throw new Error(`Failed to create recipe (HTTP ${t.status}).`);return(await t.json()).link}async activateTunnel(){let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/activate-tunnel`,options:{method:"POST"}});if(e.ok){let t=await e.json();t.status==="oauth_required"&&t.authUrl&&this.emit("oauthRequired",{authUrl:t.authUrl})}else await this.syncTools()}async syncTools(){try{let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/sync-tools`,options:{method:"POST"}});if(e.ok){let t=await e.json();if(t.requiresOAuth&&t.oauthUrl)this.emit("oauthRequired",{authUrl:t.oauthUrl});else{let s=Array.isArray(t.tools)?t.tools.length:0;this.emit("toolsSynced",{toolCount:s})}}}catch{}}};export{et as Poke,k as PokeAuthError,Gt as PokeTunnel,Bn as getToken,Wn as isLoggedIn,Nn as login,In as logout};
16
+ `;let i=br.encode(n);return St(i,s)}async function Tr(r,e,t){try{let{headerBytes:s,remainder:n}=await Dn(r),i=xr.decode(s),o=$n(i);o.body=await jn(r,o.headers,n);let[a,l]=e.split(":"),c=l?parseInt(l,10):80,h=await Hn(o,a,c);await r.write(h)}catch(s){t?.warn("proxy error",{error:String(s)});try{let n=br.encode("Bad Gateway"),i=kr(502,"Bad Gateway",{},n);await r.write(i)}catch{}}finally{try{await r.close()}catch{}}}function Hn(r,e,t){return new Promise((s,n)=>{let i={},o=new Set(["host","transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[l,c]of Object.entries(r.headers))o.has(l)||(i[l]=c);i.host=`${e}:${t}`,r.body&&r.body.length>0&&!i["content-length"]&&(i["content-length"]=String(r.body.length));let a=Fn.request({hostname:e,port:t,method:r.method,path:r.path,headers:i},l=>{let c=[],h=0;l.on("data",f=>{if(h+=f.length,h>xt){l.destroy(new Error("response body exceeds size limit"));return}c.push(f)}),l.on("end",()=>{let f=Buffer.concat(c),d={},_=new Set(["transfer-encoding","connection","keep-alive","te","trailer","upgrade"]);for(let[p,S]of Object.entries(l.headers))S!==void 0&&!_.has(p)&&(d[p]=S);d["content-length"]=String(f.length);let y=kr(l.statusCode??500,l.statusMessage??"Internal Server Error",d,new Uint8Array(f));s(y)}),l.on("error",n)});a.on("error",n),r.body&&r.body.length>0&&a.write(r.body),a.end()})}var Y=class extends Error{constructor(e){super(e),this.name="PikoAuthError"}},$=class extends Error{statusCode;constructor(e,t){super(e),this.name="PikoConnectionError",this.statusCode=t}};var Cr=100,zn=15e3,Vn=.3,Yn=new Set([400,401,403,404,405,410]);function Pr(r){if(r instanceof Y)return!1;if(typeof r=="object"&&r!==null&&"statusCode"in r){let e=r.statusCode;if(typeof e=="number")return!Yn.has(e)}if(r instanceof Error){let e=r.message.toLowerCase();if(/\b401\b/.test(e)||/\b403\b/.test(e)||e.includes("unauthorized")||e.includes("forbidden"))return!1}return!0}function Ar(r){let e=Math.min(Cr*Math.pow(2,r),zn),t=e*Vn*(Math.random()*2-1);return Math.max(Cr,Math.round(e+t))}var mo=ge(Es(),1),go=ge(Lt(),1),yo=ge(Wt(),1),Ns=ge(Ve(),1),wo=ge(Ls(),1);var Is=Ns.default;async function Ws(r){let e=_o(r.upstreamUrl,r.endpointId);return new Promise((t,s)=>{if(r.signal?.aborted){s(new $("aborted"));return}let n=new Is(e,{headers:{Authorization:`Bearer ${r.token}`}});n.binaryType="nodebuffer";let i=()=>{n.close(),s(new $("aborted"))};r.signal?.addEventListener("abort",i,{once:!0}),n.on("open",()=>{r.signal?.removeEventListener("abort",i);let o=_t(n);t({transport:o,ws:n})}),n.on("error",o=>{r.signal?.removeEventListener("abort",i),s(new $(o.message))}),n.on("unexpected-response",(o,a)=>{r.signal?.removeEventListener("abort",i),a.statusCode===401||a.statusCode===403?s(new Y(`Authentication failed: HTTP ${a.statusCode}`)):s(new $(`Unexpected HTTP ${a.statusCode} from upstream`,a.statusCode))})})}function _o(r,e){return`${r.replace(/\/$/,"").replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}/piko/v1/upstream/${e}`}var vo={info(){},warn(){},error(){}},Pe=class{opts;logger;abortController;session=null;running=!1;isConnected=!1;listeners=new Map;constructor(e){this.opts={...e,logger:e.logger??vo},this.logger=this.opts.logger,this.abortController=new AbortController,e.signal&&e.signal.addEventListener("abort",()=>{this.abortController.abort()})}get connected(){return this.isConnected}on(e,t){let s=this.listeners.get(e);s||(s=new Set,this.listeners.set(e,s)),s.add(t)}off(e,t){this.listeners.get(e)?.delete(t)}async start(){this.running||(this.abortController.signal.aborted&&(this.abortController=new AbortController),this.running=!0,this.runLoop())}async stop(){this.running=!1,this.abortController.abort(),this.session&&(await this.session.close().catch(()=>{}),this.session=null),this.setConnected(!1)}async runLoop(){let e=0;for(;this.running&&!this.abortController.signal.aborted;)try{await this.connectAndServe(),e=0}catch(t){if(this.setConnected(!1),this.session=null,!this.running||this.abortController.signal.aborted)return;if(!Pr(t)){this.logger.error("Fatal error, not retrying"),this.emit("error",t),this.running=!1;return}e++;let s=Ar(e);this.logger.warn("Connection lost, reconnecting",{attempt:e,retryInMs:s}),this.emit("disconnected"),await this.sleep(s)}}async connectAndServe(){let{transport:e}=await Ws({upstreamUrl:this.opts.upstreamUrl,endpointId:this.opts.endpointId,token:this.opts.token,signal:this.abortController.signal}),t=yt(e,{enableKeepAlive:!0,keepAliveInterval:3e4});this.session=t,this.setConnected(!0),this.emit("connected"),this.logger.info("Connected to upstream");try{for(;this.running&&!t.isClosed();){let s=await t.accept();Tr(s,this.opts.localAddr,{warn:this.logger.warn.bind(this.logger)}).catch(n=>{this.logger.warn("Stream proxy error",{error:String(n)})})}}finally{t.isClosed()||await t.close().catch(()=>{})}}setConnected(e){this.isConnected=e}emit(e,...t){let s=this.listeners.get(e);if(s)for(let n of s)try{n(...t)}catch{}}sleep(e){return new Promise((t,s)=>{if(this.abortController.signal.aborted){t();return}let n=setTimeout(t,e);this.abortController.signal.addEventListener("abort",()=>{clearTimeout(n),t()},{once:!0})})}};function Bs(r){return new Pe(r)}var Gt=class{options;localUrl;listeners=new Map;pikoClient=null;syncTimer=null;connectionId=null;tunnelUrl=null;_connected=!1;get info(){return!this.connectionId||!this.tunnelUrl?null:{connectionId:this.connectionId,tunnelUrl:this.tunnelUrl,localUrl:this.options.url,name:this.options.name}}get connected(){return this._connected}constructor(e){this.options=e;try{this.localUrl=new URL(e.url)}catch{throw new Error(`Invalid URL: ${e.url}`)}}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this}off(e,t){return this.listeners.get(e)?.delete(t),this}emit(e,...t){for(let s of this.listeners.get(e)??[])s(...t)}resolveToken(){let e=this.options.token??U()?.token;if(!e)throw new k("Not logged in. Run 'poke login'.");return e}fetchAuth({path:e,options:t}){return Xt({path:e,options:t,token:this.resolveToken(),baseUrl:this.options.baseUrl})}async start(){let e={name:this.options.name,serverUrl:this.options.url,tunnel:!0};this.options.clientId&&(e.clientId=this.options.clientId),this.options.clientSecret&&(e.clientSecret=this.options.clientSecret);let t=await this.fetchAuth({path:"/mcp/connections/cli",options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}});if(!t.ok){let a=await t.text(),l=`HTTP ${t.status}`;try{l=JSON.parse(a).message??l}catch{}throw new Error(`Failed to create tunnel: ${l}`)}let s=await t.json();if(this.connectionId=s.id,this.tunnelUrl=s.serverUrl,!this.connectionId||!this.tunnelUrl)throw new Error("Server did not return a valid connection ID or tunnel URL.");if(!s.tunnel?.token||!s.tunnel?.upstreamUrl)throw new Error("Tunnel configuration not available.");this.pikoClient=Bs({endpointId:s.id,upstreamUrl:s.tunnel.upstreamUrl,token:s.tunnel.token,localAddr:this.localUrl.host,logger:{info:()=>{},warn:()=>{},error:()=>{}}}),this.pikoClient.on("error",a=>{let l=a instanceof Error?a:new Error(String(a));this.emit("error",l)}),this.pikoClient.on("disconnected",()=>{this._connected=!1,this.emit("disconnected")});let n=new Promise((a,l)=>{let c=setTimeout(()=>l(new Error("Connection timeout")),3e4);this.pikoClient.on("connected",()=>{clearTimeout(c),a()}),this.pikoClient.on("error",h=>{clearTimeout(c),l(h)})});await this.pikoClient.start(),await n,this._connected=!0,await this.activateTunnel();let i=this.options.syncIntervalMs??5*60*1e3;i>0&&(this.syncTimer=setInterval(()=>this.syncTools(),i));let o=this.info;if(!o)throw new Error("Tunnel connected but failed to retrieve connection info.");return this.emit("connected",o),o}async stop(){if(this.syncTimer&&(clearInterval(this.syncTimer),this.syncTimer=null),(this.options.cleanupOnStop??!0)&&this.connectionId)try{await this.fetchAuth({path:`/mcp/connections/${this.connectionId}`,options:{method:"DELETE"}})}catch{}if(this.pikoClient){try{await this.pikoClient.stop()}catch{}this.pikoClient=null}this._connected=!1,this.connectionId=null,this.tunnelUrl=null}async createRecipe({name:e}={}){if(!this.connectionId)throw new Error("Tunnel is not started.");let t=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/create-recipe`,options:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e??this.options.name})}});if(!t.ok)throw new Error(`Failed to create recipe (HTTP ${t.status}).`);return(await t.json()).link}async activateTunnel(){let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/activate-tunnel`,options:{method:"POST"}});if(e.ok){let t=await e.json();t.status==="oauth_required"&&t.authUrl&&this.emit("oauthRequired",{authUrl:t.authUrl})}else await this.syncTools()}async syncTools(){try{let e=await this.fetchAuth({path:`/mcp/connections/${this.connectionId}/sync-tools`,options:{method:"POST"}});if(e.ok){let t=await e.json();if(t.requiresOAuth&&t.oauthUrl)this.emit("oauthRequired",{authUrl:t.oauthUrl});else{let s=Array.isArray(t.tools)?t.tools.length:0;this.emit("toolsSynced",{toolCount:s})}}}catch{}}};export{et as Poke,k as PokeAuthError,Gt as PokeTunnel,Bn as getToken,Wn as isLoggedIn,Nn as login,In as logout};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poke",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "Poke.com Developer SDK",
6
6
  "main": "./dist/index.cjs",