@2digits/tlo-mcp 2.0.2 → 2.0.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.
Files changed (2) hide show
  1. package/dist/bin.mjs +9 -9
  2. package/package.json +8 -8
package/dist/bin.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import*as e from"@effect/platform-node/NodeRuntime";import*as t from"@effect/platform-node/NodeServices";import*as n from"effect/Effect";import*as r from"effect/Schema";import*as i from"effect/cli/Command";import*as a from"effect/cli/Flag";import{OAuth as o,Pkce as s}from"@yielded/oauth";import*as c from"effect/Clock";import*as l from"effect/Context";import*as u from"effect/Crypto";import*as d from"effect/Equal";import*as f from"effect/Exit";import*as p from"effect/Layer";import*as m from"effect/Match";import*as h from"effect/Redacted";import*as ee from"effect/Semaphore";import{FetchHttpClient as g,HttpClient as _,HttpClientError as v,HttpClientRequest as y,HttpClientResponse as te,HttpIncomingMessage as ne,HttpRouter as b,HttpServer as x,HttpServerRequest as re,HttpServerResponse as S}from"effect/http";import{ChildProcess as C,ChildProcessSpawner as w}from"effect/process";import{createHash as ie,randomBytes as ae,timingSafeEqual as oe}from"node:crypto";import{createServer as se}from"node:http";import*as ce from"@effect/platform-node/NodeHttpServer";import*as T from"effect/Deferred";import*as le from"effect/Predicate";import*as ue from"node:os";import{homedir as de}from"node:os";import*as fe from"effect/FileSystem";import*as pe from"effect/Path";import*as E from"effect/Stream";import{Context as me,Effect as D,Layer as he,Option as ge,Queue as O,Schema as k,Stream as _e}from"effect";import{HttpApi as ve,HttpApiClient as ye,HttpApiEndpoint as A,HttpApiGroup as be,HttpApiMiddleware as xe,HttpApiSchema as Se,HttpApiSecurity as Ce,OpenApi as we}from"effect/http-api";import*as j from"node:fs/promises";import*as M from"node:path";import*as Te from"node:net";import{Duplex as Ee}from"node:stream";import*as De from"node:tls";import*as Oe from"effect/ByteSize";import*as N from"effect/ai/McpProtocol";import*as ke from"effect/ai/McpServer";import*as Ae from"effect/Option";import*as je from"effect/Struct";import*as P from"effect/ai/McpSchema";import*as Me from"effect/encoding/Sse";const Ne=`https://api.orbit.teamleader.eu`,F=`${Ne}/mcp`,Pe=r.RedactedFromValue(r.String.check(r.isMinLength(1))),Fe=r.Struct({clientId:r.String.check(r.isMinLength(1)),clientSecret:r.optionalKey(Pe),accessToken:Pe,refreshToken:r.optionalKey(Pe),expiresAt:r.optionalKey(r.Finite)});var I=class extends r.TaggedError()(`OrbitAuthError`,{message:r.String}){};function Ie(e,t,n){let r=t.refreshToken??e.refreshToken;return{clientId:e.clientId,...e.clientSecret===void 0?{}:{clientSecret:e.clientSecret},accessToken:t.accessToken,...r===void 0?{}:{refreshToken:r},...t.expiresIn===void 0?{}:{expiresAt:n+t.expiresIn*1e3}}}function Le(e,t){return e.expiresAt===void 0||e.expiresAt<=t+6e4}function Re(e){return e.clientSecret===void 0?{method:`none`,publicClient:!0}:{method:`client_secret_post`,secret:e.clientSecret.pipe(h.value,h.make)}}function ze(e,t){let n=e.searchParams.get(`state`);if(n===null||e.pathname!==`/callback`||e.searchParams.getAll(`state`).length!==1)return;let r=Buffer.from(t),i=Buffer.from(n);if(r.length!==i.length||!oe(r,i))return;if(e.searchParams.has(`error`))throw Error(`Orbit authorization was denied.`);let a=e.searchParams.get(`code`);if(a!==null&&a.length!==0&&e.searchParams.getAll(`code`).length===1)return a}const Be=n.fn(`OrbitAuth.listenForLogin`)(function*(e){let t=yield*T.make(),r=yield*n.sync(se),i=yield*ce.make(()=>r,{port:0,host:`127.0.0.1`}).pipe(n.mapError(()=>I.make({message:`Could not bind OAuth callback.`})));if(!le.isTagged(i.address,`InetAddressV4`))return yield*I.make({message:`Could not bind OAuth callback.`});let a=`127.0.0.1:${i.address.port}`,o=`http://${a}/callback`,s={"content-type":`text/plain; charset=utf-8`,"cache-control":`no-store`,"referrer-policy":`no-referrer`,"content-security-policy":`default-src 'none'`},c=S.text(`Invalid callback.`,{status:400,headers:s}),l=S.text(`Authorization received. Return to your terminal to complete login.`,{headers:s}),u=S.text(`Authorization denied. Return to your terminal.`,{status:400,headers:s}),d=n.fn(`OrbitAuth.handleCallback`)(function*(){let r=yield*re.HttpServerRequest;return r.method!==`GET`||r.headers.host!==a||(yield*T.isDone(t))?c:yield*n.try({try:()=>ze(new URL(r.url,o),e),catch:()=>I.make({message:`Orbit authorization was denied.`})}).pipe(n.matchEffect({onFailure:e=>T.fail(t,e).pipe(n.map(e=>e?u:c)),onSuccess:e=>e===void 0?n.succeed(c):T.succeed(t,e).pipe(n.map(e=>e?l:c))}))}),f=yield*b.toHttpEffect(b.add(`*`,`*`,d()));return yield*x.serveEffect(f).pipe(n.provideService(x.HttpServer,i)),yield*n.addFinalizer(()=>n.sync(()=>r.closeAllConnections())),{redirectUri:o,code:T.await(t).pipe(n.timeout(`3 minutes`),n.mapError(()=>I.make({message:`Login denied or timed out. Run tlo-mcp login again.`})))}}),Ve=r.fromJsonString(Fe),He=`@2digits/tlo-mcp/orbit`;var Ue=class extends l.Service()(`@2digits/tlo-mcp/oauth/store/SessionStore`){};function We(e){return`"${e.replaceAll(`\\`,String.raw`\\`).replaceAll(`"`,String.raw`\"`)}"`}function Ge(e={}){return p.effect(Ue,n.gen(function*(){let t=yield*fe.FileSystem,i=yield*pe.Path,a=yield*w.ChildProcessSpawner,o=e.platform??process.platform,s=e.directory??i.join(de(),`.config`,`2digits`,`tlo-mcp`),c=i.join(s,`session.json`),l=i.join(s,`session.lock`),u=t.makeDirectory(s,{recursive:!0,mode:448}).pipe(n.andThen(t.chmod(s,448))),d=n.fn(`SessionStore.security`)(function*(e,t){let r=yield*a.spawn(C.make(`/usr/bin/security`,e,{stdin:t===void 0?`ignore`:E.succeed(new TextEncoder().encode(t)),stderr:`ignore`}));return yield*n.all({output:r.stdout.pipe(E.decodeText(),E.mkString),exitCode:r.exitCode},{concurrency:`unbounded`})},n.scoped),f=n.fn(`SessionStore.load`)(function*(){let e;if(o===`darwin`){let t=yield*d([`find-generic-password`,`-a`,`orbit`,`-s`,He,`-w`]);if(t.exitCode===44)return;if(t.exitCode!==0)return yield*I.make({message:`Could not read macOS Keychain.`});e=t.output.trim()}else{let r=yield*t.readFileString(c).pipe(n.catchReason(`PlatformError`,`NotFound`,()=>n.void));if(r===void 0)return;e=r}return yield*r.decodeEffect(Ve)(e)},n.mapError(()=>I.make({message:`Could not read credentials. Run tlo-mcp login again.`}))),p=n.fn(`SessionStore.save`)(function*(e){let n=yield*r.encodeEffect(Ve)(e);if(o===`darwin`){if((yield*d([`-q`,`-i`],`add-generic-password -U -a orbit -s ${We(He)} -w ${We(n)}\n`)).exitCode!==0)return yield*I.make({message:`Could not save macOS Keychain credentials.`})}else{yield*u;let e=yield*t.makeTempDirectoryScoped({directory:s,prefix:`session-`}),r=i.join(e,`session.json`);yield*t.writeFileString(r,n,{mode:384}),yield*t.rename(r,c)}},n.scoped,n.mapError(()=>I.make({message:`Could not persist OAuth credentials. Run tlo-mcp login again.`}))),m=n.fn(`SessionStore.clear`)(function*(){if(o===`darwin`){let e=yield*d([`delete-generic-password`,`-a`,`orbit`,`-s`,He]);if(e.exitCode!==0&&e.exitCode!==44)return yield*I.make({message:`Could not remove Keychain credentials.`})}else yield*t.remove(c,{force:!0})},n.mapError(()=>I.make({message:`Could not remove OAuth credentials.`}))),h=n.fn(`SessionStore.lock`)(function*(){yield*u,yield*n.acquireRelease(t.writeFileString(l,String(process.pid),{flag:`wx`,mode:384}).pipe(n.catchReason(`PlatformError`,`AlreadyExists`,()=>I.make({message:`OAuth credentials locked. Close other login/refresh processes. After a crash only, remove ${l}.`}))),()=>t.remove(l).pipe(n.orDie))},n.mapError(e=>r.is(I)(e)?e:I.make({message:`Could not acquire OAuth credential lock. Check directory permissions.`})));return Ue.of({load:f(),save:p,clear:m(),lock:h()})}))}const Ke=Ge(),qe=r.Struct({client_id:r.String.check(r.isMinLength(1)),client_secret:r.String.check(r.isMinLength(1)).pipe(r.RedactedFromValue,r.optionalKey)});var L=class extends l.Service()(`@2digits/tlo-mcp/oauth/OrbitAuth`){};const Je=p.effect(L,n.gen(function*(){let e=yield*Ue,t=yield*u.Crypto,i=yield*_.HttpClient,a=yield*w.ChildProcessSpawner,l=yield*ee.make(1),p=yield*n.fn(`OrbitAuth.discover`)(function*(){let e=yield*_.withScope(i).get(`${Ne}/.well-known/oauth-authorization-server`);if(e.status!==200)return yield*I.make({message:`Could not discover Orbit OAuth endpoints.`});let t=yield*e.json.pipe(n.flatMap(r.decodeUnknownEffect(o.Metadata)));return t.issuer!==`https://api.orbit.teamleader.eu`||t.authorization_endpoint!==`https://api.orbit.teamleader.eu/oauth2/Authorize`||t.token_endpoint!==`https://api.orbit.teamleader.eu/oauth2/access_token`?yield*I.make({message:`Unexpected Orbit endpoints; refusing to send credentials.`}):t},n.scoped,n.timeout(`15 seconds`),n.mapError(()=>I.make({message:`Invalid or unavailable Orbit OAuth metadata.`})))().pipe(n.cachedWithTTL(e=>f.isSuccess(e)?1/0:0)),v=n.fn(`OrbitAuth.accessToken`)(function*(){let t=yield*e.load;if(t===void 0)return yield*I.make({message:`Not authenticated. Run tlo-mcp login first.`});if(!Le(t,yield*c.currentTimeMillis))return t.accessToken;if(t.refreshToken===void 0)return t.expiresAt===void 0?t.accessToken:yield*I.make({message:`Orbit session expired without a refresh token. Run tlo-mcp login.`});let r=yield*p,i=Ie(t,yield*(yield*o.make({metadata:r,clientId:t.clientId,authentication:Re(t),timeoutMs:15e3})).refreshGrant({refreshToken:t.refreshToken,resources:[F]}).pipe(n.flatMap(o.tokens)),yield*c.currentTimeMillis);return yield*e.save(i),i.accessToken}),te=n.fn(`OrbitAuth.register`)(function*(e){let t=yield*y.post(`${Ne}/oauth2/register`).pipe(y.bodyJson({client_name:`@2digits/tlo-mcp`,redirect_uris:[e],response_types:[`code`],grant_types:[`authorization_code`,`refresh_token`],token_endpoint_auth_method:`none`}),n.flatMap(_.withScope(i).execute));return t.status!==200&&t.status!==201?yield*I.make({message:`Orbit client registration failed (HTTP ${t.status}).`}):yield*t.json.pipe(n.flatMap(r.decodeUnknownEffect(qe)))},n.scoped,n.timeout(`15 seconds`)),ne=n.fn(`OrbitAuth.openBrowser`)(function*(e){return yield*(yield*m.value(process.platform).pipe(m.when(`darwin`,()=>C.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`open ${h.value(e)}`),m.when(`win32`,()=>C.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`rundll32 url.dll,FileProtocolHandler ${h.value(e)}`),m.orElse(()=>C.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`xdg-open ${h.value(e)}`))).exitCode},n.scoped,n.timeout(`5 seconds`),n.filterOrFail(d.equals(w.ExitCode(0)),()=>I.make({message:`Could not open browser.`})),(e,t)=>e.pipe(n.catch(()=>n.sync(()=>{process.stderr.write(`Open this URL in your browser:\n${h.value(t)}\n`)})))),b=n.fn(`OrbitAuth.login`)(function*(){let t=yield*p,r=yield*s.random(),i=yield*s.make(),a=yield*r.pipe(h.value,Be),l=yield*te(a.redirectUri),u={clientId:l.client_id,...l.client_secret===void 0?{}:{clientSecret:l.client_secret}},d=yield*o.make({metadata:t,...u,authentication:Re(u),timeoutMs:15e3}),f=yield*d.authorizationUrl({redirectUri:a.redirectUri,scopes:[],state:r,codeChallenge:i.challenge,resources:[F]});yield*ne(f);let m=yield*a.code,ee=yield*d.codeGrant({code:h.make(m),redirectUri:a.redirectUri,pkceVerifier:i.verifier,resources:[F]}).pipe(n.flatMap(o.tokens));yield*e.save(Ie(u,ee,yield*c.currentTimeMillis)),yield*n.sync(()=>{process.stderr.write(`Orbit login complete.
3
- `)})}),x=n.fn(`OrbitAuth.locked`)(function*(t){return yield*e.lock,yield*t},n.scoped,l.withPermit,n.provideService(_.HttpClient,i),n.provideService(u.Crypto,t),n.provideService(w.ChildProcessSpawner,a),n.provideService(g.RequestInit,{redirect:`error`}),n.mapError(e=>r.is(I)(e)?e:I.make({message:`Orbit authentication unavailable. Check connectivity or run tlo-mcp login again.`})));return L.of({accessToken:x(v()),login:x(b()),logout:x(e.clear)})})).pipe(p.provide(Ke),p.provide(g.layer)),Ye=8e3,Xe=(e,t)=>t===`idempotent`?e===429||e>=500&&e<=599:[429,503,521,522,523,525].includes(e),Ze=(e,t)=>{let n=t?.trim();if(n&&/^\d+$/.test(n)){let e=Math.min(Number(n)*1e3,Ye);return[e,e]}let r=Math.min(250*2**Math.min(e,16),Ye);return[r/2,r]},Qe={520:`the server returned an unknown error`,521:`the server refused the connection`,522:`the connection to the server timed out`,523:`the server is unreachable`,524:`the server did not answer in time`,525:`the SSL handshake with the server failed`,526:`the server's certificate is invalid`,527:`the connection to the server failed`},$e=e=>Qe[e],et=(e,t)=>{let n=$e(e);if(n)return`Cloudflare ${e}: ${n}`;let r=t.trim();return r?Array.from(r).slice(0,200).join(``):`HTTP ${e}`},tt=k.String.pipe(k.brand(`CSR.Raw`)),nt=k.String.pipe(k.brand(`Hostname`));function rt(){return(e,t)=>{class n{static makeUnsafe(e){return e}}return Object.setPrototypeOf(n,t),n}}var it=class extends rt()(`CertificateID`,k.String){};k.String.pipe(k.brand(`ChallengeToken`));var at=class extends k.Class(`Certificate/StateChallenge`)({type:k.Literal(`challenge`),token:k.String,key:k.String}){},ot=class extends k.Class(`Certificate/StateIssuing`)({type:k.Literal(`issuing`)}){},st=class extends k.Class(`Certificate/StateReady`)({type:k.Literal(`ready`),certificate:k.String,chain:k.String,expiry:k.String}){},ct=class extends k.Class(`Certificate/StateFailed`)({type:k.Literal(`failed`),reason:k.String}){};const lt=k.Union([at,ot,st,ct]);var ut=class extends k.Class(`Certificate`)({id:it,state:lt}){},R=class extends rt()(`TunnelID`,k.String){};const z=k.String.pipe(k.brand(`TunnelToken`));k.TaggedError()(`NotFound`,{tunnelID:R}),k.TaggedError()(`NoCertificate`,{tunnelID:R}),k.TaggedError()(`CertificateNotReady`,{tunnelID:R,currentState:k.String}),k.TaggedError()(`InvalidHostname`,{provided:nt,expected:nt});const dt=k.Literals([`offline`,`online`]);var ft=class extends k.Class(`Tunnel/Info`)({id:R,hostname:nt,state:dt,certificateID:it.pipe(k.optional)}){},pt=`0.5.1`,mt=class extends k.TaggedError()(`InvalidRequestError`,{message:k.String},{httpApiStatus:400}){},ht=class extends k.TaggedError()(`UnauthorizedError`,{message:k.String},{httpApiStatus:401}){},B=class extends k.TaggedError()(`TunnelNotFoundError`,{tunnelID:k.String,message:k.String},{httpApiStatus:404}){},gt=class extends k.TaggedError()(`CertificateNotFoundError`,{tunnelID:k.String,message:k.String},{httpApiStatus:404}){},_t=class extends k.TaggedError()(`InvalidHostnameError`,{provided:k.String,expected:k.String,message:k.String},{httpApiStatus:400}){},vt=class extends k.TaggedError()(`HostnameUnavailableError`,{name:k.String,message:k.String},{httpApiStatus:409}){},yt=class extends k.TaggedError()(`CertificateInProgressError`,{tunnelID:k.String,message:k.String},{httpApiStatus:409}){},V=class extends k.TaggedError()(`ServiceUnavailableError`,{message:k.String},{httpApiStatus:503}){};me.Service()(`@opentunnel/protocol/OpenTunnelAuthorizationToken`);var H=class extends xe.Service()(`@opentunnel/protocol/OpenTunnelAuthorization`,{security:{bearer:Ce.bearer},error:ht}){};const bt=k.Struct({name:k.String.pipe(k.optional)}),xt=k.Struct({tunnel:ft,token:z}).pipe(Se.status(201)),St=be.make(`tunnel`).add(A.post(`tunnel.create`,`/api/tunnel`,{payload:bt,success:xt,error:[mt,vt,V]})).add(A.get(`tunnel.get`,`/api/tunnel/:id`,{params:k.Struct({id:R}),success:ft,error:[B,V]}).middleware(H)).add(A.post(`tunnel.bindCertificate`,`/api/tunnel/:id/certificate`,{params:k.Struct({id:R}),payload:k.Struct({csr:tt}),success:ut.pipe(Se.status(202)),error:[mt,_t,B,yt,V]}).middleware(H)).add(A.get(`tunnel.getCertificate`,`/api/tunnel/:id/certificate`,{params:k.Struct({id:R}),success:ut,error:[B,gt,V]}).middleware(H)).add(A.delete(`tunnel.remove`,`/api/tunnel/:id`,{params:k.Struct({id:R}),success:Se.NoContent,error:[B,V]}).middleware(H)).add(A.get(`tunnel.connect`,`/api/tunnel/:id/connect`,{params:k.Struct({id:R}),success:k.Boolean,error:[B]}).annotateMerge(we.annotations({summary:`Connect a bridge WebSocket`,description:`This endpoint upgrades to the opentunnel WebSocket protocol.`}))).annotateMerge(we.annotations({title:`tunnel`})),Ct=ve.make(`opentunnel`).add(St).annotateMerge(we.annotations({title:`OpenTunnel API`,version:`0.1.0`,description:`Create and manage blind TLS tunnels.`})),wt=`opentunnel-sdk/${pt}`,Tt=/* @__PURE__ */ new Set([`ConnectionRefused`,`ECONNREFUSED`,`ENOTFOUND`,`EAI_AGAIN`,`FailedToOpenSocket`,`UND_ERR_CONNECT_TIMEOUT`]),Et=e=>{if(typeof e!=`object`||!e)return;let{code:t,cause:n}=e;return typeof t==`string`?t:Et(n)},Dt=e=>e.method===`GET`||e.method===`POST`&&e.url.endsWith(`/certificate`)?`idempotent`:`once`,Ot=(e,t)=>v.isHttpClientError(e)&&e.reason._tag===`TransportError`&&(t===`idempotent`||Tt.has(Et(e.reason.cause)??``)),kt=e=>_.transform(e,(e,t)=>{let n=Dt(t),r=(e,t)=>{let[n,r]=Ze(e,t);return D.sleep(n+Math.random()*(r-n))},i=t=>D.matchEffect(e,{onFailure:e=>t<6&&Ot(e,n)?D.andThen(r(t),()=>i(t+1)):D.fail(e),onSuccess:e=>t<6&&Xe(e.status,n)?D.andThen(r(t,e.headers[`retry-after`]),()=>i(t+1)):D.succeed(e)});return i(0)});var At=class e extends me.Service()(`@opentunnel/client/OpenTunnelApiClient`){static layer(t){return he.effect(e,D.gen(function*(){let e=(yield*_.HttpClient).pipe(_.mapRequest(y.setHeader(`user-agent`,wt)),kt);return{client:yield*ye.makeWith(Ct,{baseUrl:t.api,httpClient:e}),authorized:n=>ye.makeWith(Ct,{baseUrl:t.api,httpClient:e.pipe(_.mapRequest(y.bearerToken(n)))})}})).pipe(he.provide(g.layer))}};const U=(e,...t)=>{let n=jt(t),r=n.length<128?[n.length]:(()=>{let e=[];for(let t=n.length;t>0;t>>=8)e.unshift(t&255);return[128|e.length,...e]})();return jt([new Uint8Array([e,...r]),n])},jt=e=>{let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t},W=(...e)=>U(48,...e),Mt=(...e)=>U(49,...e),Nt=e=>U(12,new TextEncoder().encode(e)),Pt=e=>U(130,new TextEncoder().encode(e)),G=e=>{let[t,n,...r]=e.split(`.`).map(Number),i=[t*40+n];for(let e of r){let t=[e&127];for(let n=e>>7;n>0;n>>=7)t.unshift(n&127|128);i.push(...t)}return U(6,new Uint8Array(i))},Ft=e=>{let t=0;for(;t<e.length-1&&e[t]===0;)t++;let n=e.subarray(t);return U(2,n[0]&128?jt([new Uint8Array([0]),n]):n)};async function It(e,t){let n=new Uint8Array(await crypto.subtle.exportKey(`spki`,t.publicKey)),r=W(U(2,new Uint8Array([0])),W(Mt(W(G(`2.5.4.3`),Nt(e)))),n,U(160,W(G(`1.2.840.113549.1.9.14`),Mt(W(W(G(`2.5.29.17`),U(4,W(Pt(e),Pt(`*.${e}`))))))))),i=new Uint8Array(await crypto.subtle.sign({name:`ECDSA`,hash:`SHA-256`},t.privateKey,r)),a=W(Ft(i.subarray(0,32)),Ft(i.subarray(32))),o=W(r,W(G(`1.2.840.10045.4.3.2`)),U(3,new Uint8Array([0]),a));return`-----BEGIN CERTIFICATE REQUEST-----\n${Buffer.from(o).toString(`base64`).replace(/.{1,64}/g,`$&
4
- `)}-----END CERTIFICATE REQUEST-----`}var Lt=class extends k.TaggedError()(`OpenTunnelClientError`,{message:k.String,cause:k.optional(k.Defect())}){},K=class extends k.TaggedError()(`OpenTunnelStorageError`,{message:k.String,cause:k.optional(k.Defect())}){};function Rt(e={}){let t=e.env??process.env,n=e.home??ue.homedir(),r=t.XDG_DATA_HOME??M.join(n,`.local`,`share`),i=M.join(r,`opentunnel`),a=e=>M.join(i,e),o=e=>D.tryPromise({try:()=>j.readFile(e,`utf8`),catch:e=>e}).pipe(D.catch(t=>D.gen(function*(){if(!(typeof t==`object`&&t&&`code`in t&&t.code===`ENOENT`))return yield*new K({message:`Failed to read ${e}`,cause:t})}))),s=D.fn(`OpenTunnelStorage.profiles`)(function*(){return(yield*D.tryPromise({try:()=>j.readdir(i,{withFileTypes:!0}),catch:e=>e}).pipe(D.catch(e=>D.gen(function*(){return typeof e==`object`&&e&&`code`in e&&e.code===`ENOENT`?[]:yield*new K({message:`Failed to list ${i}`,cause:e})})))).filter(e=>e.isDirectory()).map(e=>e.name).sort()}),c=D.fn(`OpenTunnelStorage.load`)(function*(e){let t=a(e),n=yield*o(M.join(t,`tunnel.json`));if(!n)return;let[r,i,s,c]=yield*D.all([o(M.join(t,`token`)),o(M.join(t,`private-key.pem`)),o(M.join(t,`certificate.pem`)),o(M.join(t,`chain.pem`))]);if(!r||!i||!s||c===void 0)return yield*new K({message:`Incomplete tunnel identity for ${e}`});let l=JSON.parse(n);return{id:l.id,hostname:l.hostname,token:r.trim(),privateKey:i,certificate:s,chain:c,certificateExpiry:new Date(l.certificateExpiry)}}),l=D.fn(`OpenTunnelStorage.loadPending`)(function*(e){let t=a(e),n=yield*o(M.join(t,`pending.json`));if(!n)return;let[r,i]=yield*D.all([o(M.join(t,`token`)),o(M.join(t,`private-key.pem`))]);if(!r||!i)return yield*new K({message:`Incomplete pending tunnel identity for ${e}`});let s=JSON.parse(n);return{id:s.id,hostname:s.hostname,token:r.trim(),privateKey:i,csr:s.csr}});return{profiles:()=>s(),load:e=>c(e),loadPending:e=>l(e),savePending:(e,t)=>D.tryPromise({try:async()=>{let n=a(e);await j.mkdir(n,{recursive:!0,mode:448}),await Promise.all([j.writeFile(M.join(n,`pending.json`),JSON.stringify({id:t.id,hostname:t.hostname,csr:t.csr},null,2),{mode:384}),j.writeFile(M.join(n,`token`),`${t.token}\n`,{mode:384}),j.writeFile(M.join(n,`private-key.pem`),t.privateKey,{mode:384})])},catch:t=>new K({message:`Failed to save pending tunnel identity for ${e}`,cause:t})}),save:(e,t)=>D.tryPromise({try:async()=>{let n=a(e);await j.mkdir(n,{recursive:!0,mode:448}),await Promise.all([j.writeFile(M.join(n,`tunnel.json`),JSON.stringify({id:t.id,hostname:t.hostname,certificateExpiry:t.certificateExpiry.toISOString()},null,2),{mode:384}),j.writeFile(M.join(n,`token`),`${t.token}\n`,{mode:384}),j.writeFile(M.join(n,`private-key.pem`),t.privateKey,{mode:384}),j.writeFile(M.join(n,`certificate.pem`),t.certificate,{mode:384}),j.writeFile(M.join(n,`chain.pem`),t.chain,{mode:384})]),await j.rm(M.join(n,`pending.json`),{force:!0})},catch:t=>new K({message:`Failed to save tunnel identity for ${e}`,cause:t})}),remove:e=>D.tryPromise({try:()=>j.rm(a(e),{recursive:!0,force:!0}),catch:t=>new K({message:`Failed to remove tunnel identity for ${e}`,cause:t})}),list:()=>s().pipe(D.flatMap(e=>D.forEach(e,e=>c(e).pipe(D.map(t=>t?{profile:e,tunnel:t}:void 0)))),D.map(e=>e.filter(e=>e!==void 0)))}}const zt=k.Int.check(k.isBetween({minimum:0,maximum:4294967295}));var Bt=class extends k.Class(`BridgeProtocol/ClientInfo`)({version:k.String,max_conns:k.Number}){},Vt=class extends k.Class(`BridgeProtocol/AttachMessage`)({type:k.Literal(`attach`),token:k.String,transport:k.Literal(`ws`),client:Bt,routes:k.Array(k.String).pipe(k.optional)}){},Ht=class extends k.Class(`BridgeProtocol/AttachedMessage`)({type:k.Literal(`attached`),session:k.String,routes:k.Array(k.String),heartbeat_ms:k.Number,idle_timeout_ms:k.Number}){},Ut=class extends k.Class(`BridgeProtocol/AttachErrorMessage`)({type:k.Literal(`attach_error`),code:k.String}){},Wt=class extends k.Class(`BridgeProtocol/PingMessage`)({type:k.Literal(`ping`),time_sent:k.Number}){},Gt=class extends k.Class(`BridgeProtocol/PongMessage`)({type:k.Literal(`pong`),time_sent:k.Number}){},Kt=class extends k.Class(`BridgeProtocol/DrainMessage`)({type:k.Literal(`drain`),reason:k.String}){},qt=class extends k.Class(`BridgeProtocol/OpenMessage`)({type:k.Literal(`open`),conn:zt,peer:k.String,sni:k.String,alpn:k.String}){},Jt=class extends k.Class(`BridgeProtocol/EndMessage`)({type:k.Literal(`end`),conn:zt}){},Yt=class extends k.Class(`BridgeProtocol/ResetMessage`)({type:k.Literal(`reset`),conn:zt,code:k.String}){};k.Union([Vt,Wt,Gt,Kt,Jt,Yt]);const Xt=k.Union([Ht,Ut,Wt,Gt,Kt,qt,Jt,Yt]),Zt=k.decodeUnknownOption(Xt);function Qt(e){try{return Zt(JSON.parse(e))}catch{return ge.none()}}k.Union([Vt,Ht,Ut,Wt,Gt,Kt,qt,Jt,Yt]);const q={BAD_ATTACH:`bad_attach`,BAD_TOKEN:`bad_token`,TUNNEL_NOT_BOUND:`tunnel_not_bound`,CERT_NOT_READY:`cert_not_ready`,REPLACED:`replaced`,UPSTREAM_CONNECT_FAILED:`upstream_connect_failed`,UPSTREAM_IO_ERROR:`upstream_io_error`,CLIENT_IO_ERROR:`client_io_error`,IDLE_TIMEOUT:`idle_timeout`,ROUTE_CONFLICT:`route_conflict`,INVALID_ROUTE:`invalid_route`,UNKNOWN_ROUTE:`unknown_route`,TOO_MANY_CONNECTIONS:`too_many_connections`},$t={HEARTBEAT_MS:15e3,IDLE_TIMEOUT_MS:45e3,CONNECT_TIMEOUT_MS:1e4,RECONNECT_BACKOFF_MIN_MS:250,RECONNECT_BACKOFF_MAX_MS:3e4},J={CONN_ID_SIZE:4,MAX_PAYLOAD_SIZE:32768};function en(e){return e.length<J.CONN_ID_SIZE?null:{conn:new DataView(e.buffer,e.byteOffset,e.byteLength).getUint32(0,!1),payload:e.slice(J.CONN_ID_SIZE)}}function tn(e,t){let n=new Uint8Array(J.CONN_ID_SIZE+t.length);return new DataView(n.buffer).setUint32(0,e,!1),n.set(t,J.CONN_ID_SIZE),n}const nn=/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/,rn=e=>e===`@`||nn.test(e),an=(e,t)=>{let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return`@`;let i=`.${r}`;if(!n.endsWith(i))return;let a=n.slice(0,-i.length);return nn.test(a)?a:void 0},on=e=>{let t=e.lastIndexOf(`:`);if(t===-1)return;let n=e.slice(t+1);if(!/^\d{1,5}$/.test(n))return;let r=Number(n);if(r<1||r>65535)return;let i=e.slice(0,t);if(i.startsWith(`[`)){if(!i.endsWith(`]`))return;i=i.slice(1,-1)}if(i&&/^[A-Za-z0-9.\-:_]+$/.test(i))return{host:i,port:r}},sn=e=>e===`v1`||e===`v2`,cn=Uint8Array.of(13,10,13,10,0,13,10,81,85,73,84,10),ln=e=>{let t=e.split(`.`);if(t.length!==4)return;let n=/* @__PURE__ */ new Uint8Array(4);for(let[e,r]of t.entries()){if(!/^(?:0|[1-9][0-9]{0,2})$/.test(r))return;let t=Number(r);if(t>255)return;n[e]=t}return n},un=(e,t)=>{if(e===``)return[];let n=[],r=e.split(`:`);for(let[e,i]of r.entries()){if(t&&e===r.length-1&&i.includes(`.`)){let e=ln(i);if(!e)return;n.push(e[0]<<8|e[1],e[2]<<8|e[3]);continue}if(!/^[0-9A-Fa-f]{1,4}$/.test(i))return;n.push(Number.parseInt(i,16))}return n},dn=e=>{let t=e.indexOf(`::`),n;if(t===-1){if(n=un(e,!0),n?.length!==8)return}else{if(e.indexOf(`::`,t+1)!==-1)return;let r=un(e.slice(0,t),!1),i=un(e.slice(t+2),!0);if(!r||!i||r.length+i.length>7)return;n=[...r,...Array(8-r.length-i.length).fill(0),...i]}let r=/* @__PURE__ */ new Uint8Array(16);for(let[e,t]of n.entries())r[e*2]=t>>8,r[e*2+1]=t&255;return r},fn=e=>{if(!/^[0-9]{1,5}$/.test(e))return;let t=Number(e);return t<=65535?t:void 0},pn=e=>e.subarray(0,10).every(e=>e===0)&&e[10]===255&&e[11]===255,mn=(e,t)=>e.length===16&&pn(e)?{family:4,bytes:e.slice(12),port:t}:e.length===4?{family:4,bytes:e,port:t}:{family:6,bytes:e,port:t},hn=e=>{let t=ln(e)??dn(e);if(t)return mn(t,0);if(e.startsWith(`[`)){let t=e.indexOf(`]:`);if(t===-1)return;let n=dn(e.slice(1,t)),r=fn(e.slice(t+2));return n&&r!==void 0?mn(n,r):void 0}let n=e.lastIndexOf(`:`);if(n===-1)return;let r=ln(e.slice(0,n)),i=fn(e.slice(n+1));return r&&i!==void 0?mn(r,i):void 0},gn=e=>{if(e.family===4)return[...e.bytes].join(`.`);let t=Array.from({length:8},(t,n)=>e.bytes[n*2]<<8|e.bytes[n*2+1]),n=-1,r=1;for(let e=0;e<8;){if(t[e]!==0){e++;continue}let i=e;for(;i<8&&t[i]===0;)i++;i-e>r&&(n=e,r=i-e),e=i}let i=e=>e.map(e=>e.toString(16)).join(`:`);return n===-1?i(t):`${i(t.slice(0,n))}::${i(t.slice(n+r))}`},_n=new TextEncoder,vn=(e,t,n)=>{let r=hn(t);return e===`v1`?yn(r):bn(r,n)},yn=e=>{if(!e)return _n.encode(`PROXY UNKNOWN\r
5
- `);let[t,n]=e.family===4?[`TCP4`,`0.0.0.0`]:[`TCP6`,`::`];return _n.encode(`PROXY ${t} ${gn(e)} ${n} ${e.port} 443\r\n`)},bn=(e,t)=>{let n=_n.encode(t),r=n.length>0&&n.length<=255?3+n.length:0,i=(e===void 0?0:e.family===4?12:36)+r,a=new Uint8Array(16+i),o=new DataView(a.buffer);a.set(cn,0),a[12]=e?33:32,a[13]=e===void 0?0:e.family===4?17:33,o.setUint16(14,i);let s=16;if(e){let t=e.bytes.length;a.set(e.bytes,s),s+=t*2,o.setUint16(s,e.port),o.setUint16(s+2,443),s+=4}return r>0&&(a[s]=2,o.setUint16(s+1,n.length),a.set(n,s+3)),a},xn=WebSocket;var Sn=class extends Error{code;constructor(e){super(`Bridge attach failed: ${e}`),this.code=e}};const Cn=/* @__PURE__ */ new Set([q.BAD_TOKEN,q.CERT_NOT_READY]),wn=/* @__PURE__ */ new Set([`target`,`proxyProtocol`]),Tn=(e,t)=>{if(!rn(e))throw Error(`Invalid route name '${e}'`);let n=t,r;if(typeof t==`object`&&t&&!Array.isArray(t)){for(let n of Object.keys(t))if(!wn.has(n))throw Error(`Unknown option '${n}' for route '${e}': expected target or proxyProtocol`);let i=t;if(i.target===void 0)throw Error(`Route '${e}' has no target`);if(n=i.target,i.proxyProtocol!==void 0){if(!sn(i.proxyProtocol))throw Error(`Invalid proxyProtocol '${String(i.proxyProtocol)}' for route '${e}': use "v1" or "v2"`);r=i.proxyProtocol}}let i=typeof n==`string`?on(n):void 0;if(!i)throw Error(`Invalid target '${String(n)}' for route '${e}': use host:port`);return r===void 0?{target:i}:{target:i,proxyProtocol:r}},En=e=>{for(let[t,n]of Object.entries(e))Tn(t,n)},Dn=e=>new Map(Object.entries(e).map(([e,t])=>[e,Tn(e,t)])),On=e=>De.createSecureContext({key:e.privateKey,cert:`${e.certificate}\n${e.chain}`}),kn=(e,t)=>{let n=Object.keys(e).sort(),r=Object.keys(t).sort();return n.length===r.length&&n.every((e,t)=>e===r[t])},An=e=>{let t=Math.min($t.RECONNECT_BACKOFF_MIN_MS*2**Math.min(e,16),$t.RECONNECT_BACKOFF_MAX_MS);return Math.round(t/2+t/2*Math.random())};var jn=class{options;routes;resolved;identity;secureContext;refreshTimer;session;shutdown=!1;wake;status;ready;closed;constructor(e){this.options=e,this.resolved=Dn(e.routes),this.routes={...e.routes},this.identity=e.identity,this.secureContext=On(e.identity),this.status={state:`connecting`,hostname:e.identity.hostname,routes:this.routes,connections:0};let t,n;this.ready=new Promise((e,r)=>{t=e,n=r}),this.closed=this.run(t).then(()=>t(),e=>{throw n(e),e}),this.ready.catch(()=>void 0),this.refreshCertificate()}async refreshCertificate(){if(this.shutdown)return;try{let e=new URL(`/api/tunnel/${encodeURIComponent(this.identity.id)}/certificate`,this.options.api),t=await fetch(e,{headers:{authorization:`Bearer ${this.identity.token}`}}),n=(t.ok?await t.json():void 0)?.state;if(n?.type===`ready`&&n.certificate&&n.expiry&&n.certificate!==this.identity.certificate){let e={...this.identity,certificate:n.certificate,chain:n.chain??``,certificateExpiry:new Date(n.expiry)};this.secureContext=On(e),this.identity=e,this.options.onRenewed?.(e),this.emit({type:`certificate-renewed`,expiry:n.expiry})}}catch{}if(this.shutdown)return;let e=this.identity.certificateExpiry.getTime()-Date.now()<2592e6;this.refreshTimer=setTimeout(()=>void this.refreshCertificate(),e?6e4:432e5),this.refreshTimer.unref?.()}getStatus(){return{...this.status,routes:{...this.status.routes}}}setRoutes(e){let t=Dn(e),n=this.routes;this.resolved=t,this.routes={...e},this.status.routes=this.routes,kn(n,this.routes)||this.session?.end({type:`routes-changed`}),this.wake?.()}close(){return this.shutdown=!0,clearTimeout(this.refreshTimer),this.session?.end({type:`shutdown`}),this.wake?.(),this.closed.catch(()=>void 0)}emit(e){this.options.onEvent(e)}sleep(e){return new Promise(t=>{let n=setTimeout(r,e);function r(){clearTimeout(n),t()}this.wake=r}).finally(()=>{this.wake=void 0})}async run(e){let t=0;try{for(;!this.shutdown;){if(Object.keys(this.routes).length===0){this.status.state=`waiting-routes`,await this.sleep(2**31-1);continue}this.status.state=`connecting`,this.emit({type:`connecting`,attempt:t});let n=new Mn(this.options,this.routes,()=>this.secureContext,{emit:e=>this.emit(e),route:e=>this.resolved.get(e),attached:t=>{this.status.state=`connected`,this.status.session=t,this.status.lastError=void 0,this.status.connectedAt=Date.now(),e()},connections:e=>{this.status.connections=e}});this.session=n;let r=await n.run();if(this.session=void 0,this.status.session=void 0,this.status.connections=0,r.type===`shutdown`)break;if(r.type===`fatal`)throw r.error;if(r.type===`routes-changed`){this.emit({type:`disconnected`,reason:`routes changed`}),t=0;continue}r.attached&&(t=0),this.emit({type:`disconnected`,reason:r.error});let i=An(t);t++,this.status.state=`reconnecting`,this.status.lastError=r.error,this.emit({type:`reconnecting`,attempt:t,delayMs:i}),await this.sleep(i)}this.status.state=`stopped`,this.emit({type:`stopped`})}catch(e){throw this.status.state=`stopped`,this.status.lastError=e instanceof Error?e.message:String(e),this.emit({type:`stopped`,error:this.status.lastError}),e}}},Mn=class{options;routes;secureContext;hooks;socket;finish;channels=/* @__PURE__ */ new Map;drainWaiters=[];drainTimer;heartbeat;attachTimeout;lastReceived=Date.now();attached=!1;constructor(e,t,n,r){this.options=e,this.routes=t,this.secureContext=n,this.hooks=r}run(){return new Promise(e=>{this.finish=e;let t=new URL(`/api/tunnel/${encodeURIComponent(this.options.identity.id)}/connect`,this.options.api);t.protocol=t.protocol===`https:`?`wss:`:`ws:`;let n=new xn(t,{protocols:[`opentunnel`],headers:{"user-agent":wt}});n.binaryType=`arraybuffer`,this.socket=n,this.attachTimeout=setTimeout(()=>this.end({type:`retry`,error:`bridge attach timed out`,attached:!1}),$t.CONNECT_TIMEOUT_MS),n.addEventListener(`open`,()=>{this.sendControl({type:`attach`,token:this.options.identity.token,transport:`ws`,routes:Object.keys(this.routes).sort(),client:{version:`0.1.0`,max_conns:256}})}),n.addEventListener(`message`,e=>{this.lastReceived=Date.now(),typeof e.data==`string`?this.onControl(e.data):this.onData(new Uint8Array(e.data))}),n.addEventListener(`close`,e=>{let t=`${e.code} ${e.reason}`.trim();this.end({type:`retry`,error:`bridge closed: ${t}`,attached:this.attached})})})}end(e){let t=this.finish;if(t){this.finish=void 0,clearTimeout(this.attachTimeout),clearInterval(this.heartbeat),clearInterval(this.drainTimer);for(let e of this.drainWaiters.splice(0))e();for(let e of[...this.channels.values()])e.destroy(`bridge disconnected`);this.socket&&this.socket.readyState<=WebSocket.OPEN&&this.socket.close(1e3),t(e)}}onControl(e){let t=Qt(e);if(ge.isNone(t))return;let n=t.value;if(!this.attached){if(n.type===`attached`){this.attached=!0,clearTimeout(this.attachTimeout);let e=Math.max(n.heartbeat_ms,1e3),t=Math.max(n.idle_timeout_ms,5e3);this.heartbeat=setInterval(()=>{if(Date.now()-this.lastReceived>t){this.end({type:`retry`,error:`bridge idle timeout`,attached:!0});return}this.sendControl({type:`ping`,time_sent:Date.now()})},e),this.hooks.attached(n.session),this.hooks.emit({type:`connected`,session:n.session,routes:[...n.routes]})}else n.type===`attach_error`&&this.end(Cn.has(n.code)?{type:`fatal`,error:new Sn(n.code)}:{type:`retry`,error:`attach failed: ${n.code}`,attached:!1});return}switch(n.type){case`open`:return this.open(n.conn,n.sni,n.peer);case`end`:return this.channels.get(n.conn)?.endInbound();case`reset`:return this.channels.get(n.conn)?.destroy(n.code,!1);case`ping`:return this.sendControl({type:`pong`,time_sent:n.time_sent});case`drain`:return this.end({type:`retry`,error:`server draining: ${n.reason}`,attached:!0})}}onData(e){let t=en(e);t&&this.channels.get(t.conn)?.push(t.payload)}open(e,t,n){let r=an(t,this.options.identity.hostname),i=r===void 0?void 0:this.hooks.route(r);if(r===void 0||!i){this.sendControl({type:`reset`,conn:e,code:q.UNKNOWN_ROUTE});return}if(this.channels.size>=256){this.sendControl({type:`reset`,conn:e,code:q.TOO_MANY_CONNECTIONS});return}let a=i.proxyProtocol===void 0?void 0:vn(i.proxyProtocol,n,t),o=new Nn(e,r,i.target,a,this.secureContext(),this);this.channels.set(e,o),this.hooks.connections(this.channels.size),this.hooks.emit({type:`connection-opened`,conn:e,route:r,peer:n})}closed(e,t){this.channels.get(e.conn)===e&&(this.channels.delete(e.conn),this.hooks.connections(this.channels.size),this.hooks.emit({type:`connection-closed`,conn:e.conn,route:e.route,...t===void 0?{}:{error:t}}))}sendControl(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify(e))}sendData(e,t,n){let r=this.socket;if(!this.finish||r?.readyState!==WebSocket.OPEN){n(/* @__PURE__ */ Error(`bridge closed`));return}for(let n=0;n<t.byteLength;n+=J.MAX_PAYLOAD_SIZE){let i=t.subarray(n,n+J.MAX_PAYLOAD_SIZE);r.send(tn(e,i))}if(r.bufferedAmount<1048576){n();return}this.drainWaiters.push(()=>n()),this.drainTimer??=setInterval(()=>{if(!(this.socket&&this.socket.bufferedAmount>262144&&this.finish)){clearInterval(this.drainTimer),this.drainTimer=void 0;for(let e of this.drainWaiters.splice(0))e()}},5)}},Nn=class{conn;route;session;raw;tls;upstream;done=!1;constructor(e,t,n,r,i,a){this.conn=e,this.route=t,this.session=a,this.raw=new Ee({allowHalfOpen:!0,read(){},write:(t,n,r)=>a.sendData(e,t,r),final:t=>{a.sendControl({type:`end`,conn:e}),t()}}),this.tls=new De.TLSSocket(this.raw,{isServer:!0,secureContext:i}),this.tls.on(`secure`,()=>{let e=Te.connect({host:n.host,port:n.port,allowHalfOpen:!0});this.upstream=e,e.setNoDelay(!0),r&&e.write(r),e.on(`error`,t=>this.destroy(e.connecting?q.UPSTREAM_CONNECT_FAILED:q.UPSTREAM_IO_ERROR,!0,t.message)),this.tls.pipe(e),e.pipe(this.tls)}),this.tls.on(`error`,e=>this.destroy(q.UPSTREAM_IO_ERROR,!0,e.message)),this.tls.on(`close`,()=>this.finish(void 0))}push(e){if(!this.done){if(this.raw.readableLength>8388608){this.destroy(q.UPSTREAM_IO_ERROR,!0,`local connection stalled`);return}this.raw.push(e)}}endInbound(){this.done||this.raw.push(null)}destroy(e,t=!0,n=e){this.done||(t&&this.session.sendControl({type:`reset`,conn:this.conn,code:e}),this.finish(n))}finish(e){this.done||(this.done=!0,this.upstream?.destroy(),this.tls.destroy(),this.raw.destroy(),this.session.closed(this,e))}};const Y=e=>e?.profile??`default`,Pn=e=>{if(v.isHttpClientError(e)){let t=e.response;if(!t)return e.message;let n=et(t.status,``);return n.startsWith(`HTTP `)?n:`${n} (HTTP ${t.status})`}if(typeof e==`object`&&e){let{_tag:t,message:n}=e;if(typeof t==`string`&&typeof n==`string`)return`${t}: ${n}`}return e instanceof Error?e.message:void 0},X=(e,t)=>{let n=Pn(t);return new Lt({message:n?`${e}: ${n}`:e,cause:t})},Fn=e=>v.isHttpClientError(e)&&(e.reason._tag===`TransportError`||e.response!==void 0&&Xe(e.response.status,`idempotent`)),In=e=>{let t=new Uint8Array(e),n=``;for(let e of t)n+=String.fromCharCode(e);let r=btoa(n);return`-----BEGIN PRIVATE KEY-----\n${r.match(/.{1,64}/g)?.join(`
6
- `)??r}\n-----END PRIVATE KEY-----\n`};var Ln=class e extends me.Service()(`@opentunnel/client/OpenTunnelClient`){static layer(t={}){let n=t.storage??Rt();return he.effect(e,D.gen(function*(){let e=yield*At,r=new URL(t.api??`https://opentunnel.xyz`),i=D.fn(`OpenTunnelClient.tunnel.get`)(function*(e){return yield*n.load(Y(e))}),a=D.fn(`OpenTunnelClient.tunnel.completePending`)(function*(t){let r=yield*e.authorized(z.make(t.pending.token));yield*D.sync(()=>t.onProgress?.(`requesting-certificate`)),yield*r.tunnel[`tunnel.bindCertificate`]({params:{id:R.make(t.pending.id)},payload:{csr:t.pending.csr}}).pipe(D.catchTag(`CertificateInProgressError`,()=>D.void),D.mapError(e=>X(`Failed to start certificate issuance`,e)));let i=yield*D.gen(function*(){for(;;){yield*D.sync(()=>t.onProgress?.(`waiting-certificate`));let e=yield*r.tunnel[`tunnel.getCertificate`]({params:{id:R.make(t.pending.id)}}).pipe(D.mapError(e=>X(`Failed to read certificate`,e)));if(e.state.type===`ready`)return e.state;if(e.state.type===`failed`)return yield*new Lt({message:`Certificate issuance failed: ${e.state.reason}`});yield*D.sleep(`2 seconds`)}}),a={id:t.pending.id,hostname:t.pending.hostname,token:t.pending.token,privateKey:t.pending.privateKey,certificate:i.certificate,chain:i.chain,certificateExpiry:new Date(i.expiry)};return yield*D.sync(()=>t.onProgress?.(`saving-identity`)),yield*n.save(t.profile,a),yield*D.sync(()=>t.onProgress?.(`ready`)),a}),o=D.fn(`OpenTunnelClient.tunnel.provision`)(function*(e){yield*D.sync(()=>e.onProgress?.(`generating-key`));let t=yield*D.tryPromise({try:()=>crypto.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},!0,[`sign`,`verify`]),catch:e=>X(`Failed to generate certificate key`,e)});yield*D.sync(()=>e.onProgress?.(`generating-csr`));let r=yield*D.tryPromise({try:()=>It(e.hostname,t),catch:e=>X(`Failed to generate certificate request`,e)}),i=yield*D.tryPromise({try:()=>crypto.subtle.exportKey(`pkcs8`,t.privateKey),catch:e=>X(`Failed to export certificate key`,e)}),o={id:String(e.id),hostname:e.hostname,token:e.token,privateKey:In(i),csr:r};return yield*n.savePending(e.profile,o),yield*a({profile:e.profile,pending:o,onProgress:e.onProgress})}),s=D.fn(`OpenTunnelClient.tunnel.create`)(function*(t){let r=Y(t);if(yield*n.load(r))return yield*new Lt({message:`Profile '${r}' already has a tunnel`});let i=yield*n.loadPending(r);if(i)return yield*D.sync(()=>t?.onProgress?.(`resuming-certificate`)),yield*a({profile:r,pending:i,onProgress:t?.onProgress});yield*D.sync(()=>t?.onProgress?.(`creating-tunnel`));let s=yield*e.client.tunnel[`tunnel.create`]({payload:{}}).pipe(D.mapError(e=>X(`Failed to create tunnel`,e)));return yield*o({profile:r,id:s.tunnel.id,hostname:String(s.tunnel.hostname),token:s.token,onProgress:t?.onProgress})}),c=D.fn(`OpenTunnelClient.tunnel.ensure`)(function*(t){let r=yield*i(t);if(!r)return yield*s(t);let a=yield*(yield*e.authorized(z.make(r.token))).tunnel[`tunnel.getCertificate`]({params:{id:R.make(r.id)}}).pipe(D.catchIf(e=>Fn(e)&&r.certificateExpiry.getTime()>Date.now(),()=>D.succeed(void 0)),D.mapError(e=>X(`Failed to read certificate`,e)));if(!a)return r;let c=a.state;if(c.type===`ready`&&c.certificate!==r.certificate){let e={...r,certificate:c.certificate,chain:c.chain,certificateExpiry:new Date(c.expiry)};return yield*n.save(Y(t),e),e}return c.type===`failed`?yield*o({profile:Y(t),id:R.make(r.id),hostname:r.hostname,token:z.make(r.token)}):r}),l=D.fn(`OpenTunnelClient.tunnel.pending`)(function*(e){let t=yield*n.loadPending(Y(e));return t?{id:t.id,hostname:t.hostname}:void 0}),u=D.fn(`OpenTunnelClient.tunnel.resume`)(function*(e){let t=Y(e),r=yield*n.loadPending(t);if(r)return yield*D.sync(()=>e?.onProgress?.(`resuming-certificate`)),yield*a({profile:t,pending:r,onProgress:e?.onProgress})});return{profile:{list:n.profiles},tunnel:{list:n.list,get:i,pending:l,resume:u,create:s,ensure:c,remove:D.fn(`OpenTunnelClient.tunnel.remove`)(function*(t){let r=Y(t),i=yield*n.load(r);i&&(yield*(yield*e.authorized(z.make(i.token))).tunnel[`tunnel.remove`]({params:{id:R.make(i.id)}}).pipe(D.mapError(e=>X(`Failed to remove tunnel`,e))),yield*n.remove(r))}),connect:D.fn(`OpenTunnelClient.tunnel.connect`)(function*(e){yield*D.try({try:()=>En(e.routes),catch:e=>X(e instanceof Error?e.message:`Invalid routes`,e)});let t=yield*c(e),i=yield*O.unbounded(),a=yield*D.acquireRelease(D.sync(()=>new jn({api:r,identity:t,routes:e.routes,onEvent:e=>{O.offerUnsafe(i,e),e.type===`stopped`&&O.endUnsafe(i)},onRenewed:t=>{D.runFork(n.save(Y(e),t).pipe(D.ignore))}})),e=>D.promise(()=>e.close()).pipe(D.andThen(O.end(i))));return yield*D.tryPromise({try:()=>a.ready,catch:e=>X(`Failed to connect tunnel`,e)}),{tunnel:t,events:_e.fromQueue(i),status:D.sync(()=>a.getStatus()),setRoutes:e=>D.try({try:()=>a.setRoutes(e),catch:e=>X(e instanceof Error?e.message:`Invalid routes`,e)}),closed:D.tryPromise({try:()=>a.closed,catch:e=>X(`Tunnel stopped`,e)}),close:D.promise(()=>a.close())}})}}})).pipe(he.provide(At.layer({api:t.api??`https://opentunnel.xyz`})))}};const Rn=r.Union([r.Struct({jsonrpc:r.Literal(`2.0`),id:P.RequestId,result:r.Unknown}),r.Struct({jsonrpc:r.Literal(`2.0`),id:r.NullOr(P.RequestId),error:P.McpErrorBase}),r.Struct({jsonrpc:r.Literal(`2.0`),method:r.String,params:r.optionalKey(r.Unknown)})]),zn=r.fromJsonString(Rn),Bn=N.v2025_06_18.protocolVersion;var Z=class extends r.TaggedError()(`OrbitMcpError`,{message:r.String}){},Q=class extends l.Service()(`@2digits/tlo-mcp/mcp/OrbitClient`){};const Vn=p.effect(Q,n.gen(function*(){let e=yield*L,t=(yield*_.HttpClient).pipe(_.withScope,_.transformResponse(n.flatMap(te.matchStatus({"2xx":e=>n.succeed(e),401:e=>Z.make({message:`Orbit HTTP ${e.status}: authorization rejected. Run tlo-mcp login; a 403 can also mean insufficient rights.`}),403:e=>Z.make({message:`Orbit HTTP ${e.status}: authorization rejected. Run tlo-mcp login; a 403 can also mean insufficient rights.`}),429:e=>Z.make({message:`Orbit rate limited. Retry after ${e.headers[`retry-after`]??`the server cooldown`}; request not replayed.`}),orElse:e=>Z.make({message:`Orbit HTTP ${e.status}. Request not replayed.`})})))),i=0,a,o=n.fn(`OrbitClient.send`)(function*(o,s,c=!1){let l=++i,u=yield*e.accessToken,d=yield*y.post(F).pipe(y.bearerToken(u),y.setHeader(`accept`,`application/json, text/event-stream`),y.setHeader(`mcp-protocol-version`,Bn),e=>a===void 0?e:y.setHeader(e,`mcp-session-id`,a),y.bodyJson({jsonrpc:`2.0`,...c?{}:{id:l},method:o,params:s})),f=yield*t.execute(d),p=f.headers[`mcp-session-id`];if(o===`initialize`&&(a=p),c)return;let m=f.headers[`content-type`]?.startsWith(`text/event-stream`)?yield*f.stream.pipe(E.decodeText(),E.pipeThroughChannel(Me.decode({maxEventSize:10485760})),E.filter(e=>e.event===`message`||e.event===``),E.map(je.get(`data`)),E.mapEffect(e=>r.decodeEffect(zn)(e)),E.filter(e=>`id`in e&&e.id===l),E.runHead,n.flatMap(e=>Ae.isSome(e)?n.succeed(e.value):Z.make({message:`Orbit stream ended before replying.`}))):yield*te.schemaBodyJson(Rn)(f);return!(`id`in m)||m.id!==l?yield*Z.make({message:`Unexpected Orbit response ID.`}):`error`in m?yield*Z.make({message:m.error.message}):m.result},n.timeout(`120 seconds`),n.scoped,n.provideService(g.RequestInit,{redirect:`error`}),n.mapError(e=>r.is(Z)(e)?e:Z.make({message:`Orbit connection or response failed. Request not replayed.`}))),s=yield*o(`initialize`,{protocolVersion:Bn,capabilities:{},clientInfo:{name:`@2digits/tlo-mcp`,version:`0.1.38`}}).pipe(n.flatMap(r.decodeUnknownEffect(P.InitializeResult)));if(s.protocolVersion!==Bn)return yield*Z.make({message:`Unsupported upstream MCP protocol: ${s.protocolVersion}.`});yield*o(`notifications/initialized`,{},!0);let c=/* @__PURE__ */ new Set,l=yield*E.paginate(void 0,n.fn(`OrbitClient.listToolsPage`)(function*(e){let t=yield*o(`tools/list`,e===void 0?{}:{cursor:e}).pipe(n.flatMap(r.decodeUnknownEffect(P.ListToolsResult)));if(t.nextCursor!==void 0){if(c.has(t.nextCursor))return yield*Z.make({message:`Orbit returned a repeated tools cursor.`});c.add(t.nextCursor)}return[t.tools,Ae.fromUndefinedOr(t.nextCursor)]})).pipe(E.runCollect);return Q.of({tools:l,instructions:s.instructions,callTool:n.fn(`OrbitClient.callTool`)(function*(e,t){return yield*o(`tools/call`,{name:e,arguments:t})},n.flatMap(r.decodeUnknownEffect(P.CallToolResult)),n.catchTag(`SchemaError`,()=>Z.make({message:`Invalid Orbit tool result. Request not replayed.`})))})})).pipe(p.provide(g.layer)),Hn=p.effectDiscard(n.gen(function*(){let e=yield*Q,t=yield*ke.McpServer;yield*n.forEach(e.tools,n.fn(`OrbitProxyLive.tool`)(function*(i){yield*t.addTool({tool:i,annotations:l.empty(),handle:n.fn(`handle`)(function*(e){return yield*r.decodeUnknownEffect(r.JsonObject)(e)},n.mapError(e=>P.InvalidParams.make({message:`Tool arguments must be a JSON object.`,data:e})),n.flatMap(t=>e.callTool(i.name,t).pipe(n.mapError(e=>P.InternalError.make({message:e.message})))))})}),{discard:!0})})),Un=`${de()}/.config/2digits/tlo-mcp/gateway.key`,Wn=r.String.check(r.isPattern(/^[a-f0-9]{64}$/)).pipe(r.RedactedFromValue);var $=class extends r.TaggedError()(`GatewayError`,{message:r.String}){};const Gn=n.fn(`Gateway.loadKey`)(function*(e){let t=yield*fe.FileSystem,i=(yield*pe.Path).dirname(e);yield*t.makeDirectory(i,{recursive:!0,mode:448}),yield*t.chmod(i,448),yield*t.writeFileString(e,ae(32).toString(`hex`),{flag:`wx`,mode:384}).pipe(n.catchReason(`PlatformError`,`AlreadyExists`,()=>n.void));let a=yield*t.stat(e);return a.type!==`File`||a.mode&63?yield*$.make({message:`Gateway key must be a private file (chmod 600).`}):yield*t.readFileString(e).pipe(n.flatMap(r.decodeEffect(Wn)))},n.mapError(()=>$.make({message:`Cannot load private gateway key. Check its permissions and format.`})));function Kn(e){let t=ie(`sha256`).update(`Bearer ${h.value(e)}`).digest();return b.middleware(e=>n.gen(function*(){let r=yield*re.HttpServerRequest,i=ie(`sha256`).update(r.headers.authorization??``).digest();return oe(t,i)?r.headers.origin===void 0?yield*e.pipe(n.provideService(ne.MaxBodySize,Oe.bytes(1048576)),n.map(e=>S.setHeader(e,`cache-control`,`no-store`))):S.empty({status:403,headers:{"cache-control":`no-store`}}):S.empty({status:401,headers:{"www-authenticate":`Bearer`,"cache-control":`no-store`}})}),{global:!0})}function qn(e){let t=p.unwrap(n.gen(function*(){let e=yield*Q;return Hn.pipe(p.provide(ke.layerHttp({name:`tlo-mcp`,version:`1.0.0`,instructions:e.instructions,path:`/mcp`,protocols:[N.v2025_11_25,N.v2025_06_18,N.v2025_03_26]})))}));return p.merge(t,Kn(e))}const Jn=r.String.check(r.isPattern(/^[a-z0-9]+\.opentunnel\.xyz$/)),Yn=n.fn(`Gateway.openTunnel`)(function*(e){let t=yield*(yield*Ln).tunnel.connect({profile:`tlo-mcp`,routes:{tlo:`127.0.0.1:${e}`}}),i=yield*r.decodeEffect(Jn)(t.tunnel.hostname);return yield*t.events.pipe(E.runForEach(e=>e.type===`reconnecting`?n.sync(()=>process.stderr.write(`OpenTunnel reconnecting...
7
- `)):n.void),n.forkScoped),{endpoint:`https://tlo.${i}/mcp`,closed:t.closed.pipe(n.mapError(()=>$.make({message:`OpenTunnel stopped unexpectedly. Restart tlo-mcp.`})))}},n.timeout(`180 seconds`),n.mapError(()=>$.make({message:`Could not connect OpenTunnel. Check network access and retry; existing tunnel identity is preserved.`})));function Xn(e){return`Connect my personal Teamleader Orbit MCP to Executor Cloud.
2
+ import*as e from"@effect/platform-node/NodeRuntime";import*as t from"@effect/platform-node/NodeServices";import*as n from"effect/Effect";import*as r from"effect/Schema";import*as i from"effect/cli/Command";import*as a from"effect/cli/Flag";import*as o from"@yielded/oauth/OAuth";import*as s from"@yielded/oauth/Pkce";import*as c from"effect/Clock";import*as l from"effect/Context";import*as u from"effect/Crypto";import*as d from"effect/Equal";import*as f from"effect/Exit";import*as p from"effect/Layer";import*as m from"effect/Match";import*as h from"effect/Redacted";import*as ee from"effect/Semaphore";import*as te from"effect/http/FetchHttpClient";import*as g from"effect/http/HttpClient";import*as _ from"effect/http/HttpClientRequest";import*as v from"effect/process/ChildProcess";import*as y from"effect/process/ChildProcessSpawner";import{createHash as ne,randomBytes as re,timingSafeEqual as ie}from"node:crypto";import{createServer as ae}from"node:http";import*as b from"@effect/platform-node/NodeHttpServer";import*as x from"effect/Deferred";import*as oe from"effect/Predicate";import*as S from"effect/http/HttpRouter";import*as se from"effect/http/HttpServer";import*as ce from"effect/http/HttpServerRequest";import*as C from"effect/http/HttpServerResponse";import*as le from"node:os";import{homedir as ue}from"node:os";import*as de from"effect/FileSystem";import*as fe from"effect/Path";import*as w from"effect/Stream";import{Context as pe,Effect as T,Layer as E,Option as me,Queue as D,Schema as O,Stream as he}from"effect";import{FetchHttpClient as ge,HttpClient as k,HttpClientError as _e,HttpClientRequest as ve}from"effect/http";import{HttpApi as ye,HttpApiClient as be,HttpApiEndpoint as A,HttpApiGroup as xe,HttpApiMiddleware as Se,HttpApiSchema as Ce,HttpApiSecurity as we,OpenApi as Te}from"effect/http-api";import*as j from"node:fs/promises";import*as M from"node:path";import*as Ee from"node:net";import{Duplex as De}from"node:stream";import*as Oe from"node:tls";import*as ke from"effect/ByteSize";import*as N from"effect/ai/McpProtocol";import*as Ae from"effect/ai/McpServer";import*as je from"effect/http/HttpIncomingMessage";import*as Me from"effect/Option";import*as Ne from"effect/Struct";import*as P from"effect/ai/McpSchema";import*as Pe from"effect/encoding/Sse";import*as Fe from"effect/http/HttpClientResponse";const Ie=`https://api.orbit.teamleader.eu`,F=`${Ie}/mcp`,Le=r.RedactedFromValue(r.String.check(r.isMinLength(1))),Re=r.Struct({clientId:r.String.check(r.isMinLength(1)),clientSecret:r.optionalKey(Le),accessToken:Le,refreshToken:r.optionalKey(Le),expiresAt:r.optionalKey(r.Finite)});var I=class extends r.TaggedError()(`OrbitAuthError`,{message:r.String}){};function ze(e,t,n){let r=t.refreshToken??e.refreshToken;return{clientId:e.clientId,...e.clientSecret===void 0?{}:{clientSecret:e.clientSecret},accessToken:t.accessToken,...r===void 0?{}:{refreshToken:r},...t.expiresIn===void 0?{}:{expiresAt:n+t.expiresIn*1e3}}}function Be(e,t){return e.expiresAt===void 0||e.expiresAt<=t+6e4}function Ve(e){return e.clientSecret===void 0?{method:`none`,publicClient:!0}:{method:`client_secret_post`,secret:e.clientSecret.pipe(h.value,h.make)}}function He(e,t){let n=e.searchParams.get(`state`);if(n===null||e.pathname!==`/callback`||e.searchParams.getAll(`state`).length!==1)return;let r=Buffer.from(t),i=Buffer.from(n);if(r.length!==i.length||!ie(r,i))return;if(e.searchParams.has(`error`))throw Error(`Orbit authorization was denied.`);let a=e.searchParams.get(`code`);if(a!==null&&a.length!==0&&e.searchParams.getAll(`code`).length===1)return a}const Ue=n.fn(`OrbitAuth.listenForLogin`)(function*(e){let t=yield*x.make(),r=yield*n.sync(ae),i=yield*b.make(()=>r,{port:0,host:`127.0.0.1`}).pipe(n.mapError(()=>I.make({message:`Could not bind OAuth callback.`})));if(!oe.isTagged(i.address,`InetAddressV4`))return yield*I.make({message:`Could not bind OAuth callback.`});let a=`127.0.0.1:${i.address.port}`,o=`http://${a}/callback`,s={"content-type":`text/plain; charset=utf-8`,"cache-control":`no-store`,"referrer-policy":`no-referrer`,"content-security-policy":`default-src 'none'`},c=C.text(`Invalid callback.`,{status:400,headers:s}),l=C.text(`Authorization received. Return to your terminal to complete login.`,{headers:s}),u=C.text(`Authorization denied. Return to your terminal.`,{status:400,headers:s}),d=n.fn(`OrbitAuth.handleCallback`)(function*(){let r=yield*ce.HttpServerRequest;return r.method!==`GET`||r.headers.host!==a||(yield*x.isDone(t))?c:yield*n.try({try:()=>He(new URL(r.url,o),e),catch:()=>I.make({message:`Orbit authorization was denied.`})}).pipe(n.matchEffect({onFailure:e=>x.fail(t,e).pipe(n.map(e=>e?u:c)),onSuccess:e=>e===void 0?n.succeed(c):x.succeed(t,e).pipe(n.map(e=>e?l:c))}))}),f=yield*S.toHttpEffect(S.add(`*`,`*`,d()));return yield*se.serveEffect(f).pipe(n.provideService(se.HttpServer,i)),yield*n.addFinalizer(()=>n.sync(()=>r.closeAllConnections())),{redirectUri:o,code:x.await(t).pipe(n.timeout(`3 minutes`),n.mapError(()=>I.make({message:`Login denied or timed out. Run tlo-mcp login again.`})))}}),We=r.fromJsonString(Re),Ge=`@2digits/tlo-mcp/orbit`;var L=class extends l.Service()(`@2digits/tlo-mcp/oauth/store/SessionStore`){};function Ke(e){return`"${e.replaceAll(`\\`,String.raw`\\`).replaceAll(`"`,String.raw`\"`)}"`}function qe(e={}){return p.effect(L,n.gen(function*(){let t=yield*de.FileSystem,i=yield*fe.Path,a=yield*y.ChildProcessSpawner,o=e.platform??process.platform,s=e.directory??i.join(ue(),`.config`,`2digits`,`tlo-mcp`),c=i.join(s,`session.json`),l=i.join(s,`session.lock`),u=t.makeDirectory(s,{recursive:!0,mode:448}).pipe(n.andThen(t.chmod(s,448))),d=n.fn(`SessionStore.security`)(function*(e,t){let r=yield*a.spawn(v.make(`/usr/bin/security`,e,{stdin:t===void 0?`ignore`:w.succeed(new TextEncoder().encode(t)),stderr:`ignore`}));return yield*n.all({output:r.stdout.pipe(w.decodeText(),w.mkString),exitCode:r.exitCode},{concurrency:`unbounded`})},n.scoped),f=n.fn(`SessionStore.load`)(function*(){let e;if(o===`darwin`){let t=yield*d([`find-generic-password`,`-a`,`orbit`,`-s`,Ge,`-w`]);if(t.exitCode===44)return;if(t.exitCode!==0)return yield*I.make({message:`Could not read macOS Keychain.`});e=t.output.trim()}else{let r=yield*t.readFileString(c).pipe(n.catchReason(`PlatformError`,`NotFound`,()=>n.void));if(r===void 0)return;e=r}return yield*r.decodeEffect(We)(e)},n.mapError(()=>I.make({message:`Could not read credentials. Run tlo-mcp login again.`}))),p=n.fn(`SessionStore.save`)(function*(e){let n=yield*r.encodeEffect(We)(e);if(o===`darwin`){if((yield*d([`-q`,`-i`],`add-generic-password -U -a orbit -s ${Ke(Ge)} -w ${Ke(n)}\n`)).exitCode!==0)return yield*I.make({message:`Could not save macOS Keychain credentials.`})}else{yield*u;let e=yield*t.makeTempDirectoryScoped({directory:s,prefix:`session-`}),r=i.join(e,`session.json`);yield*t.writeFileString(r,n,{mode:384}),yield*t.rename(r,c)}},n.scoped,n.mapError(()=>I.make({message:`Could not persist OAuth credentials. Run tlo-mcp login again.`}))),m=n.fn(`SessionStore.clear`)(function*(){if(o===`darwin`){let e=yield*d([`delete-generic-password`,`-a`,`orbit`,`-s`,Ge]);if(e.exitCode!==0&&e.exitCode!==44)return yield*I.make({message:`Could not remove Keychain credentials.`})}else yield*t.remove(c,{force:!0})},n.mapError(()=>I.make({message:`Could not remove OAuth credentials.`}))),h=n.fn(`SessionStore.lock`)(function*(){yield*u,yield*n.acquireRelease(t.writeFileString(l,String(process.pid),{flag:`wx`,mode:384}).pipe(n.catchReason(`PlatformError`,`AlreadyExists`,()=>I.make({message:`OAuth credentials locked. Close other login/refresh processes. After a crash only, remove ${l}.`}))),()=>t.remove(l).pipe(n.orDie))},n.mapError(e=>r.is(I)(e)?e:I.make({message:`Could not acquire OAuth credential lock. Check directory permissions.`})));return L.of({load:f(),save:p,clear:m(),lock:h()})}))}const Je=qe(),Ye=r.Struct({client_id:r.String.check(r.isMinLength(1)),client_secret:r.String.check(r.isMinLength(1)).pipe(r.RedactedFromValue,r.optionalKey)});var R=class extends l.Service()(`@2digits/tlo-mcp/oauth/OrbitAuth`){};const Xe=p.effect(R,n.gen(function*(){let e=yield*L,t=yield*u.Crypto,i=yield*g.HttpClient,a=yield*y.ChildProcessSpawner,l=yield*ee.make(1),p=yield*n.fn(`OrbitAuth.discover`)(function*(){let e=yield*g.withScope(i).get(`${Ie}/.well-known/oauth-authorization-server`);if(e.status!==200)return yield*I.make({message:`Could not discover Orbit OAuth endpoints.`});let t=yield*e.json.pipe(n.flatMap(r.decodeUnknownEffect(o.Metadata)));return t.issuer!==`https://api.orbit.teamleader.eu`||t.authorization_endpoint!==`https://api.orbit.teamleader.eu/oauth2/Authorize`||t.token_endpoint!==`https://api.orbit.teamleader.eu/oauth2/access_token`?yield*I.make({message:`Unexpected Orbit endpoints; refusing to send credentials.`}):t},n.scoped,n.timeout(`15 seconds`),n.mapError(()=>I.make({message:`Invalid or unavailable Orbit OAuth metadata.`})))().pipe(n.cachedWithTTL(e=>f.isSuccess(e)?1/0:0)),ne=n.fn(`OrbitAuth.accessToken`)(function*(){let t=yield*e.load;if(t===void 0)return yield*I.make({message:`Not authenticated. Run tlo-mcp login first.`});if(!Be(t,yield*c.currentTimeMillis))return t.accessToken;if(t.refreshToken===void 0)return t.expiresAt===void 0?t.accessToken:yield*I.make({message:`Orbit session expired without a refresh token. Run tlo-mcp login.`});let r=yield*p,i=ze(t,yield*(yield*o.make({metadata:r,clientId:t.clientId,authentication:Ve(t),timeoutMs:15e3})).refreshGrant({refreshToken:t.refreshToken,resources:[F]}).pipe(n.flatMap(o.tokens)),yield*c.currentTimeMillis);return yield*e.save(i),i.accessToken}),re=n.fn(`OrbitAuth.register`)(function*(e){let t=yield*_.post(`${Ie}/oauth2/register`).pipe(_.bodyJson({client_name:`@2digits/tlo-mcp`,redirect_uris:[e],response_types:[`code`],grant_types:[`authorization_code`,`refresh_token`],token_endpoint_auth_method:`none`}),n.flatMap(g.withScope(i).execute));return t.status!==200&&t.status!==201?yield*I.make({message:`Orbit client registration failed (HTTP ${t.status}).`}):yield*t.json.pipe(n.flatMap(r.decodeUnknownEffect(Ye)))},n.scoped,n.timeout(`15 seconds`)),ie=n.fn(`OrbitAuth.openBrowser`)(function*(e){return yield*(yield*m.value(process.platform).pipe(m.when(`darwin`,()=>v.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`open ${h.value(e)}`),m.when(`win32`,()=>v.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`rundll32 url.dll,FileProtocolHandler ${h.value(e)}`),m.orElse(()=>v.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`xdg-open ${h.value(e)}`))).exitCode},n.scoped,n.timeout(`5 seconds`),n.filterOrFail(d.equals(y.ExitCode(0)),()=>I.make({message:`Could not open browser.`})),(e,t)=>e.pipe(n.catch(()=>n.sync(()=>{process.stderr.write(`Open this URL in your browser:\n${h.value(t)}\n`)})))),ae=n.fn(`OrbitAuth.login`)(function*(){let t=yield*p,r=yield*s.random(),i=yield*s.make(),a=yield*r.pipe(h.value,Ue),l=yield*re(a.redirectUri),u={clientId:l.client_id,...l.client_secret===void 0?{}:{clientSecret:l.client_secret}},d=yield*o.make({metadata:t,...u,authentication:Ve(u),timeoutMs:15e3}),f=yield*d.authorizationUrl({redirectUri:a.redirectUri,scopes:[],state:r,codeChallenge:i.challenge,resources:[F]});yield*ie(f);let m=yield*a.code,ee=yield*d.codeGrant({code:h.make(m),redirectUri:a.redirectUri,pkceVerifier:i.verifier,resources:[F]}).pipe(n.flatMap(o.tokens));yield*e.save(ze(u,ee,yield*c.currentTimeMillis)),yield*n.sync(()=>{process.stderr.write(`Orbit login complete.
3
+ `)})}),b=n.fn(`OrbitAuth.locked`)(function*(t){return yield*e.lock,yield*t},n.scoped,l.withPermit,n.provideService(g.HttpClient,i),n.provideService(u.Crypto,t),n.provideService(y.ChildProcessSpawner,a),n.provideService(te.RequestInit,{redirect:`error`}),n.mapError(e=>r.is(I)(e)?e:I.make({message:`Orbit authentication unavailable. Check connectivity or run tlo-mcp login again.`})));return R.of({accessToken:b(ne()),login:b(ae()),logout:b(e.clear)})})).pipe(p.provide(Je),p.provide(te.layer)),Ze=8e3,Qe=(e,t)=>t===`idempotent`?e===429||e>=500&&e<=599:[429,503,521,522,523,525].includes(e),$e=(e,t)=>{let n=t?.trim();if(n&&/^\d+$/.test(n)){let e=Math.min(Number(n)*1e3,Ze);return[e,e]}let r=Math.min(250*2**Math.min(e,16),Ze);return[r/2,r]},et={520:`the server returned an unknown error`,521:`the server refused the connection`,522:`the connection to the server timed out`,523:`the server is unreachable`,524:`the server did not answer in time`,525:`the SSL handshake with the server failed`,526:`the server's certificate is invalid`,527:`the connection to the server failed`},tt=e=>et[e],nt=(e,t)=>{let n=tt(e);if(n)return`Cloudflare ${e}: ${n}`;let r=t.trim();return r?Array.from(r).slice(0,200).join(``):`HTTP ${e}`},rt=O.String.pipe(O.brand(`CSR.Raw`)),it=O.String.pipe(O.brand(`Hostname`));function at(){return(e,t)=>{class n{static makeUnsafe(e){return e}}return Object.setPrototypeOf(n,t),n}}var ot=class extends at()(`CertificateID`,O.String){};O.String.pipe(O.brand(`ChallengeToken`));var st=class extends O.Class(`Certificate/StateChallenge`)({type:O.Literal(`challenge`),token:O.String,key:O.String}){},ct=class extends O.Class(`Certificate/StateIssuing`)({type:O.Literal(`issuing`)}){},lt=class extends O.Class(`Certificate/StateReady`)({type:O.Literal(`ready`),certificate:O.String,chain:O.String,expiry:O.String}){},ut=class extends O.Class(`Certificate/StateFailed`)({type:O.Literal(`failed`),reason:O.String}){};const dt=O.Union([st,ct,lt,ut]);var ft=class extends O.Class(`Certificate`)({id:ot,state:dt}){},z=class extends at()(`TunnelID`,O.String){};const B=O.String.pipe(O.brand(`TunnelToken`));O.TaggedError()(`NotFound`,{tunnelID:z}),O.TaggedError()(`NoCertificate`,{tunnelID:z}),O.TaggedError()(`CertificateNotReady`,{tunnelID:z,currentState:O.String}),O.TaggedError()(`InvalidHostname`,{provided:it,expected:it});const pt=O.Literals([`offline`,`online`]);var mt=class extends O.Class(`Tunnel/Info`)({id:z,hostname:it,state:pt,certificateID:ot.pipe(O.optional)}){},ht=`0.5.1`,gt=class extends O.TaggedError()(`InvalidRequestError`,{message:O.String},{httpApiStatus:400}){},_t=class extends O.TaggedError()(`UnauthorizedError`,{message:O.String},{httpApiStatus:401}){},V=class extends O.TaggedError()(`TunnelNotFoundError`,{tunnelID:O.String,message:O.String},{httpApiStatus:404}){},vt=class extends O.TaggedError()(`CertificateNotFoundError`,{tunnelID:O.String,message:O.String},{httpApiStatus:404}){},yt=class extends O.TaggedError()(`InvalidHostnameError`,{provided:O.String,expected:O.String,message:O.String},{httpApiStatus:400}){},bt=class extends O.TaggedError()(`HostnameUnavailableError`,{name:O.String,message:O.String},{httpApiStatus:409}){},xt=class extends O.TaggedError()(`CertificateInProgressError`,{tunnelID:O.String,message:O.String},{httpApiStatus:409}){},H=class extends O.TaggedError()(`ServiceUnavailableError`,{message:O.String},{httpApiStatus:503}){};pe.Service()(`@opentunnel/protocol/OpenTunnelAuthorizationToken`);var U=class extends Se.Service()(`@opentunnel/protocol/OpenTunnelAuthorization`,{security:{bearer:we.bearer},error:_t}){};const St=O.Struct({name:O.String.pipe(O.optional)}),Ct=O.Struct({tunnel:mt,token:B}).pipe(Ce.status(201)),wt=xe.make(`tunnel`).add(A.post(`tunnel.create`,`/api/tunnel`,{payload:St,success:Ct,error:[gt,bt,H]})).add(A.get(`tunnel.get`,`/api/tunnel/:id`,{params:O.Struct({id:z}),success:mt,error:[V,H]}).middleware(U)).add(A.post(`tunnel.bindCertificate`,`/api/tunnel/:id/certificate`,{params:O.Struct({id:z}),payload:O.Struct({csr:rt}),success:ft.pipe(Ce.status(202)),error:[gt,yt,V,xt,H]}).middleware(U)).add(A.get(`tunnel.getCertificate`,`/api/tunnel/:id/certificate`,{params:O.Struct({id:z}),success:ft,error:[V,vt,H]}).middleware(U)).add(A.delete(`tunnel.remove`,`/api/tunnel/:id`,{params:O.Struct({id:z}),success:Ce.NoContent,error:[V,H]}).middleware(U)).add(A.get(`tunnel.connect`,`/api/tunnel/:id/connect`,{params:O.Struct({id:z}),success:O.Boolean,error:[V]}).annotateMerge(Te.annotations({summary:`Connect a bridge WebSocket`,description:`This endpoint upgrades to the opentunnel WebSocket protocol.`}))).annotateMerge(Te.annotations({title:`tunnel`})),Tt=ye.make(`opentunnel`).add(wt).annotateMerge(Te.annotations({title:`OpenTunnel API`,version:`0.1.0`,description:`Create and manage blind TLS tunnels.`})),Et=`opentunnel-sdk/${ht}`,Dt=/* @__PURE__ */ new Set([`ConnectionRefused`,`ECONNREFUSED`,`ENOTFOUND`,`EAI_AGAIN`,`FailedToOpenSocket`,`UND_ERR_CONNECT_TIMEOUT`]),Ot=e=>{if(typeof e!=`object`||!e)return;let{code:t,cause:n}=e;return typeof t==`string`?t:Ot(n)},kt=e=>e.method===`GET`||e.method===`POST`&&e.url.endsWith(`/certificate`)?`idempotent`:`once`,At=(e,t)=>_e.isHttpClientError(e)&&e.reason._tag===`TransportError`&&(t===`idempotent`||Dt.has(Ot(e.reason.cause)??``)),jt=e=>k.transform(e,(e,t)=>{let n=kt(t),r=(e,t)=>{let[n,r]=$e(e,t);return T.sleep(n+Math.random()*(r-n))},i=t=>T.matchEffect(e,{onFailure:e=>t<6&&At(e,n)?T.andThen(r(t),()=>i(t+1)):T.fail(e),onSuccess:e=>t<6&&Qe(e.status,n)?T.andThen(r(t,e.headers[`retry-after`]),()=>i(t+1)):T.succeed(e)});return i(0)});var Mt=class e extends pe.Service()(`@opentunnel/client/OpenTunnelApiClient`){static layer(t){return E.effect(e,T.gen(function*(){let e=(yield*k.HttpClient).pipe(k.mapRequest(ve.setHeader(`user-agent`,Et)),jt);return{client:yield*be.makeWith(Tt,{baseUrl:t.api,httpClient:e}),authorized:n=>be.makeWith(Tt,{baseUrl:t.api,httpClient:e.pipe(k.mapRequest(ve.bearerToken(n)))})}})).pipe(E.provide(ge.layer))}};const W=(e,...t)=>{let n=Nt(t),r=n.length<128?[n.length]:(()=>{let e=[];for(let t=n.length;t>0;t>>=8)e.unshift(t&255);return[128|e.length,...e]})();return Nt([new Uint8Array([e,...r]),n])},Nt=e=>{let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t},G=(...e)=>W(48,...e),Pt=(...e)=>W(49,...e),Ft=e=>W(12,new TextEncoder().encode(e)),It=e=>W(130,new TextEncoder().encode(e)),K=e=>{let[t,n,...r]=e.split(`.`).map(Number),i=[t*40+n];for(let e of r){let t=[e&127];for(let n=e>>7;n>0;n>>=7)t.unshift(n&127|128);i.push(...t)}return W(6,new Uint8Array(i))},Lt=e=>{let t=0;for(;t<e.length-1&&e[t]===0;)t++;let n=e.subarray(t);return W(2,n[0]&128?Nt([new Uint8Array([0]),n]):n)};async function Rt(e,t){let n=new Uint8Array(await crypto.subtle.exportKey(`spki`,t.publicKey)),r=G(W(2,new Uint8Array([0])),G(Pt(G(K(`2.5.4.3`),Ft(e)))),n,W(160,G(K(`1.2.840.113549.1.9.14`),Pt(G(G(K(`2.5.29.17`),W(4,G(It(e),It(`*.${e}`))))))))),i=new Uint8Array(await crypto.subtle.sign({name:`ECDSA`,hash:`SHA-256`},t.privateKey,r)),a=G(Lt(i.subarray(0,32)),Lt(i.subarray(32))),o=G(r,G(K(`1.2.840.10045.4.3.2`)),W(3,new Uint8Array([0]),a));return`-----BEGIN CERTIFICATE REQUEST-----\n${Buffer.from(o).toString(`base64`).replace(/.{1,64}/g,`$&
4
+ `)}-----END CERTIFICATE REQUEST-----`}var zt=class extends O.TaggedError()(`OpenTunnelClientError`,{message:O.String,cause:O.optional(O.Defect())}){},q=class extends O.TaggedError()(`OpenTunnelStorageError`,{message:O.String,cause:O.optional(O.Defect())}){};function Bt(e={}){let t=e.env??process.env,n=e.home??le.homedir(),r=t.XDG_DATA_HOME??M.join(n,`.local`,`share`),i=M.join(r,`opentunnel`),a=e=>M.join(i,e),o=e=>T.tryPromise({try:()=>j.readFile(e,`utf8`),catch:e=>e}).pipe(T.catch(t=>T.gen(function*(){if(!(typeof t==`object`&&t&&`code`in t&&t.code===`ENOENT`))return yield*new q({message:`Failed to read ${e}`,cause:t})}))),s=T.fn(`OpenTunnelStorage.profiles`)(function*(){return(yield*T.tryPromise({try:()=>j.readdir(i,{withFileTypes:!0}),catch:e=>e}).pipe(T.catch(e=>T.gen(function*(){return typeof e==`object`&&e&&`code`in e&&e.code===`ENOENT`?[]:yield*new q({message:`Failed to list ${i}`,cause:e})})))).filter(e=>e.isDirectory()).map(e=>e.name).sort()}),c=T.fn(`OpenTunnelStorage.load`)(function*(e){let t=a(e),n=yield*o(M.join(t,`tunnel.json`));if(!n)return;let[r,i,s,c]=yield*T.all([o(M.join(t,`token`)),o(M.join(t,`private-key.pem`)),o(M.join(t,`certificate.pem`)),o(M.join(t,`chain.pem`))]);if(!r||!i||!s||c===void 0)return yield*new q({message:`Incomplete tunnel identity for ${e}`});let l=JSON.parse(n);return{id:l.id,hostname:l.hostname,token:r.trim(),privateKey:i,certificate:s,chain:c,certificateExpiry:new Date(l.certificateExpiry)}}),l=T.fn(`OpenTunnelStorage.loadPending`)(function*(e){let t=a(e),n=yield*o(M.join(t,`pending.json`));if(!n)return;let[r,i]=yield*T.all([o(M.join(t,`token`)),o(M.join(t,`private-key.pem`))]);if(!r||!i)return yield*new q({message:`Incomplete pending tunnel identity for ${e}`});let s=JSON.parse(n);return{id:s.id,hostname:s.hostname,token:r.trim(),privateKey:i,csr:s.csr}});return{profiles:()=>s(),load:e=>c(e),loadPending:e=>l(e),savePending:(e,t)=>T.tryPromise({try:async()=>{let n=a(e);await j.mkdir(n,{recursive:!0,mode:448}),await Promise.all([j.writeFile(M.join(n,`pending.json`),JSON.stringify({id:t.id,hostname:t.hostname,csr:t.csr},null,2),{mode:384}),j.writeFile(M.join(n,`token`),`${t.token}\n`,{mode:384}),j.writeFile(M.join(n,`private-key.pem`),t.privateKey,{mode:384})])},catch:t=>new q({message:`Failed to save pending tunnel identity for ${e}`,cause:t})}),save:(e,t)=>T.tryPromise({try:async()=>{let n=a(e);await j.mkdir(n,{recursive:!0,mode:448}),await Promise.all([j.writeFile(M.join(n,`tunnel.json`),JSON.stringify({id:t.id,hostname:t.hostname,certificateExpiry:t.certificateExpiry.toISOString()},null,2),{mode:384}),j.writeFile(M.join(n,`token`),`${t.token}\n`,{mode:384}),j.writeFile(M.join(n,`private-key.pem`),t.privateKey,{mode:384}),j.writeFile(M.join(n,`certificate.pem`),t.certificate,{mode:384}),j.writeFile(M.join(n,`chain.pem`),t.chain,{mode:384})]),await j.rm(M.join(n,`pending.json`),{force:!0})},catch:t=>new q({message:`Failed to save tunnel identity for ${e}`,cause:t})}),remove:e=>T.tryPromise({try:()=>j.rm(a(e),{recursive:!0,force:!0}),catch:t=>new q({message:`Failed to remove tunnel identity for ${e}`,cause:t})}),list:()=>s().pipe(T.flatMap(e=>T.forEach(e,e=>c(e).pipe(T.map(t=>t?{profile:e,tunnel:t}:void 0)))),T.map(e=>e.filter(e=>e!==void 0)))}}const Vt=O.Int.check(O.isBetween({minimum:0,maximum:4294967295}));var Ht=class extends O.Class(`BridgeProtocol/ClientInfo`)({version:O.String,max_conns:O.Number}){},Ut=class extends O.Class(`BridgeProtocol/AttachMessage`)({type:O.Literal(`attach`),token:O.String,transport:O.Literal(`ws`),client:Ht,routes:O.Array(O.String).pipe(O.optional)}){},Wt=class extends O.Class(`BridgeProtocol/AttachedMessage`)({type:O.Literal(`attached`),session:O.String,routes:O.Array(O.String),heartbeat_ms:O.Number,idle_timeout_ms:O.Number}){},Gt=class extends O.Class(`BridgeProtocol/AttachErrorMessage`)({type:O.Literal(`attach_error`),code:O.String}){},Kt=class extends O.Class(`BridgeProtocol/PingMessage`)({type:O.Literal(`ping`),time_sent:O.Number}){},qt=class extends O.Class(`BridgeProtocol/PongMessage`)({type:O.Literal(`pong`),time_sent:O.Number}){},Jt=class extends O.Class(`BridgeProtocol/DrainMessage`)({type:O.Literal(`drain`),reason:O.String}){},Yt=class extends O.Class(`BridgeProtocol/OpenMessage`)({type:O.Literal(`open`),conn:Vt,peer:O.String,sni:O.String,alpn:O.String}){},Xt=class extends O.Class(`BridgeProtocol/EndMessage`)({type:O.Literal(`end`),conn:Vt}){},Zt=class extends O.Class(`BridgeProtocol/ResetMessage`)({type:O.Literal(`reset`),conn:Vt,code:O.String}){};O.Union([Ut,Kt,qt,Jt,Xt,Zt]);const Qt=O.Union([Wt,Gt,Kt,qt,Jt,Yt,Xt,Zt]),$t=O.decodeUnknownOption(Qt);function en(e){try{return $t(JSON.parse(e))}catch{return me.none()}}O.Union([Ut,Wt,Gt,Kt,qt,Jt,Yt,Xt,Zt]);const J={BAD_ATTACH:`bad_attach`,BAD_TOKEN:`bad_token`,TUNNEL_NOT_BOUND:`tunnel_not_bound`,CERT_NOT_READY:`cert_not_ready`,REPLACED:`replaced`,UPSTREAM_CONNECT_FAILED:`upstream_connect_failed`,UPSTREAM_IO_ERROR:`upstream_io_error`,CLIENT_IO_ERROR:`client_io_error`,IDLE_TIMEOUT:`idle_timeout`,ROUTE_CONFLICT:`route_conflict`,INVALID_ROUTE:`invalid_route`,UNKNOWN_ROUTE:`unknown_route`,TOO_MANY_CONNECTIONS:`too_many_connections`},tn={HEARTBEAT_MS:15e3,IDLE_TIMEOUT_MS:45e3,CONNECT_TIMEOUT_MS:1e4,RECONNECT_BACKOFF_MIN_MS:250,RECONNECT_BACKOFF_MAX_MS:3e4},Y={CONN_ID_SIZE:4,MAX_PAYLOAD_SIZE:32768};function nn(e){return e.length<Y.CONN_ID_SIZE?null:{conn:new DataView(e.buffer,e.byteOffset,e.byteLength).getUint32(0,!1),payload:e.slice(Y.CONN_ID_SIZE)}}function rn(e,t){let n=new Uint8Array(Y.CONN_ID_SIZE+t.length);return new DataView(n.buffer).setUint32(0,e,!1),n.set(t,Y.CONN_ID_SIZE),n}const an=/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/,on=e=>e===`@`||an.test(e),sn=(e,t)=>{let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return`@`;let i=`.${r}`;if(!n.endsWith(i))return;let a=n.slice(0,-i.length);return an.test(a)?a:void 0},cn=e=>{let t=e.lastIndexOf(`:`);if(t===-1)return;let n=e.slice(t+1);if(!/^\d{1,5}$/.test(n))return;let r=Number(n);if(r<1||r>65535)return;let i=e.slice(0,t);if(i.startsWith(`[`)){if(!i.endsWith(`]`))return;i=i.slice(1,-1)}if(i&&/^[A-Za-z0-9.\-:_]+$/.test(i))return{host:i,port:r}},ln=e=>e===`v1`||e===`v2`,un=Uint8Array.of(13,10,13,10,0,13,10,81,85,73,84,10),dn=e=>{let t=e.split(`.`);if(t.length!==4)return;let n=/* @__PURE__ */ new Uint8Array(4);for(let[e,r]of t.entries()){if(!/^(?:0|[1-9][0-9]{0,2})$/.test(r))return;let t=Number(r);if(t>255)return;n[e]=t}return n},fn=(e,t)=>{if(e===``)return[];let n=[],r=e.split(`:`);for(let[e,i]of r.entries())if(t&&e===r.length-1&&i.includes(`.`)){let e=dn(i);if(!e)return;n.push(e[0]<<8|e[1],e[2]<<8|e[3])}else{if(!/^[0-9A-Fa-f]{1,4}$/.test(i))return;n.push(Number.parseInt(i,16))}return n},pn=e=>{let t=e.indexOf(`::`),n;if(t===-1){if(n=fn(e,!0),n?.length!==8)return}else{if(e.indexOf(`::`,t+1)!==-1)return;let r=fn(e.slice(0,t),!1),i=fn(e.slice(t+2),!0);if(!r||!i||r.length+i.length>7)return;n=[...r,...Array(8-r.length-i.length).fill(0),...i]}let r=/* @__PURE__ */ new Uint8Array(16);for(let[e,t]of n.entries())r[e*2]=t>>8,r[e*2+1]=t&255;return r},mn=e=>{if(!/^[0-9]{1,5}$/.test(e))return;let t=Number(e);return t<=65535?t:void 0},hn=e=>e.subarray(0,10).every(e=>e===0)&&e[10]===255&&e[11]===255,gn=(e,t)=>e.length===16&&hn(e)?{family:4,bytes:e.slice(12),port:t}:e.length===4?{family:4,bytes:e,port:t}:{family:6,bytes:e,port:t},_n=e=>{let t=dn(e)??pn(e);if(t)return gn(t,0);if(e.startsWith(`[`)){let t=e.indexOf(`]:`);if(t===-1)return;let n=pn(e.slice(1,t)),r=mn(e.slice(t+2));return n&&r!==void 0?gn(n,r):void 0}let n=e.lastIndexOf(`:`);if(n===-1)return;let r=dn(e.slice(0,n)),i=mn(e.slice(n+1));return r&&i!==void 0?gn(r,i):void 0},vn=e=>{if(e.family===4)return[...e.bytes].join(`.`);let t=Array.from({length:8},(t,n)=>e.bytes[n*2]<<8|e.bytes[n*2+1]),n=-1,r=1;for(let e=0;e<8;){if(t[e]!==0){e++;continue}let i=e;for(;i<8&&t[i]===0;)i++;i-e>r&&(n=e,r=i-e),e=i}let i=e=>e.map(e=>e.toString(16)).join(`:`);return n===-1?i(t):`${i(t.slice(0,n))}::${i(t.slice(n+r))}`},yn=new TextEncoder,bn=(e,t,n)=>{let r=_n(t);return e===`v1`?xn(r):Sn(r,n)},xn=e=>{if(!e)return yn.encode(`PROXY UNKNOWN\r
5
+ `);let[t,n]=e.family===4?[`TCP4`,`0.0.0.0`]:[`TCP6`,`::`];return yn.encode(`PROXY ${t} ${vn(e)} ${n} ${e.port} 443\r\n`)},Sn=(e,t)=>{let n=yn.encode(t),r=n.length>0&&n.length<=255?3+n.length:0,i=(e===void 0?0:e.family===4?12:36)+r,a=new Uint8Array(16+i),o=new DataView(a.buffer);a.set(un,0),a[12]=e?33:32,a[13]=e===void 0?0:e.family===4?17:33,o.setUint16(14,i);let s=16;if(e){let t=e.bytes.length;a.set(e.bytes,s),s+=t*2,o.setUint16(s,e.port),o.setUint16(s+2,443),s+=4}return r>0&&(a[s]=2,o.setUint16(s+1,n.length),a.set(n,s+3)),a},Cn=WebSocket;var wn=class extends Error{code;constructor(e){super(`Bridge attach failed: ${e}`),this.code=e}};const Tn=/* @__PURE__ */ new Set([J.BAD_TOKEN,J.CERT_NOT_READY]),En=/* @__PURE__ */ new Set([`target`,`proxyProtocol`]),Dn=(e,t)=>{if(!on(e))throw Error(`Invalid route name '${e}'`);let n=t,r;if(typeof t==`object`&&t&&!Array.isArray(t)){for(let n of Object.keys(t))if(!En.has(n))throw Error(`Unknown option '${n}' for route '${e}': expected target or proxyProtocol`);let i=t;if(i.target===void 0)throw Error(`Route '${e}' has no target`);if(n=i.target,i.proxyProtocol!==void 0){if(!ln(i.proxyProtocol))throw Error(`Invalid proxyProtocol '${String(i.proxyProtocol)}' for route '${e}': use "v1" or "v2"`);r=i.proxyProtocol}}let i=typeof n==`string`?cn(n):void 0;if(!i)throw Error(`Invalid target '${String(n)}' for route '${e}': use host:port`);return r===void 0?{target:i}:{target:i,proxyProtocol:r}},On=e=>{for(let[t,n]of Object.entries(e))Dn(t,n)},kn=e=>new Map(Object.entries(e).map(([e,t])=>[e,Dn(e,t)])),An=e=>Oe.createSecureContext({key:e.privateKey,cert:`${e.certificate}\n${e.chain}`}),jn=(e,t)=>{let n=Object.keys(e).sort(),r=Object.keys(t).sort();return n.length===r.length&&n.every((e,t)=>e===r[t])},Mn=e=>{let t=Math.min(tn.RECONNECT_BACKOFF_MIN_MS*2**Math.min(e,16),tn.RECONNECT_BACKOFF_MAX_MS);return Math.round(t/2+t/2*Math.random())};var Nn=class{options;routes;resolved;identity;secureContext;refreshTimer;session;shutdown=!1;wake;status;ready;closed;constructor(e){this.options=e,this.resolved=kn(e.routes),this.routes={...e.routes},this.identity=e.identity,this.secureContext=An(e.identity),this.status={state:`connecting`,hostname:e.identity.hostname,routes:this.routes,connections:0};let t,n;this.ready=new Promise((e,r)=>{t=e,n=r}),this.closed=this.run(t).then(()=>t(),e=>{throw n(e),e}),this.ready.catch(()=>void 0),this.refreshCertificate()}async refreshCertificate(){if(this.shutdown)return;try{let e=new URL(`/api/tunnel/${encodeURIComponent(this.identity.id)}/certificate`,this.options.api),t=await fetch(e,{headers:{authorization:`Bearer ${this.identity.token}`}}),n=(t.ok?await t.json():void 0)?.state;if(n?.type===`ready`&&n.certificate&&n.expiry&&n.certificate!==this.identity.certificate){let e={...this.identity,certificate:n.certificate,chain:n.chain??``,certificateExpiry:new Date(n.expiry)};this.secureContext=An(e),this.identity=e,this.options.onRenewed?.(e),this.emit({type:`certificate-renewed`,expiry:n.expiry})}}catch{}if(this.shutdown)return;let e=this.identity.certificateExpiry.getTime()-Date.now()<2592e6;this.refreshTimer=setTimeout(()=>void this.refreshCertificate(),e?6e4:432e5),this.refreshTimer.unref?.()}getStatus(){return{...this.status,routes:{...this.status.routes}}}setRoutes(e){let t=kn(e),n=this.routes;this.resolved=t,this.routes={...e},this.status.routes=this.routes,jn(n,this.routes)||this.session?.end({type:`routes-changed`}),this.wake?.()}close(){return this.shutdown=!0,clearTimeout(this.refreshTimer),this.session?.end({type:`shutdown`}),this.wake?.(),this.closed.catch(()=>void 0)}emit(e){this.options.onEvent(e)}sleep(e){return new Promise(t=>{let n=setTimeout(r,e);function r(){clearTimeout(n),t()}this.wake=r}).finally(()=>{this.wake=void 0})}async run(e){let t=0;try{for(;!this.shutdown;){if(Object.keys(this.routes).length===0){this.status.state=`waiting-routes`,await this.sleep(2**31-1);continue}this.status.state=`connecting`,this.emit({type:`connecting`,attempt:t});let n=new Pn(this.options,this.routes,()=>this.secureContext,{emit:e=>this.emit(e),route:e=>this.resolved.get(e),attached:t=>{this.status.state=`connected`,this.status.session=t,this.status.lastError=void 0,this.status.connectedAt=Date.now(),e()},connections:e=>{this.status.connections=e}});this.session=n;let r=await n.run();if(this.session=void 0,this.status.session=void 0,this.status.connections=0,r.type===`shutdown`)break;if(r.type===`fatal`)throw r.error;if(r.type===`routes-changed`){this.emit({type:`disconnected`,reason:`routes changed`}),t=0;continue}r.attached&&(t=0),this.emit({type:`disconnected`,reason:r.error});let i=Mn(t);t++,this.status.state=`reconnecting`,this.status.lastError=r.error,this.emit({type:`reconnecting`,attempt:t,delayMs:i}),await this.sleep(i)}this.status.state=`stopped`,this.emit({type:`stopped`})}catch(e){throw this.status.state=`stopped`,this.status.lastError=e instanceof Error?e.message:String(e),this.emit({type:`stopped`,error:this.status.lastError}),e}}},Pn=class{options;routes;secureContext;hooks;socket;finish;channels=/* @__PURE__ */ new Map;drainWaiters=[];drainTimer;heartbeat;attachTimeout;lastReceived=Date.now();attached=!1;constructor(e,t,n,r){this.options=e,this.routes=t,this.secureContext=n,this.hooks=r}run(){return new Promise(e=>{this.finish=e;let t=new URL(`/api/tunnel/${encodeURIComponent(this.options.identity.id)}/connect`,this.options.api);t.protocol=t.protocol===`https:`?`wss:`:`ws:`;let n=new Cn(t,{protocols:[`opentunnel`],headers:{"user-agent":Et}});n.binaryType=`arraybuffer`,this.socket=n,this.attachTimeout=setTimeout(()=>this.end({type:`retry`,error:`bridge attach timed out`,attached:!1}),tn.CONNECT_TIMEOUT_MS),n.addEventListener(`open`,()=>{this.sendControl({type:`attach`,token:this.options.identity.token,transport:`ws`,routes:Object.keys(this.routes).sort(),client:{version:`0.1.0`,max_conns:256}})}),n.addEventListener(`message`,e=>{this.lastReceived=Date.now(),typeof e.data==`string`?this.onControl(e.data):this.onData(new Uint8Array(e.data))}),n.addEventListener(`close`,e=>{let t=`${e.code} ${e.reason}`.trim();this.end({type:`retry`,error:`bridge closed: ${t}`,attached:this.attached})})})}end(e){let t=this.finish;if(t){this.finish=void 0,clearTimeout(this.attachTimeout),clearInterval(this.heartbeat),clearInterval(this.drainTimer);for(let e of this.drainWaiters.splice(0))e();for(let e of[...this.channels.values()])e.destroy(`bridge disconnected`);this.socket&&this.socket.readyState<=WebSocket.OPEN&&this.socket.close(1e3),t(e)}}onControl(e){let t=en(e);if(me.isNone(t))return;let n=t.value;if(this.attached)switch(n.type){case`open`:return this.open(n.conn,n.sni,n.peer);case`end`:return this.channels.get(n.conn)?.endInbound();case`reset`:return this.channels.get(n.conn)?.destroy(n.code,!1);case`ping`:return this.sendControl({type:`pong`,time_sent:n.time_sent});case`drain`:return this.end({type:`retry`,error:`server draining: ${n.reason}`,attached:!0})}else if(n.type===`attached`){this.attached=!0,clearTimeout(this.attachTimeout);let e=Math.max(n.heartbeat_ms,1e3),t=Math.max(n.idle_timeout_ms,5e3);this.heartbeat=setInterval(()=>{Date.now()-this.lastReceived>t?this.end({type:`retry`,error:`bridge idle timeout`,attached:!0}):this.sendControl({type:`ping`,time_sent:Date.now()})},e),this.hooks.attached(n.session),this.hooks.emit({type:`connected`,session:n.session,routes:[...n.routes]})}else n.type===`attach_error`&&this.end(Tn.has(n.code)?{type:`fatal`,error:new wn(n.code)}:{type:`retry`,error:`attach failed: ${n.code}`,attached:!1})}onData(e){let t=nn(e);t&&this.channels.get(t.conn)?.push(t.payload)}open(e,t,n){let r=sn(t,this.options.identity.hostname),i=r===void 0?void 0:this.hooks.route(r);if(r===void 0||!i){this.sendControl({type:`reset`,conn:e,code:J.UNKNOWN_ROUTE});return}if(this.channels.size>=256){this.sendControl({type:`reset`,conn:e,code:J.TOO_MANY_CONNECTIONS});return}let a=i.proxyProtocol===void 0?void 0:bn(i.proxyProtocol,n,t),o=new Fn(e,r,i.target,a,this.secureContext(),this);this.channels.set(e,o),this.hooks.connections(this.channels.size),this.hooks.emit({type:`connection-opened`,conn:e,route:r,peer:n})}closed(e,t){this.channels.get(e.conn)===e&&(this.channels.delete(e.conn),this.hooks.connections(this.channels.size),this.hooks.emit({type:`connection-closed`,conn:e.conn,route:e.route,...t===void 0?{}:{error:t}}))}sendControl(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify(e))}sendData(e,t,n){let r=this.socket;if(!this.finish||r?.readyState!==WebSocket.OPEN)n(/* @__PURE__ */ Error(`bridge closed`));else{for(let n=0;n<t.byteLength;n+=Y.MAX_PAYLOAD_SIZE){let i=t.subarray(n,n+Y.MAX_PAYLOAD_SIZE);r.send(rn(e,i))}r.bufferedAmount<1048576?n():(this.drainWaiters.push(()=>n()),this.drainTimer??=setInterval(()=>{if(!(this.socket&&this.socket.bufferedAmount>262144&&this.finish)){clearInterval(this.drainTimer),this.drainTimer=void 0;for(let e of this.drainWaiters.splice(0))e()}},5))}}},Fn=class{conn;route;session;raw;tls;upstream;done=!1;constructor(e,t,n,r,i,a){this.conn=e,this.route=t,this.session=a,this.raw=new De({allowHalfOpen:!0,read(){},write:(t,n,r)=>a.sendData(e,t,r),final:t=>{a.sendControl({type:`end`,conn:e}),t()}}),this.tls=new Oe.TLSSocket(this.raw,{isServer:!0,secureContext:i}),this.tls.on(`secure`,()=>{let e=Ee.connect({host:n.host,port:n.port,allowHalfOpen:!0});this.upstream=e,e.setNoDelay(!0),r&&e.write(r),e.on(`error`,t=>this.destroy(e.connecting?J.UPSTREAM_CONNECT_FAILED:J.UPSTREAM_IO_ERROR,!0,t.message)),this.tls.pipe(e),e.pipe(this.tls)}),this.tls.on(`error`,e=>this.destroy(J.UPSTREAM_IO_ERROR,!0,e.message)),this.tls.on(`close`,()=>this.finish(void 0))}push(e){this.done||(this.raw.readableLength>8388608?this.destroy(J.UPSTREAM_IO_ERROR,!0,`local connection stalled`):this.raw.push(e))}endInbound(){this.done||this.raw.push(null)}destroy(e,t=!0,n=e){this.done||(t&&this.session.sendControl({type:`reset`,conn:this.conn,code:e}),this.finish(n))}finish(e){this.done||(this.done=!0,this.upstream?.destroy(),this.tls.destroy(),this.raw.destroy(),this.session.closed(this,e))}};const X=e=>e?.profile??`default`,In=e=>{if(_e.isHttpClientError(e)){let t=e.response;if(!t)return e.message;let n=nt(t.status,``);return n.startsWith(`HTTP `)?n:`${n} (HTTP ${t.status})`}if(typeof e==`object`&&e){let{_tag:t,message:n}=e;if(typeof t==`string`&&typeof n==`string`)return`${t}: ${n}`}return e instanceof Error?e.message:void 0},Z=(e,t)=>{let n=In(t);return new zt({message:n?`${e}: ${n}`:e,cause:t})},Ln=e=>_e.isHttpClientError(e)&&(e.reason._tag===`TransportError`||e.response!==void 0&&Qe(e.response.status,`idempotent`)),Rn=e=>{let t=new Uint8Array(e),n=``;for(let e of t)n+=String.fromCharCode(e);let r=btoa(n);return`-----BEGIN PRIVATE KEY-----\n${r.match(/.{1,64}/g)?.join(`
6
+ `)??r}\n-----END PRIVATE KEY-----\n`};var zn=class e extends pe.Service()(`@opentunnel/client/OpenTunnelClient`){static layer(t={}){let n=t.storage??Bt();return E.effect(e,T.gen(function*(){let e=yield*Mt,r=new URL(t.api??`https://opentunnel.xyz`),i=T.fn(`OpenTunnelClient.tunnel.get`)(function*(e){return yield*n.load(X(e))}),a=T.fn(`OpenTunnelClient.tunnel.completePending`)(function*(t){let r=yield*e.authorized(B.make(t.pending.token));yield*T.sync(()=>t.onProgress?.(`requesting-certificate`)),yield*r.tunnel[`tunnel.bindCertificate`]({params:{id:z.make(t.pending.id)},payload:{csr:t.pending.csr}}).pipe(T.catchTag(`CertificateInProgressError`,()=>T.void),T.mapError(e=>Z(`Failed to start certificate issuance`,e)));let i=yield*T.gen(function*(){for(;;){yield*T.sync(()=>t.onProgress?.(`waiting-certificate`));let e=yield*r.tunnel[`tunnel.getCertificate`]({params:{id:z.make(t.pending.id)}}).pipe(T.mapError(e=>Z(`Failed to read certificate`,e)));if(e.state.type===`ready`)return e.state;if(e.state.type===`failed`)return yield*new zt({message:`Certificate issuance failed: ${e.state.reason}`});yield*T.sleep(`2 seconds`)}}),a={id:t.pending.id,hostname:t.pending.hostname,token:t.pending.token,privateKey:t.pending.privateKey,certificate:i.certificate,chain:i.chain,certificateExpiry:new Date(i.expiry)};return yield*T.sync(()=>t.onProgress?.(`saving-identity`)),yield*n.save(t.profile,a),yield*T.sync(()=>t.onProgress?.(`ready`)),a}),o=T.fn(`OpenTunnelClient.tunnel.provision`)(function*(e){yield*T.sync(()=>e.onProgress?.(`generating-key`));let t=yield*T.tryPromise({try:()=>crypto.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},!0,[`sign`,`verify`]),catch:e=>Z(`Failed to generate certificate key`,e)});yield*T.sync(()=>e.onProgress?.(`generating-csr`));let r=yield*T.tryPromise({try:()=>Rt(e.hostname,t),catch:e=>Z(`Failed to generate certificate request`,e)}),i=yield*T.tryPromise({try:()=>crypto.subtle.exportKey(`pkcs8`,t.privateKey),catch:e=>Z(`Failed to export certificate key`,e)}),o={id:String(e.id),hostname:e.hostname,token:e.token,privateKey:Rn(i),csr:r};return yield*n.savePending(e.profile,o),yield*a({profile:e.profile,pending:o,onProgress:e.onProgress})}),s=T.fn(`OpenTunnelClient.tunnel.create`)(function*(t){let r=X(t);if(yield*n.load(r))return yield*new zt({message:`Profile '${r}' already has a tunnel`});let i=yield*n.loadPending(r);if(i)return yield*T.sync(()=>t?.onProgress?.(`resuming-certificate`)),yield*a({profile:r,pending:i,onProgress:t?.onProgress});yield*T.sync(()=>t?.onProgress?.(`creating-tunnel`));let s=yield*e.client.tunnel[`tunnel.create`]({payload:{}}).pipe(T.mapError(e=>Z(`Failed to create tunnel`,e)));return yield*o({profile:r,id:s.tunnel.id,hostname:String(s.tunnel.hostname),token:s.token,onProgress:t?.onProgress})}),c=T.fn(`OpenTunnelClient.tunnel.ensure`)(function*(t){let r=yield*i(t);if(!r)return yield*s(t);let a=yield*(yield*e.authorized(B.make(r.token))).tunnel[`tunnel.getCertificate`]({params:{id:z.make(r.id)}}).pipe(T.catchIf(e=>Ln(e)&&r.certificateExpiry.getTime()>Date.now(),()=>T.succeed(void 0)),T.mapError(e=>Z(`Failed to read certificate`,e)));if(!a)return r;let c=a.state;if(c.type===`ready`&&c.certificate!==r.certificate){let e={...r,certificate:c.certificate,chain:c.chain,certificateExpiry:new Date(c.expiry)};return yield*n.save(X(t),e),e}return c.type===`failed`?yield*o({profile:X(t),id:z.make(r.id),hostname:r.hostname,token:B.make(r.token)}):r}),l=T.fn(`OpenTunnelClient.tunnel.pending`)(function*(e){let t=yield*n.loadPending(X(e));return t?{id:t.id,hostname:t.hostname}:void 0}),u=T.fn(`OpenTunnelClient.tunnel.resume`)(function*(e){let t=X(e),r=yield*n.loadPending(t);if(r)return yield*T.sync(()=>e?.onProgress?.(`resuming-certificate`)),yield*a({profile:t,pending:r,onProgress:e?.onProgress})});return{profile:{list:n.profiles},tunnel:{list:n.list,get:i,pending:l,resume:u,create:s,ensure:c,remove:T.fn(`OpenTunnelClient.tunnel.remove`)(function*(t){let r=X(t),i=yield*n.load(r);i&&(yield*(yield*e.authorized(B.make(i.token))).tunnel[`tunnel.remove`]({params:{id:z.make(i.id)}}).pipe(T.mapError(e=>Z(`Failed to remove tunnel`,e))),yield*n.remove(r))}),connect:T.fn(`OpenTunnelClient.tunnel.connect`)(function*(e){yield*T.try({try:()=>On(e.routes),catch:e=>Z(e instanceof Error?e.message:`Invalid routes`,e)});let t=yield*c(e),i=yield*D.unbounded(),a=yield*T.acquireRelease(T.sync(()=>new Nn({api:r,identity:t,routes:e.routes,onEvent:e=>{D.offerUnsafe(i,e),e.type===`stopped`&&D.endUnsafe(i)},onRenewed:t=>{T.runFork(n.save(X(e),t).pipe(T.ignore))}})),e=>T.promise(()=>e.close()).pipe(T.andThen(D.end(i))));return yield*T.tryPromise({try:()=>a.ready,catch:e=>Z(`Failed to connect tunnel`,e)}),{tunnel:t,events:he.fromQueue(i),status:T.sync(()=>a.getStatus()),setRoutes:e=>T.try({try:()=>a.setRoutes(e),catch:e=>Z(e instanceof Error?e.message:`Invalid routes`,e)}),closed:T.tryPromise({try:()=>a.closed,catch:e=>Z(`Tunnel stopped`,e)}),close:T.promise(()=>a.close())}})}}})).pipe(E.provide(Mt.layer({api:t.api??`https://opentunnel.xyz`})))}};const Bn=r.Union([r.Struct({jsonrpc:r.Literal(`2.0`),id:P.RequestId,result:r.Unknown}),r.Struct({jsonrpc:r.Literal(`2.0`),id:r.NullOr(P.RequestId),error:P.McpErrorBase}),r.Struct({jsonrpc:r.Literal(`2.0`),method:r.String,params:r.optionalKey(r.Unknown)})]),Vn=r.fromJsonString(Bn),Hn=N.v2025_06_18.protocolVersion;var Q=class extends r.TaggedError()(`OrbitMcpError`,{message:r.String}){},Un=class extends l.Service()(`@2digits/tlo-mcp/mcp/OrbitClient`){};const Wn=p.effect(Un,n.gen(function*(){let e=yield*R,t=(yield*g.HttpClient).pipe(g.withScope,g.transformResponse(n.flatMap(Fe.matchStatus({"2xx":e=>n.succeed(e),401:e=>Q.make({message:`Orbit HTTP ${e.status}: authorization rejected. Run tlo-mcp login; a 403 can also mean insufficient rights.`}),403:e=>Q.make({message:`Orbit HTTP ${e.status}: authorization rejected. Run tlo-mcp login; a 403 can also mean insufficient rights.`}),429:e=>Q.make({message:`Orbit rate limited. Retry after ${e.headers[`retry-after`]??`the server cooldown`}; request not replayed.`}),orElse:e=>Q.make({message:`Orbit HTTP ${e.status}. Request not replayed.`})})))),i=0,a,o=n.fn(`OrbitClient.send`)(function*(o,s,c=!1){let l=++i,u=yield*e.accessToken,d=yield*_.post(F).pipe(_.bearerToken(u),_.setHeader(`accept`,`application/json, text/event-stream`),_.setHeader(`mcp-protocol-version`,Hn),e=>a===void 0?e:_.setHeader(e,`mcp-session-id`,a),_.bodyJson({jsonrpc:`2.0`,...c?{}:{id:l},method:o,params:s})),f=yield*t.execute(d),p=f.headers[`mcp-session-id`];if(o===`initialize`&&(a=p),c)return;let m=f.headers[`content-type`]?.startsWith(`text/event-stream`)?yield*f.stream.pipe(w.decodeText(),w.pipeThroughChannel(Pe.decode({maxEventSize:10485760})),w.filter(e=>e.event===`message`||e.event===``),w.map(Ne.get(`data`)),w.mapEffect(e=>r.decodeEffect(Vn)(e)),w.filter(e=>`id`in e&&e.id===l),w.runHead,n.flatMap(e=>Me.isSome(e)?n.succeed(e.value):Q.make({message:`Orbit stream ended before replying.`}))):yield*Fe.schemaBodyJson(Bn)(f);return!(`id`in m)||m.id!==l?yield*Q.make({message:`Unexpected Orbit response ID.`}):`error`in m?yield*Q.make({message:m.error.message}):m.result},n.timeout(`120 seconds`),n.scoped,n.provideService(te.RequestInit,{redirect:`error`}),n.mapError(e=>r.is(Q)(e)?e:Q.make({message:`Orbit connection or response failed. Request not replayed.`}))),s=yield*o(`initialize`,{protocolVersion:Hn,capabilities:{},clientInfo:{name:`@2digits/tlo-mcp`,version:`0.1.38`}}).pipe(n.flatMap(r.decodeUnknownEffect(P.InitializeResult)));if(s.protocolVersion!==Hn)return yield*Q.make({message:`Unsupported upstream MCP protocol: ${s.protocolVersion}.`});yield*o(`notifications/initialized`,{},!0);let c=/* @__PURE__ */ new Set,l=yield*w.paginate(void 0,n.fn(`OrbitClient.listToolsPage`)(function*(e){let t=yield*o(`tools/list`,e===void 0?{}:{cursor:e}).pipe(n.flatMap(r.decodeUnknownEffect(P.ListToolsResult)));if(t.nextCursor!==void 0){if(c.has(t.nextCursor))return yield*Q.make({message:`Orbit returned a repeated tools cursor.`});c.add(t.nextCursor)}return[t.tools,Me.fromUndefinedOr(t.nextCursor)]})).pipe(w.runCollect);return Un.of({tools:l,instructions:s.instructions,callTool:n.fn(`OrbitClient.callTool`)(function*(e,t){return yield*o(`tools/call`,{name:e,arguments:t})},n.flatMap(r.decodeUnknownEffect(P.CallToolResult)),n.catchTag(`SchemaError`,()=>Q.make({message:`Invalid Orbit tool result. Request not replayed.`})))})})).pipe(p.provide(te.layer)),Gn=p.effectDiscard(n.gen(function*(){let e=yield*Un,t=yield*Ae.McpServer;yield*n.forEach(e.tools,n.fn(`OrbitProxyLive.tool`)(function*(i){yield*t.addTool({tool:i,annotations:l.empty(),handle:n.fn(`handle`)(function*(e){return yield*r.decodeUnknownEffect(r.JsonObject)(e)},n.mapError(e=>P.InvalidParams.make({message:`Tool arguments must be a JSON object.`,data:e})),n.flatMap(t=>e.callTool(i.name,t).pipe(n.mapError(e=>P.InternalError.make({message:e.message})))))})}),{discard:!0})})),Kn=`${ue()}/.config/2digits/tlo-mcp/gateway.key`,qn=r.String.check(r.isPattern(/^[a-f0-9]{64}$/)).pipe(r.RedactedFromValue);var $=class extends r.TaggedError()(`GatewayError`,{message:r.String}){};const Jn=n.fn(`Gateway.loadKey`)(function*(e){let t=yield*de.FileSystem,i=(yield*fe.Path).dirname(e);yield*t.makeDirectory(i,{recursive:!0,mode:448}),yield*t.chmod(i,448),yield*t.writeFileString(e,re(32).toString(`hex`),{flag:`wx`,mode:384}).pipe(n.catchReason(`PlatformError`,`AlreadyExists`,()=>n.void));let a=yield*t.stat(e);return a.type!==`File`||a.mode&63?yield*$.make({message:`Gateway key must be a private file (chmod 600).`}):yield*t.readFileString(e).pipe(n.flatMap(r.decodeEffect(qn)))},n.mapError(()=>$.make({message:`Cannot load private gateway key. Check its permissions and format.`})));function Yn(e){let t=ne(`sha256`).update(`Bearer ${h.value(e)}`).digest();return S.middleware(e=>n.gen(function*(){let r=yield*ce.HttpServerRequest,i=ne(`sha256`).update(r.headers.authorization??``).digest();return ie(t,i)?r.headers.origin===void 0?yield*e.pipe(n.provideService(je.MaxBodySize,ke.bytes(1048576)),n.map(e=>C.setHeader(e,`cache-control`,`no-store`))):C.empty({status:403,headers:{"cache-control":`no-store`}}):C.empty({status:401,headers:{"www-authenticate":`Bearer`,"cache-control":`no-store`}})}),{global:!0})}function Xn(e){let t=p.unwrap(n.gen(function*(){let e=yield*Un;return Gn.pipe(p.provide(Ae.layerHttp({name:`tlo-mcp`,version:`1.0.0`,instructions:e.instructions,path:`/mcp`,protocols:[N.v2025_11_25,N.v2025_06_18,N.v2025_03_26]})))}));return p.merge(t,Yn(e))}const Zn=r.String.check(r.isPattern(/^[a-z0-9]+\.opentunnel\.xyz$/)),Qn=n.fn(`Gateway.openTunnel`)(function*(e){let t=yield*(yield*zn).tunnel.connect({profile:`tlo-mcp`,routes:{tlo:`127.0.0.1:${e}`}}),i=yield*r.decodeEffect(Zn)(t.tunnel.hostname);return yield*t.events.pipe(w.runForEach(e=>e.type===`reconnecting`?n.sync(()=>process.stderr.write(`OpenTunnel reconnecting...
7
+ `)):n.void),n.forkScoped),{endpoint:`https://tlo.${i}/mcp`,closed:t.closed.pipe(n.mapError(()=>$.make({message:`OpenTunnel stopped unexpectedly. Restart tlo-mcp.`})))}},n.timeout(`180 seconds`),n.mapError(()=>$.make({message:`Could not connect OpenTunnel. Check network access and retry; existing tunnel identity is preserved.`})));function $n(e){return`Connect my personal Teamleader Orbit MCP to Executor Cloud.
8
8
  Endpoint: ${e}
9
9
  Transport: Streamable HTTP. Authentication: API key in Authorization: Bearer <key>.
10
10
  Register an MCP integration named Teamleader Orbit (slug tlo_mcp) with a bearer-header API-key template, not OAuth.
@@ -12,9 +12,9 @@ Use a secure credential handoff so I enter the gateway key directly in Executor;
12
12
  Create a personal connection, discover tools, and verify with one read-only tool. Do not change business data.
13
13
  If this endpoint already exists, reuse its integration/connection instead of creating duplicates.
14
14
  Orbit login and refresh are handled on my Mac; do not register another Orbit OAuth client.
15
- `}const Zn=n.fn(`TloMcp.connect`)(function*(e){let t=yield*Gn(Un),r=yield*Ue,i=yield*L;(yield*r.load)===void 0&&(yield*i.login),yield*p.build(b.serve(qn(t),{disableLogger:!0,disableListenLog:!0}).pipe(p.provide(Vn),p.provide(ce.layer(()=>se({maxHeaderSize:8192,requestTimeout:13e4}),{host:`127.0.0.1`,port:e})))),yield*n.sync(()=>process.stderr.write(`Connecting OpenTunnel (first certificate may take a few minutes)...
16
- `));let{endpoint:a,closed:o}=yield*Yn(e);return yield*n.sync(()=>{process.stderr.write(`TLO gateway ready: ${a}\nGateway key: ${Un} (private; not printed)\n`),process.platform===`darwin`&&process.stderr.write(`Copy the key for Executor's secure credential form: pbcopy < "${Un}"\n`),process.stderr.write(`Keep this process running and your Mac awake. Ctrl-C closes the gateway and tunnel.
15
+ `}const er=n.fn(`TloMcp.connect`)(function*(e){let t=yield*Jn(Kn),r=yield*L,i=yield*R;(yield*r.load)===void 0&&(yield*i.login),yield*p.build(S.serve(Xn(t),{disableLogger:!0,disableListenLog:!0}).pipe(p.provide(Wn),p.provide(b.layer(()=>ae({maxHeaderSize:8192,requestTimeout:13e4}),{host:`127.0.0.1`,port:e})))),yield*n.sync(()=>process.stderr.write(`Connecting OpenTunnel (first certificate may take a few minutes)...
16
+ `));let{endpoint:a,closed:o}=yield*Qn(e);return yield*n.sync(()=>{process.stderr.write(`TLO gateway ready: ${a}\nGateway key: ${Kn} (private; not printed)\n`),process.platform===`darwin`&&process.stderr.write(`Copy the key for Executor's secure credential form: pbcopy < "${Kn}"\n`),process.stderr.write(`Keep this process running and your Mac awake. Ctrl-C closes the gateway and tunnel.
17
17
 
18
18
  Copy this prompt to your agent:
19
19
 
20
- `),process.stdout.write(Xn(a))}),yield*o,yield*$.make({message:`The OpenTunnel connection stopped. Restart tlo-mcp.`})},n.scoped,n.provide(p.mergeAll(Je,Ke,Ln.layer()))),Qn=i.make(`login`,{},n.fn(`TloMcp.login`)(function*(){yield*(yield*L).login},n.provide(Je),n.scoped)),$n=i.make(`logout`,{},n.fn(`TloMcp.logout`)(function*(){yield*(yield*L).logout},n.provide(Je),n.scoped)),er=i.make(`tlo-mcp`,{port:a.Int(`port`).pipe(a.withSchema(r.Int.check(r.isBetween({minimum:1024,maximum:65535}))),a.withDefault(4790))},({port:e})=>Zn(e)).pipe(i.withSubcommands([Qn,$n]));n.fn(`TloMcp.run`)(function*(e){return yield*i.runWith(er,{version:`1.0.0`})(e)})(process.argv.slice(2)).pipe(n.provide(t.layer),e.runMain);export{};
20
+ `),process.stdout.write($n(a))}),yield*o,yield*$.make({message:`The OpenTunnel connection stopped. Restart tlo-mcp.`})},n.scoped,n.provide(p.mergeAll(Xe,Je,zn.layer()))),tr=i.make(`login`,{},n.fn(`TloMcp.login`)(function*(){yield*(yield*R).login},n.provide(Xe),n.scoped)),nr=i.make(`logout`,{},n.fn(`TloMcp.logout`)(function*(){yield*(yield*R).logout},n.provide(Xe),n.scoped)),rr=i.make(`tlo-mcp`,{port:a.Int(`port`).pipe(a.withSchema(r.Int.check(r.isBetween({minimum:1024,maximum:65535}))),a.withDefault(4790))},({port:e})=>er(e)).pipe(i.withSubcommands([tr,nr]));n.fn(`TloMcp.run`)(function*(e){return yield*i.runWith(rr,{version:`1.0.0`})(e)})(process.argv.slice(2)).pipe(n.provide(t.layer),e.runMain);export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2digits/tlo-mcp",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/2digits-agency/configs.git",
@@ -18,19 +18,19 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
- "@effect/platform-node": "4.0.4",
21
+ "@effect/platform-node": "4.0.5",
22
22
  "@opentunnel/client": "0.5.1",
23
- "@yielded/oauth": "0.1.0-beta.38",
24
- "effect": "4.0.4"
23
+ "@yielded/oauth": "0.1.0-beta.42",
24
+ "effect": "4.0.5"
25
25
  },
26
26
  "devDependencies": {
27
- "@2digits/tsconfig": "0.9.2",
27
+ "@2digits/tsconfig": "1.0.0",
28
28
  "@arethetypeswrong/core": "0.18.5",
29
- "@effect/vitest": "4.0.4",
29
+ "@effect/vitest": "4.0.5",
30
30
  "publint": "0.3.25",
31
31
  "typescript": "7.0.2",
32
- "vite-plus": "1.1.0",
33
- "vitest": "5.0.4"
32
+ "vite-plus": "1.3.0",
33
+ "vitest": "5.0.5"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=24"