skydive-cli 0.5.0-beta.31 → 0.5.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/api-DQCaztBg.mjs +315 -0
- package/dist/js/{billing-blocked-Dgu5-oDy.mjs → billing-blocked-SE6tySLd.mjs} +1 -1
- package/dist/js/bin.mjs +34 -29
- package/dist/js/{boot-iaVSnd7h.mjs → boot-yyXrIeVy.mjs} +15 -11
- package/dist/js/chunk-BbwQpWto.mjs +33 -0
- package/dist/js/{client-DbqRBquD.mjs → client-d6K9qm6B.mjs} +70 -2
- package/dist/js/client-yz3zrpAS.mjs +5 -0
- package/dist/js/daemon-Btd2436i.mjs +7 -0
- package/dist/js/{daemon-BTyg329O.mjs → daemon-CxFxH36O.mjs} +4 -2
- package/dist/js/daemon-client-1Qfc_Y3J.mjs +8 -0
- package/dist/js/{daemon-client--A_yMKq6.mjs → daemon-client-Br1NlkRp.mjs} +1 -1
- package/dist/js/dist-CRtjM7ba.mjs +1750 -0
- package/dist/js/{forward-DL6DYqyc.mjs → forward-zwB55Bls.mjs} +25 -4
- package/dist/js/localhost-cert-Bn-UBUmj.mjs +67 -0
- package/dist/js/{print-DACzRUwQ.mjs → print-B9djx8GU.mjs} +3 -3
- package/dist/js/{print-C_TnQSPg.mjs → print-Clgq46GU.mjs} +3 -3
- package/dist/js/{print-share-BH9gvCls.mjs → print-share-BBy3OTno.mjs} +6 -2
- package/dist/js/{profiler-BVbYs_Yg.mjs → profiler-CJMnC90G.mjs} +2 -2
- package/dist/js/{raw-pty-DY4KelZW.mjs → raw-pty-B9Bue8gg.mjs} +1 -1
- package/dist/js/raw-pty-g3kATVQe.mjs +5 -0
- package/dist/js/{rest-CgfbKXst.mjs → rest-C6E4mS2A.mjs} +2 -2
- package/dist/js/rest-CaWUSrnH.mjs +6 -0
- package/dist/js/tls-cert-CLgSQALB.mjs +4 -0
- package/dist/js/tls-cert-CV-pwxVN.mjs +67 -0
- package/package.json +1 -1
- package/dist/js/api-DG5W6iwx.mjs +0 -131
- package/dist/js/client-hH2PJL8y.mjs +0 -5
- package/dist/js/daemon-SPQgtsbW.mjs +0 -6
- package/dist/js/daemon-client-C7emKKlj.mjs +0 -7
- package/dist/js/raw-pty-DmdUf4_w.mjs +0 -5
- package/dist/js/rest-VV5nc-Mn.mjs +0 -6
- /package/dist/js/{billing-blocked-2wju4gC_.mjs → billing-blocked-D3l5kJlX.mjs} +0 -0
- /package/dist/js/{client-c4c5MmgN.mjs → client-BTQ1fwzM.mjs} +0 -0
- /package/dist/js/{http-error-DzyrsLAZ.mjs → http-error-UHH3mVBF.mjs} +0 -0
- /package/dist/js/{output-DYzzdXYV.mjs → output-wY0VQDea.mjs} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { r as warnOnRebindProtection } from "./tls-cert-CV-pwxVN.mjs";
|
|
3
|
+
import { l as startTlsForward, t as fetchForwardTarget } from "./api-DQCaztBg.mjs";
|
|
3
4
|
import net from "node:net";
|
|
4
5
|
import { WebSocket, createWebSocketStream } from "ws";
|
|
5
6
|
|
|
@@ -103,7 +104,7 @@ const MAX_POOL_SIZE = 16;
|
|
|
103
104
|
* sandbox recycle just costs the next connection a cold-start wait rather
|
|
104
105
|
* than invalidating the forward.
|
|
105
106
|
*/
|
|
106
|
-
async function startForward({ auth, agentId, localPort, targetPort, log }) {
|
|
107
|
+
async function startForward({ auth, agentId, localPort, targetPort, tlsCertSource, log }) {
|
|
107
108
|
let target = await fetchForwardTarget(auth, agentId);
|
|
108
109
|
let mintedAt = Date.now();
|
|
109
110
|
async function freshTarget() {
|
|
@@ -174,11 +175,31 @@ async function startForward({ auth, agentId, localPort, targetPort, log }) {
|
|
|
174
175
|
});
|
|
175
176
|
});
|
|
176
177
|
const addr = server.address();
|
|
178
|
+
const boundPort = addr && typeof addr === "object" ? addr.port : localPort;
|
|
179
|
+
let tls = null;
|
|
180
|
+
if (tlsCertSource) try {
|
|
181
|
+
const cert = await tlsCertSource();
|
|
182
|
+
if (cert) {
|
|
183
|
+
if (cert.hostname !== "localhost") await warnOnRebindProtection(cert.hostname, log);
|
|
184
|
+
tls = await startTlsForward({
|
|
185
|
+
cert,
|
|
186
|
+
localPort: boundPort,
|
|
187
|
+
targetPort,
|
|
188
|
+
acquireTunnel: async () => pool.take() ?? dialTunnel(),
|
|
189
|
+
log
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
} catch (err) {
|
|
193
|
+
log(`forward: TLS origin unavailable (${err instanceof Error ? err.message : String(err)}); continuing with http only`);
|
|
194
|
+
}
|
|
177
195
|
return {
|
|
178
|
-
port:
|
|
196
|
+
port: boundPort,
|
|
197
|
+
tls,
|
|
179
198
|
close: () => new Promise((resolve) => {
|
|
180
199
|
pool.close();
|
|
181
|
-
|
|
200
|
+
(tls ? tls.close() : Promise.resolve()).then(() => {
|
|
201
|
+
server.close(() => resolve());
|
|
202
|
+
});
|
|
182
203
|
})
|
|
183
204
|
};
|
|
184
205
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as __toESM } from "./chunk-BbwQpWto.mjs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
//#region ../portal-daemon/src/localhost-cert.ts
|
|
8
|
+
/**
|
|
9
|
+
* Durable home for the one piece of state devcert doesn't keep for us: the
|
|
10
|
+
* user's decision to skip automatic cert setup. The daemon's own state dir is
|
|
11
|
+
* tmpdir-based (wiped on reboot), and re-raising a system trust prompt on
|
|
12
|
+
* every `portal forward` after a decline is exactly the nagging that trains
|
|
13
|
+
* people to fear the feature.
|
|
14
|
+
*/
|
|
15
|
+
function declineMarkerPath() {
|
|
16
|
+
return path.join(os.homedir(), ".config", "skydive-portal", "tls-declined");
|
|
17
|
+
}
|
|
18
|
+
async function hasDeclined() {
|
|
19
|
+
try {
|
|
20
|
+
await readFile(declineMarkerPath());
|
|
21
|
+
return true;
|
|
22
|
+
} catch (_error) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function recordDecline(reason) {
|
|
27
|
+
const marker = declineMarkerPath();
|
|
28
|
+
try {
|
|
29
|
+
await mkdir(path.dirname(marker), { recursive: true });
|
|
30
|
+
await writeFile(marker, `Automatic localhost TLS setup failed or was declined:\n${reason}\n\nDelete this file to let it try again.\n`);
|
|
31
|
+
} catch (_error) {}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fully automatic localhost cert: devcert generates a per-machine local CA on
|
|
35
|
+
* first use, installs it into the system/browser trust stores (one OS-native
|
|
36
|
+
* consent prompt, the mkcert pattern), signs a `localhost` leaf, and reuses
|
|
37
|
+
* both silently on every later run. The private key never leaves the machine
|
|
38
|
+
* and the CA is unique to it, so trusting it vouches only for this user's own
|
|
39
|
+
* loopback.
|
|
40
|
+
*
|
|
41
|
+
* Every failure — the user declining the trust prompt included — degrades to
|
|
42
|
+
* the plain-http origin (the product default) with one log line, and writes a
|
|
43
|
+
* marker so subsequent runs don't nag. `resetLocalhostCertDecline` (or
|
|
44
|
+
* deleting the marker file) re-arms it.
|
|
45
|
+
*/
|
|
46
|
+
function localhostCertSource(log) {
|
|
47
|
+
return async () => {
|
|
48
|
+
if (await hasDeclined()) return null;
|
|
49
|
+
try {
|
|
50
|
+
const { certificateFor } = await import("./dist-CRtjM7ba.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
51
|
+
const { key, cert } = await certificateFor("localhost", { skipHostsFile: true });
|
|
52
|
+
return {
|
|
53
|
+
key: key.toString(),
|
|
54
|
+
cert: cert.toString(),
|
|
55
|
+
hostname: "localhost"
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
59
|
+
await recordDecline(reason);
|
|
60
|
+
log(`portal: automatic localhost TLS setup didn't complete (${reason}). Continuing with http only; delete ~/.config/skydive-portal/tls-declined to try again.`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { localhostCertSource };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-
|
|
3
|
-
import "./rest-
|
|
4
|
-
import "./billing-blocked-
|
|
2
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-Clgq46GU.mjs";
|
|
3
|
+
import "./rest-C6E4mS2A.mjs";
|
|
4
|
+
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
|
6
6
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as HttpError } from "./http-error-
|
|
3
|
-
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
4
|
-
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-
|
|
2
|
+
import { t as HttpError } from "./http-error-UHH3mVBF.mjs";
|
|
3
|
+
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-C6E4mS2A.mjs";
|
|
4
|
+
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import Conf from "conf";
|
|
7
7
|
import { err, ok } from "neverthrow";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as printError } from "./output-
|
|
2
|
+
import { n as printError } from "./output-wY0VQDea.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/chat/print-share.ts
|
|
5
5
|
/**
|
|
@@ -14,7 +14,8 @@ import { n as printError } from "./output-DYzzdXYV.mjs";
|
|
|
14
14
|
* the reply (and to --json).
|
|
15
15
|
*/
|
|
16
16
|
async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
17
|
-
const { PortalClient } = await import("./client-
|
|
17
|
+
const { PortalClient } = await import("./client-yz3zrpAS.mjs");
|
|
18
|
+
const { defaultTlsCertSource } = await import("./tls-cert-CLgSQALB.mjs");
|
|
18
19
|
let signalConnected;
|
|
19
20
|
const connected = new Promise((resolve) => {
|
|
20
21
|
signalConnected = resolve;
|
|
@@ -26,6 +27,9 @@ async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
|
26
27
|
deviceToken: null
|
|
27
28
|
}),
|
|
28
29
|
resolveCwd: () => process.cwd(),
|
|
30
|
+
tlsCertSource: defaultTlsCertSource(process.env, (msg) => {
|
|
31
|
+
console.error(msg);
|
|
32
|
+
}),
|
|
29
33
|
persistedMachineName: null,
|
|
30
34
|
onMachineName: () => {},
|
|
31
35
|
onState: (state) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as getConfigPath } from "./print-
|
|
2
|
+
import { C as getConfigPath } from "./print-Clgq46GU.mjs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { err, ok } from "neverthrow";
|
|
@@ -8,7 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
|
|
9
9
|
//#region package.json
|
|
10
10
|
var name = "skydive-cli";
|
|
11
|
-
var version$1 = "0.5.0-beta.
|
|
11
|
+
var version$1 = "0.5.0-beta.32";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/auth/organization.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as HttpError } from "./http-error-
|
|
3
|
-
import { a as billingBlockedOutcomeSchema } from "./billing-blocked-
|
|
2
|
+
import { t as HttpError } from "./http-error-UHH3mVBF.mjs";
|
|
3
|
+
import { a as billingBlockedOutcomeSchema } from "./billing-blocked-D3l5kJlX.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { createParser } from "eventsource-parser";
|
|
6
6
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import dns from "node:dns";
|
|
4
|
+
|
|
5
|
+
//#region ../portal-daemon/src/tls-cert.ts
|
|
6
|
+
/**
|
|
7
|
+
* File-backed cert source, configured by environment:
|
|
8
|
+
* SKYDIVE_PORTAL_TLS_KEY / SKYDIVE_PORTAL_TLS_CERT (PEM file paths) and
|
|
9
|
+
* SKYDIVE_PORTAL_TLS_HOST (the name the cert covers). All three or nothing —
|
|
10
|
+
* a partial configuration is reported once and treated as absent, so a typo
|
|
11
|
+
* degrades to today's behavior instead of a crash loop.
|
|
12
|
+
*/
|
|
13
|
+
function envTlsCertSource(env, log) {
|
|
14
|
+
return async () => {
|
|
15
|
+
const keyPath = env.SKYDIVE_PORTAL_TLS_KEY;
|
|
16
|
+
const certPath = env.SKYDIVE_PORTAL_TLS_CERT;
|
|
17
|
+
const hostname = env.SKYDIVE_PORTAL_TLS_HOST;
|
|
18
|
+
if (!keyPath && !certPath && !hostname) return null;
|
|
19
|
+
if (!keyPath || !certPath || !hostname) {
|
|
20
|
+
log("forward: SKYDIVE_PORTAL_TLS_KEY, SKYDIVE_PORTAL_TLS_CERT and SKYDIVE_PORTAL_TLS_HOST must all be set to enable the TLS origin; continuing with http only");
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const [key, cert] = await Promise.all([readFile(keyPath, "utf8"), readFile(certPath, "utf8")]);
|
|
25
|
+
return {
|
|
26
|
+
key,
|
|
27
|
+
cert,
|
|
28
|
+
hostname
|
|
29
|
+
};
|
|
30
|
+
} catch (err) {
|
|
31
|
+
log(`forward: could not read TLS key/cert (${err instanceof Error ? err.message : String(err)}); continuing with http only`);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The default cert source for both tunnel surfaces: an explicit env/file
|
|
38
|
+
* configuration wins (BYO cert — also the escape hatch for machines where the
|
|
39
|
+
* trust prompt is unwanted, e.g. CI), otherwise the fully automatic
|
|
40
|
+
* devcert-backed localhost source.
|
|
41
|
+
*/
|
|
42
|
+
function defaultTlsCertSource(env, log) {
|
|
43
|
+
const fromEnv = envTlsCertSource(env, log);
|
|
44
|
+
return async () => {
|
|
45
|
+
if (env.SKYDIVE_PORTAL_TLS_KEY || env.SKYDIVE_PORTAL_TLS_CERT || env.SKYDIVE_PORTAL_TLS_HOST) return fromEnv();
|
|
46
|
+
const { localhostCertSource } = await import("./localhost-cert-Bn-UBUmj.mjs");
|
|
47
|
+
return localhostCertSource(log)();
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Detect resolver-level DNS rebind protection: some routers/resolvers refuse
|
|
52
|
+
* public names that answer with loopback addresses (Plex documents this exact
|
|
53
|
+
* failure for *.plex.direct). The https origin simply won't resolve for such
|
|
54
|
+
* users — warn with the cause, and leave the http origin (unaffected) as the
|
|
55
|
+
* fallback. Never fatal: the browser may use a different resolver than the
|
|
56
|
+
* OS, so we start the listener regardless.
|
|
57
|
+
*/
|
|
58
|
+
async function warnOnRebindProtection(hostname, log) {
|
|
59
|
+
try {
|
|
60
|
+
if (!(await dns.promises.lookup(hostname, { all: true })).some((a) => a.address === "127.0.0.1" || a.address === "::1")) log(`forward: ${hostname} did not resolve to 127.0.0.1 — a resolver on this network may block public names pointing at loopback (DNS rebind protection). The https origin may not work here; http://localhost is unaffected.`);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
log(`forward: ${hostname} did not resolve (${error instanceof Error ? error.message : String(error)}) — a resolver on this network may block public names pointing at loopback (DNS rebind protection). The https origin may not work here; http://localhost is unaffected.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { envTlsCertSource as n, warnOnRebindProtection as r, defaultTlsCertSource as t };
|
package/package.json
CHANGED
package/dist/js/api-DG5W6iwx.mjs
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
//#region ../portal-daemon/src/api.ts
|
|
6
|
-
/**
|
|
7
|
-
* The portal's session-authed REST surface, shared by `PortalClient` (the
|
|
8
|
-
* TUI/`portal open` connection) and the `skydive portal` management
|
|
9
|
-
* commands, so the endpoint contracts and response schemas live in exactly
|
|
10
|
-
* one place.
|
|
11
|
-
*/
|
|
12
|
-
const deviceSchema = z.object({
|
|
13
|
-
id: z.string(),
|
|
14
|
-
machineName: z.string(),
|
|
15
|
-
friendlyName: z.string(),
|
|
16
|
-
connected: z.boolean(),
|
|
17
|
-
lastSeen: z.string().nullable(),
|
|
18
|
-
grantedAgentIds: z.array(z.string())
|
|
19
|
-
});
|
|
20
|
-
const devicesResponseSchema = z.object({
|
|
21
|
-
devices: z.array(deviceSchema),
|
|
22
|
-
agents: z.array(z.object({
|
|
23
|
-
id: z.string(),
|
|
24
|
-
name: z.string()
|
|
25
|
-
}))
|
|
26
|
-
});
|
|
27
|
-
const deviceTokenSchema = z.object({ token: z.string().min(1) });
|
|
28
|
-
async function portalFetch(auth, path, init) {
|
|
29
|
-
const res = await fetch(`${auth.appUrl}${path}`, {
|
|
30
|
-
method: init.method,
|
|
31
|
-
headers: {
|
|
32
|
-
authorization: `Bearer ${auth.sessionToken}`,
|
|
33
|
-
accept: "application/json",
|
|
34
|
-
...init.body ? { "content-type": "application/json" } : {}
|
|
35
|
-
},
|
|
36
|
-
...init.body ? { body: init.body } : {}
|
|
37
|
-
});
|
|
38
|
-
if (!res.ok) {
|
|
39
|
-
const body = await res.text().catch(() => "");
|
|
40
|
-
throw new HttpError(res.status, body);
|
|
41
|
-
}
|
|
42
|
-
return res.json();
|
|
43
|
-
}
|
|
44
|
-
async function fetchPortalDevices(auth) {
|
|
45
|
-
const json = await portalFetch(auth, "/api/v1/portal/devices", { method: "GET" });
|
|
46
|
-
return devicesResponseSchema.parse(json);
|
|
47
|
-
}
|
|
48
|
-
const registerResponseSchema = z.object({ device: z.object({ id: z.string() }) });
|
|
49
|
-
/**
|
|
50
|
-
* Register this machine's device row without connecting. Connecting registers
|
|
51
|
-
* as a side effect; this covers granting an agent on a machine that has never
|
|
52
|
-
* shared yet (the grant references the device row).
|
|
53
|
-
*/
|
|
54
|
-
async function registerPortalDevice(auth, { machineName, friendlyName }) {
|
|
55
|
-
const json = await portalFetch(auth, "/api/v1/portal/devices", {
|
|
56
|
-
method: "POST",
|
|
57
|
-
body: JSON.stringify({
|
|
58
|
-
machineName,
|
|
59
|
-
friendlyName
|
|
60
|
-
})
|
|
61
|
-
});
|
|
62
|
-
return registerResponseSchema.parse(json).device;
|
|
63
|
-
}
|
|
64
|
-
/** Short-lived token the machine presents when dialing the portal WebSocket. */
|
|
65
|
-
async function mintPortalDeviceToken(auth) {
|
|
66
|
-
const json = await portalFetch(auth, "/api/v1/portal/device-token", { method: "POST" });
|
|
67
|
-
return deviceTokenSchema.parse(json).token;
|
|
68
|
-
}
|
|
69
|
-
const forwardTargetSchema = z.object({
|
|
70
|
-
daemonOrigin: z.string().min(1),
|
|
71
|
-
token: z.string().min(1),
|
|
72
|
-
expiresInSeconds: z.number()
|
|
73
|
-
});
|
|
74
|
-
/**
|
|
75
|
-
* Everything `portal forward` needs to dial an agent's sandbox daemon through
|
|
76
|
-
* the agent-webserver edge Worker: the daemon's stable public origin and a
|
|
77
|
-
* daemon auth token (canUse-gated server-side).
|
|
78
|
-
*/
|
|
79
|
-
async function fetchForwardTarget(auth, agentId) {
|
|
80
|
-
const json = await portalFetch(auth, "/api/v1/portal/forward-target", {
|
|
81
|
-
method: "POST",
|
|
82
|
-
body: JSON.stringify({ agentId })
|
|
83
|
-
});
|
|
84
|
-
return forwardTargetSchema.parse(json);
|
|
85
|
-
}
|
|
86
|
-
async function grantPortalAccess(auth, { deviceId, agentId, conversationId }) {
|
|
87
|
-
await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants`, {
|
|
88
|
-
method: "POST",
|
|
89
|
-
body: JSON.stringify({
|
|
90
|
-
agentId,
|
|
91
|
-
conversationId
|
|
92
|
-
})
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
async function revokePortalAccess(auth, { deviceId, agentId }) {
|
|
96
|
-
await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants/${encodeURIComponent(agentId)}`, { method: "DELETE" });
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* The device row for a given machine identity. Matching is by `machineName`
|
|
100
|
-
* equality — the stable handle the machine registers under, not the display
|
|
101
|
-
* label.
|
|
102
|
-
*/
|
|
103
|
-
function findThisDevice(devices, machineName) {
|
|
104
|
-
return devices.find((device) => device.machineName === machineName) ?? null;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* One-time grant migration onto the merged device. Earlier CLI builds
|
|
108
|
-
* registered a separate `<machineName>-cli` device, so a user's existing
|
|
109
|
-
* approvals hang off that row; the merged device would start with zero grants
|
|
110
|
-
* and every already-authorized agent would ask again. Copy any grant the
|
|
111
|
-
* merged device is missing (the grant endpoint upserts, so re-runs are
|
|
112
|
-
* no-ops). The legacy row is left in place — an old CLI build may still
|
|
113
|
-
* connect under it. Returns how many grants were copied.
|
|
114
|
-
*/
|
|
115
|
-
async function unifyLegacyCliGrants(auth, machineName) {
|
|
116
|
-
const { devices } = await fetchPortalDevices(auth);
|
|
117
|
-
const merged = findThisDevice(devices, machineName);
|
|
118
|
-
const legacy = findThisDevice(devices, `${machineName}-cli`);
|
|
119
|
-
if (!merged || !legacy) return 0;
|
|
120
|
-
const have = new Set(merged.grantedAgentIds);
|
|
121
|
-
const missing = legacy.grantedAgentIds.filter((id) => !have.has(id));
|
|
122
|
-
for (const agentId of missing) await grantPortalAccess(auth, {
|
|
123
|
-
deviceId: merged.id,
|
|
124
|
-
agentId,
|
|
125
|
-
conversationId: null
|
|
126
|
-
});
|
|
127
|
-
return missing.length;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
//#endregion
|
|
131
|
-
export { mintPortalDeviceToken as a, unifyLegacyCliGrants as c, grantPortalAccess as i, fetchPortalDevices as n, registerPortalDevice as o, findThisDevice as r, revokePortalAccess as s, fetchForwardTarget as t };
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import "./client-DbqRBquD.mjs";
|
|
3
|
-
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-BTyg329O.mjs";
|
|
4
|
-
import "./api-DG5W6iwx.mjs";
|
|
5
|
-
|
|
6
|
-
export { runPortalDaemon };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|