skydive-cli 0.4.1-beta.5 → 0.5.0-beta.1
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/bin.mjs +10 -10
- package/dist/js/{boot-BMxgenOa.mjs → boot-BvmABDme.mjs} +30 -10
- package/dist/js/client-CKBQ8pft.mjs +4 -0
- package/dist/js/{client-DaZyQfQ-.mjs → client-Dd5sMXPv.mjs} +46 -8
- package/dist/js/{daemon-DpqP-4tS.mjs → daemon-C6yadKp2.mjs} +109 -24
- package/dist/js/{daemon-BKVPOhzU.mjs → daemon-XCciQ1mS.mjs} +2 -2
- package/dist/js/daemon-client-CgziZU3X.mjs +6 -0
- package/dist/js/{daemon-client-F7gX12Mp.mjs → daemon-client-Rr6PJvRg.mjs} +24 -3
- package/dist/js/{print-share-Jgn0lqGr.mjs → print-share-C1LPsgTP.mjs} +5 -2
- package/dist/js/{profiler-BoEx7ARj.mjs → profiler-DHQhmoSF.mjs} +1 -1
- package/package.json +1 -1
- package/dist/js/client-BsTDaKa2.mjs +0 -4
- package/dist/js/daemon-client-TQuXEURW.mjs +0 -6
package/dist/js/bin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as ensureActiveOrganization, D as setActiveWorkspace, E as listWorkspaces, O as name, T as getSessionIdentity, a as installCrashHandler, k as version, t as maybeStartProfiling, u as brandHelpArt, w as getActiveWorkspaceId, x as themes } from "./profiler-
|
|
2
|
+
import { C as ensureActiveOrganization, D as setActiveWorkspace, E as listWorkspaces, O as name, T as getSessionIdentity, a as installCrashHandler, k as version, t as maybeStartProfiling, u as brandHelpArt, w as getActiveWorkspaceId, x as themes } from "./profiler-DHQhmoSF.mjs";
|
|
3
3
|
import { A as getStoredApiKeyWorkspaceName, C as getLastSeenVersion, F as resolveManagementAuth, I as resolveSession, M as resolveAppUrl, N as resolveChatAuth, O as getShareMachineDefault, P as resolveConfig, R as saveConfig, S as getConfigPath, T as getPromptHistoryPath, V as setLastSeenVersion, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getUpdateCheckDisabled, k as getStoredApiKeyId, v as DEFAULT_API_URL, w as getPreference, x as deleteConfig, z as saveSession } from "./print-CbayCa87.mjs";
|
|
4
4
|
import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
|
|
5
5
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
6
6
|
import { t as createRestClient } from "./rest-BY2nADw5.mjs";
|
|
7
7
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
8
|
-
import { a as registerPortalDevice, c as resolveMachineIdentity, i as grantPortalAccess, n as fetchPortalDevices, o as revokePortalAccess, r as findThisDevice } from "./client-
|
|
9
|
-
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-
|
|
8
|
+
import { a as registerPortalDevice, c as resolveMachineIdentity, i as grantPortalAccess, n as fetchPortalDevices, o as revokePortalAccess, r as findThisDevice } from "./client-Dd5sMXPv.mjs";
|
|
9
|
+
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-C6yadKp2.mjs";
|
|
10
10
|
import { t as SandboxStream } from "./client-Cn2af31H.mjs";
|
|
11
11
|
import { hideBin } from "yargs/helpers";
|
|
12
12
|
import yargs from "yargs";
|
|
@@ -1516,7 +1516,7 @@ const importCommand = {
|
|
|
1516
1516
|
process.exit(1);
|
|
1517
1517
|
}
|
|
1518
1518
|
}
|
|
1519
|
-
const { runChat } = await import("./boot-
|
|
1519
|
+
const { runChat } = await import("./boot-BvmABDme.mjs");
|
|
1520
1520
|
await runChat({
|
|
1521
1521
|
appUrl,
|
|
1522
1522
|
sessionToken: session.value.sessionToken,
|
|
@@ -1543,7 +1543,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1543
1543
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1544
1544
|
process.exit(1);
|
|
1545
1545
|
}
|
|
1546
|
-
const { connectMachineShare } = await import("./print-share-
|
|
1546
|
+
const { connectMachineShare } = await import("./print-share-C1LPsgTP.mjs");
|
|
1547
1547
|
const machineShare = await connectMachineShare({
|
|
1548
1548
|
appUrl,
|
|
1549
1549
|
sessionToken: session.value.sessionToken,
|
|
@@ -1928,7 +1928,7 @@ const chatCommand = {
|
|
|
1928
1928
|
sessionToken: auth.value.token,
|
|
1929
1929
|
agentSelector: argv.agent ?? null
|
|
1930
1930
|
});
|
|
1931
|
-
const { runChat } = await import("./boot-
|
|
1931
|
+
const { runChat } = await import("./boot-BvmABDme.mjs");
|
|
1932
1932
|
await runChat({
|
|
1933
1933
|
appUrl: auth.value.appUrl,
|
|
1934
1934
|
sessionToken: auth.value.token,
|
|
@@ -2020,7 +2020,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2020
2020
|
process.exit(1);
|
|
2021
2021
|
}
|
|
2022
2022
|
} else {
|
|
2023
|
-
const { connectMachineShare } = await import("./print-share-
|
|
2023
|
+
const { connectMachineShare } = await import("./print-share-C1LPsgTP.mjs");
|
|
2024
2024
|
machineShare = await connectMachineShare({
|
|
2025
2025
|
appUrl: auth.value.appUrl,
|
|
2026
2026
|
sessionToken: auth.value.token,
|
|
@@ -2287,7 +2287,7 @@ const switchCommand = {
|
|
|
2287
2287
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
2288
2288
|
process.exit(1);
|
|
2289
2289
|
}
|
|
2290
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2290
|
+
const { runWorkspacePicker } = await import("./boot-BvmABDme.mjs");
|
|
2291
2291
|
await runWorkspacePicker(session);
|
|
2292
2292
|
return;
|
|
2293
2293
|
}
|
|
@@ -2366,7 +2366,7 @@ const openCommand = {
|
|
|
2366
2366
|
const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
2367
2367
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
2368
2368
|
const { machineName } = await resolveMachineIdentity();
|
|
2369
|
-
const { PortalDaemonClient } = await import("./daemon-client-
|
|
2369
|
+
const { PortalDaemonClient } = await import("./daemon-client-CgziZU3X.mjs");
|
|
2370
2370
|
let lastLine = "";
|
|
2371
2371
|
let signalConnected;
|
|
2372
2372
|
const connected = new Promise((resolve) => {
|
|
@@ -4169,7 +4169,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
|
4169
4169
|
process.exit(0);
|
|
4170
4170
|
}
|
|
4171
4171
|
if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
|
|
4172
|
-
const { runPortalDaemon } = await import("./daemon-
|
|
4172
|
+
const { runPortalDaemon } = await import("./daemon-XCciQ1mS.mjs");
|
|
4173
4173
|
runPortalDaemon(process.argv);
|
|
4174
4174
|
} else runCli();
|
|
4175
4175
|
function runCli() {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-
|
|
2
|
+
import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-DHQhmoSF.mjs";
|
|
3
3
|
import { B as saveTheme, D as getSavedTheme, E as getReviewStateDir, L as resolveWebUrl, S as getConfigPath, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams, v as DEFAULT_API_URL, y as DEFAULT_APP_URL } from "./print-CbayCa87.mjs";
|
|
4
4
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
5
5
|
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-BY2nADw5.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
|
|
7
|
-
import { t as PortalClient } from "./client-
|
|
8
|
-
import "./daemon-
|
|
7
|
+
import { t as PortalClient } from "./client-Dd5sMXPv.mjs";
|
|
8
|
+
import "./daemon-C6yadKp2.mjs";
|
|
9
9
|
import { t as SandboxStream } from "./client-Cn2af31H.mjs";
|
|
10
|
-
import { t as PortalDaemonClient } from "./daemon-client-
|
|
10
|
+
import { t as PortalDaemonClient } from "./daemon-client-Rr6PJvRg.mjs";
|
|
11
11
|
import { t as runRawPtyPassthrough } from "./raw-pty-B6mAroiI.mjs";
|
|
12
12
|
import * as os$1 from "node:os";
|
|
13
13
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
@@ -109,6 +109,7 @@ const initialPortalUi = {
|
|
|
109
109
|
machineName: null,
|
|
110
110
|
error: null,
|
|
111
111
|
grantedAgentIds: [],
|
|
112
|
+
declinedAgentIds: [],
|
|
112
113
|
prompt: null
|
|
113
114
|
};
|
|
114
115
|
function initialChatTitle(screen) {
|
|
@@ -181,12 +182,13 @@ const useStore = create((set, get) => ({
|
|
|
181
182
|
chatTitle: null
|
|
182
183
|
}),
|
|
183
184
|
setPortalClient: (portalClient) => set({ portalClient }),
|
|
184
|
-
syncPortal: ({ status, machineName, error, grantedAgentIds }) => set((state) => ({ portal: {
|
|
185
|
+
syncPortal: ({ status, machineName, error, grantedAgentIds, declinedAgentIds }) => set((state) => ({ portal: {
|
|
185
186
|
...state.portal,
|
|
186
187
|
status,
|
|
187
188
|
machineName,
|
|
188
189
|
error,
|
|
189
|
-
grantedAgentIds
|
|
190
|
+
grantedAgentIds,
|
|
191
|
+
declinedAgentIds
|
|
190
192
|
} })),
|
|
191
193
|
setPortalPrompt: (prompt) => set((state) => ({ portal: {
|
|
192
194
|
...state.portal,
|
|
@@ -317,14 +319,18 @@ function createPortalClient(opts) {
|
|
|
317
319
|
if (portalDaemonDisabled()) {
|
|
318
320
|
const client = new PortalClient({
|
|
319
321
|
appUrl: opts.appUrl,
|
|
320
|
-
|
|
322
|
+
credentials: () => ({
|
|
323
|
+
sessionToken: opts.sessionToken,
|
|
324
|
+
deviceToken: null
|
|
325
|
+
}),
|
|
321
326
|
resolveCwd: () => process.cwd(),
|
|
322
327
|
onState: (state) => opts.onState({
|
|
323
328
|
status: state.status,
|
|
324
329
|
machineName: state.machineName,
|
|
325
330
|
friendlyName: state.friendlyName,
|
|
326
331
|
error: state.error,
|
|
327
|
-
grantedAgentIds: state.grantedAgentIds
|
|
332
|
+
grantedAgentIds: state.grantedAgentIds,
|
|
333
|
+
declinedAgentIds: []
|
|
328
334
|
})
|
|
329
335
|
});
|
|
330
336
|
return {
|
|
@@ -332,6 +338,9 @@ function createPortalClient(opts) {
|
|
|
332
338
|
enable: () => client.enable(),
|
|
333
339
|
disable: () => client.disable(),
|
|
334
340
|
grantAgent: (agentId) => client.grantAgent(agentId),
|
|
341
|
+
decline: (_agentId, declined) => {
|
|
342
|
+
if (declined && client.grantedAgentIds().length === 0) client.disable();
|
|
343
|
+
},
|
|
335
344
|
isGranted: (agentId) => client.isGranted(agentId),
|
|
336
345
|
bind: () => {},
|
|
337
346
|
dispose: () => client.dispose()
|
|
@@ -370,7 +379,8 @@ function usePortal({ appUrl, sessionToken, shareMachine, enabled = true }) {
|
|
|
370
379
|
status: state.status,
|
|
371
380
|
machineName: state.machineName,
|
|
372
381
|
error: state.error,
|
|
373
|
-
grantedAgentIds: state.grantedAgentIds
|
|
382
|
+
grantedAgentIds: state.grantedAgentIds,
|
|
383
|
+
declinedAgentIds: state.declinedAgentIds
|
|
374
384
|
})
|
|
375
385
|
});
|
|
376
386
|
setPortalClient(client);
|
|
@@ -11255,6 +11265,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11255
11265
|
}
|
|
11256
11266
|
if (portal.status === "off") {
|
|
11257
11267
|
declinedRef.current.delete(agent.id);
|
|
11268
|
+
portalClient.decline(agent.id, false);
|
|
11258
11269
|
portalClient.enable();
|
|
11259
11270
|
if (conversationId) portalClient.bind(conversationId, shellSessionRef.current?.cwd ?? process.cwd());
|
|
11260
11271
|
} else {
|
|
@@ -11288,9 +11299,14 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11288
11299
|
]);
|
|
11289
11300
|
const declineGrant = useCallback(() => {
|
|
11290
11301
|
declinedRef.current.add(agent.id);
|
|
11302
|
+
portalClient?.decline(agent.id, true);
|
|
11291
11303
|
setPortalPrompt(null);
|
|
11292
11304
|
useStore.getState().showToast({ message: "kept this machine private" });
|
|
11293
|
-
}, [
|
|
11305
|
+
}, [
|
|
11306
|
+
portalClient,
|
|
11307
|
+
agent.id,
|
|
11308
|
+
setPortalPrompt
|
|
11309
|
+
]);
|
|
11294
11310
|
const openPortalAccess = useCallback(() => {
|
|
11295
11311
|
if (!portalClient) {
|
|
11296
11312
|
useStore.getState().showToast({ message: useStore.getState().authKind === "api-key" ? "local access needs a browser session; run `skydive auth login --web`" : "local access is unavailable in this session" });
|
|
@@ -11299,6 +11315,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11299
11315
|
switch (portal.status) {
|
|
11300
11316
|
case "off":
|
|
11301
11317
|
declinedRef.current.delete(agent.id);
|
|
11318
|
+
portalClient.decline(agent.id, false);
|
|
11302
11319
|
portalClient.enable();
|
|
11303
11320
|
useStore.getState().showToast({ message: "enabling local access on this machine…" });
|
|
11304
11321
|
break;
|
|
@@ -11311,6 +11328,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11311
11328
|
break;
|
|
11312
11329
|
}
|
|
11313
11330
|
declinedRef.current.delete(agent.id);
|
|
11331
|
+
portalClient.decline(agent.id, false);
|
|
11314
11332
|
setPortalPrompt({
|
|
11315
11333
|
agentId: agent.id,
|
|
11316
11334
|
agentName: agent.name
|
|
@@ -11515,6 +11533,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11515
11533
|
useEffect(() => {
|
|
11516
11534
|
if (!portalClient || portal.status !== "connected") return;
|
|
11517
11535
|
if (portal.grantedAgentIds.includes(agent.id)) return;
|
|
11536
|
+
if (portal.declinedAgentIds.includes(agent.id)) return;
|
|
11518
11537
|
if (declinedRef.current.has(agent.id)) return;
|
|
11519
11538
|
if (portal.prompt?.agentId === agent.id) return;
|
|
11520
11539
|
setPortalPrompt({
|
|
@@ -11532,6 +11551,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11532
11551
|
portalClient,
|
|
11533
11552
|
portal.status,
|
|
11534
11553
|
portal.grantedAgentIds,
|
|
11554
|
+
portal.declinedAgentIds,
|
|
11535
11555
|
portal.prompt,
|
|
11536
11556
|
agent.id,
|
|
11537
11557
|
agent.name,
|
|
@@ -58,12 +58,18 @@ const INHERITED_ENV = [
|
|
|
58
58
|
"TERM",
|
|
59
59
|
"PATH"
|
|
60
60
|
];
|
|
61
|
+
const DARWIN_PATH_PREPEND = ["/opt/homebrew/bin", "/usr/local/bin"];
|
|
61
62
|
function buildEnv(extra) {
|
|
62
63
|
const env = {};
|
|
63
64
|
for (const key of INHERITED_ENV) {
|
|
64
65
|
const value = process.env[key];
|
|
65
66
|
if (value !== void 0) env[key] = value;
|
|
66
67
|
}
|
|
68
|
+
if (process.platform === "darwin") {
|
|
69
|
+
const current = env.PATH ? env.PATH.split(":") : [];
|
|
70
|
+
const missing = DARWIN_PATH_PREPEND.filter((dir) => !current.includes(dir));
|
|
71
|
+
if (missing.length > 0) env.PATH = [...missing, ...current].join(":");
|
|
72
|
+
}
|
|
67
73
|
if (extra) for (const [key, value] of Object.entries(extra)) env[key] = value;
|
|
68
74
|
return env;
|
|
69
75
|
}
|
|
@@ -133,7 +139,8 @@ const ctrlMessageSchema = z.discriminatedUnion("t", [
|
|
|
133
139
|
z.object({ t: z.literal("cancel") }),
|
|
134
140
|
z.object({
|
|
135
141
|
t: z.literal("close"),
|
|
136
|
-
exitCode: z.number()
|
|
142
|
+
exitCode: z.number(),
|
|
143
|
+
frames: z.number().int().nonnegative().optional()
|
|
137
144
|
}),
|
|
138
145
|
z.object({
|
|
139
146
|
t: z.literal("error"),
|
|
@@ -277,7 +284,8 @@ var JobManager = class {
|
|
|
277
284
|
this.jobs.delete(id);
|
|
278
285
|
this.opts.send(encodeCtrl(id, {
|
|
279
286
|
t: "close",
|
|
280
|
-
exitCode: code ?? -1
|
|
287
|
+
exitCode: code ?? -1,
|
|
288
|
+
frames: job.seq
|
|
281
289
|
}));
|
|
282
290
|
});
|
|
283
291
|
}
|
|
@@ -428,6 +436,10 @@ var PortalClient = class {
|
|
|
428
436
|
isGranted(agentId) {
|
|
429
437
|
return this.granted.has(agentId);
|
|
430
438
|
}
|
|
439
|
+
/** Agents currently authorized on this machine (last known server state). */
|
|
440
|
+
grantedAgentIds() {
|
|
441
|
+
return [...this.granted];
|
|
442
|
+
}
|
|
431
443
|
enable() {
|
|
432
444
|
if (this.enabled || this.disposed) return;
|
|
433
445
|
this.enabled = true;
|
|
@@ -459,14 +471,36 @@ var PortalClient = class {
|
|
|
459
471
|
}
|
|
460
472
|
/** Grant one agent access to this machine (default-deny; user-initiated). */
|
|
461
473
|
async grantAgent(agentId) {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
474
|
+
const auth = this.sessionAuth();
|
|
475
|
+
if (!auth) throw new Error("granting needs a signed-in CLI session on this machine (the desktop connection alone cannot manage grants)");
|
|
476
|
+
await grantPortalAccess(auth, {
|
|
477
|
+
deviceId: await this.ensureDeviceId(),
|
|
465
478
|
agentId
|
|
466
479
|
});
|
|
467
480
|
this.granted.add(agentId);
|
|
468
481
|
this.emit();
|
|
469
482
|
}
|
|
483
|
+
/** Session-authed REST auth, or null when only a device token is held. */
|
|
484
|
+
sessionAuth() {
|
|
485
|
+
const { sessionToken } = this.opts.credentials();
|
|
486
|
+
return sessionToken ? {
|
|
487
|
+
appUrl: this.opts.appUrl,
|
|
488
|
+
sessionToken
|
|
489
|
+
} : null;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The bearer token for the portal WebSocket dial: minted from the session
|
|
493
|
+
* when one is held, otherwise the pre-minted device token as-is.
|
|
494
|
+
*/
|
|
495
|
+
async wsToken() {
|
|
496
|
+
const creds = this.opts.credentials();
|
|
497
|
+
if (creds.sessionToken) return mintPortalDeviceToken({
|
|
498
|
+
appUrl: this.opts.appUrl,
|
|
499
|
+
sessionToken: creds.sessionToken
|
|
500
|
+
});
|
|
501
|
+
if (creds.deviceToken) return creds.deviceToken;
|
|
502
|
+
throw new Error("no portal credentials (not signed in)");
|
|
503
|
+
}
|
|
470
504
|
setStatus(status, error = null) {
|
|
471
505
|
this.status = status;
|
|
472
506
|
this.error = error;
|
|
@@ -491,7 +525,7 @@ var PortalClient = class {
|
|
|
491
525
|
while (this.enabled && !this.disposed) {
|
|
492
526
|
this.setStatus("connecting");
|
|
493
527
|
try {
|
|
494
|
-
const token = await
|
|
528
|
+
const token = await this.wsToken();
|
|
495
529
|
await this.runConnection(token);
|
|
496
530
|
backoff = INITIAL_BACKOFF_MS;
|
|
497
531
|
} catch (err) {
|
|
@@ -545,8 +579,10 @@ var PortalClient = class {
|
|
|
545
579
|
throw new Error("this machine is not connected yet");
|
|
546
580
|
}
|
|
547
581
|
async refreshDevice() {
|
|
582
|
+
const auth = this.sessionAuth();
|
|
583
|
+
if (!auth) return;
|
|
548
584
|
try {
|
|
549
|
-
const { devices } = await fetchPortalDevices(
|
|
585
|
+
const { devices } = await fetchPortalDevices(auth);
|
|
550
586
|
const mine = findThisDevice(devices, this.machineName);
|
|
551
587
|
if (!mine) return;
|
|
552
588
|
this.deviceId = mine.id;
|
|
@@ -563,9 +599,11 @@ var PortalClient = class {
|
|
|
563
599
|
async syncDeviceState() {
|
|
564
600
|
await this.refreshDevice();
|
|
565
601
|
if (this.legacyGrantsChecked) return;
|
|
602
|
+
const auth = this.sessionAuth();
|
|
603
|
+
if (!auth) return;
|
|
566
604
|
this.legacyGrantsChecked = true;
|
|
567
605
|
try {
|
|
568
|
-
if (await unifyLegacyCliGrants(
|
|
606
|
+
if (await unifyLegacyCliGrants(auth, this.machineName) > 0) await this.refreshDevice();
|
|
569
607
|
} catch (_error) {}
|
|
570
608
|
}
|
|
571
609
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { s as isRecord, t as PortalClient } from "./client-
|
|
2
|
+
import { s as isRecord, t as PortalClient } from "./client-Dd5sMXPv.mjs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -47,14 +47,35 @@ const LOCAL_PROTOCOL_VERSION = 1;
|
|
|
47
47
|
*/
|
|
48
48
|
const PORTAL_DAEMON_FLAG = "--skydive-internal-portal-daemon";
|
|
49
49
|
/**
|
|
50
|
+
* One daemon per machine requires one socket key per backend — but the prod
|
|
51
|
+
* backend is reachable under three hosts: the CLI's default appUrl is
|
|
52
|
+
* `https://api.skydive.com` while the desktop app carries
|
|
53
|
+
* `https://skydive.com` (and the web is on www). Hashing those raw strings
|
|
54
|
+
* would give each surface its own daemon, and with the merged machine
|
|
55
|
+
* identity both daemons would register the SAME device — the presence
|
|
56
|
+
* contention the daemon exists to prevent. Collapse the known prod aliases to
|
|
57
|
+
* the CLI's canonical form (proven for both the REST surface and the WS
|
|
58
|
+
* upgrade; the apex 301-redirects, which a WS upgrade cannot follow).
|
|
59
|
+
* Non-prod URLs (previews, localhost) pass through untouched apart from
|
|
60
|
+
* trailing-slash trimming.
|
|
61
|
+
*/
|
|
62
|
+
function canonicalPortalAppUrl(appUrl) {
|
|
63
|
+
const trimmed = appUrl.replace(/\/+$/, "");
|
|
64
|
+
const host = trimmed.replace(/^https?:\/\//, "").toLowerCase();
|
|
65
|
+
if (host === "skydive.com" || host === "www.skydive.com" || host === "api.skydive.com") return "https://api.skydive.com";
|
|
66
|
+
return trimmed;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
50
69
|
* Per-user, per-app daemon socket + state paths. Scoped by OS user and a hash
|
|
51
|
-
* of the appUrl so two logins or two deployments (prod vs a PR
|
|
52
|
-
* distinct daemons and never share a portal identity
|
|
53
|
-
*
|
|
70
|
+
* of the CANONICAL appUrl so two logins or two deployments (prod vs a PR
|
|
71
|
+
* preview) get distinct daemons and never share a portal identity — while
|
|
72
|
+
* every alias of one deployment (see {@link canonicalPortalAppUrl}) shares
|
|
73
|
+
* one. Kept under the OS temp dir because Unix socket paths are
|
|
74
|
+
* length-limited (~104 bytes on macOS).
|
|
54
75
|
*/
|
|
55
76
|
function daemonPaths(appUrl) {
|
|
56
77
|
const user = safeSegment(os.userInfo().username || "user");
|
|
57
|
-
const app = createHash("sha256").update(appUrl).digest("hex").slice(0, 12);
|
|
78
|
+
const app = createHash("sha256").update(canonicalPortalAppUrl(appUrl)).digest("hex").slice(0, 12);
|
|
58
79
|
const dir = path.join(os.tmpdir(), `skydive-portal-${user}-${app}`);
|
|
59
80
|
return {
|
|
60
81
|
dir,
|
|
@@ -67,17 +88,30 @@ function safeSegment(value) {
|
|
|
67
88
|
return value.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 24) || "user";
|
|
68
89
|
}
|
|
69
90
|
/**
|
|
70
|
-
* `hello` — a
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
91
|
+
* `hello` — a client attaches. `sessionId` is unique per client process.
|
|
92
|
+
* `token` authenticates the daemon's portal connection; `tokenKind` says what
|
|
93
|
+
* it is:
|
|
94
|
+
*
|
|
95
|
+
* - `session` — a CLI session token. The daemon mints short-lived portal
|
|
96
|
+
* device tokens from it, and can call the session-authed portal REST
|
|
97
|
+
* surface (device refresh, grants).
|
|
98
|
+
* - `device` — a pre-minted portal device token (the desktop app is handed
|
|
99
|
+
* one by the web bridge and holds no session of its own). Dials the portal
|
|
100
|
+
* WebSocket directly; the session-only REST surface is unavailable until
|
|
101
|
+
* some client supplies a session token.
|
|
102
|
+
*
|
|
103
|
+
* The daemon uses the FIRST hello's token to connect and refreshes from later
|
|
104
|
+
* hellos so a long-lived daemon can outlive the client that spawned it.
|
|
105
|
+
* `tokenKind` defaults to `session` on parse: hellos from CLI builds that
|
|
106
|
+
* predate the field are session-token clients by definition, and the default
|
|
107
|
+
* keeps them attaching to a newer daemon.
|
|
75
108
|
*/
|
|
76
109
|
const clientHelloSchema = z.object({
|
|
77
110
|
t: z.literal("hello"),
|
|
78
111
|
v: z.number(),
|
|
79
112
|
sessionId: z.string().min(1),
|
|
80
|
-
token: z.string().min(1)
|
|
113
|
+
token: z.string().min(1),
|
|
114
|
+
tokenKind: z.enum(["session", "device"]).default("session")
|
|
81
115
|
});
|
|
82
116
|
/**
|
|
83
117
|
* `bind` — associate a conversation with a working directory. Sent on chat
|
|
@@ -109,6 +143,20 @@ const clientGrantSchema = z.object({
|
|
|
109
143
|
t: z.literal("grant"),
|
|
110
144
|
agentId: z.string().min(1)
|
|
111
145
|
});
|
|
146
|
+
/**
|
|
147
|
+
* `decline` — the user answered no to an agent's request for this machine
|
|
148
|
+
* (`declined: true`), or re-opened the door for it (`declined: false`).
|
|
149
|
+
*
|
|
150
|
+
* The daemon holds this because a decline is about the MACHINE, not one
|
|
151
|
+
* terminal: every attached CLI raises its own grant prompt off the daemon's
|
|
152
|
+
* shared state, so a per-process no would be re-asked by every other open
|
|
153
|
+
* conversation and by the next `skydive` launch.
|
|
154
|
+
*/
|
|
155
|
+
const clientDeclineSchema = z.object({
|
|
156
|
+
t: z.literal("decline"),
|
|
157
|
+
agentId: z.string().min(1),
|
|
158
|
+
declined: z.boolean()
|
|
159
|
+
});
|
|
112
160
|
/** `bye` — a CLI detaches cleanly (its conversations drop from the map). */
|
|
113
161
|
const clientByeSchema = z.object({
|
|
114
162
|
t: z.literal("bye"),
|
|
@@ -133,6 +181,7 @@ const clientMessageSchema = z.discriminatedUnion("t", [
|
|
|
133
181
|
clientDisableSchema,
|
|
134
182
|
clientCwdSchema,
|
|
135
183
|
clientGrantSchema,
|
|
184
|
+
clientDeclineSchema,
|
|
136
185
|
clientByeSchema,
|
|
137
186
|
clientStatusSchema,
|
|
138
187
|
clientShutdownSchema
|
|
@@ -152,7 +201,8 @@ const daemonStateSchema = z.object({
|
|
|
152
201
|
machineName: z.string(),
|
|
153
202
|
friendlyName: z.string(),
|
|
154
203
|
error: z.string().nullable(),
|
|
155
|
-
grantedAgentIds: z.array(z.string())
|
|
204
|
+
grantedAgentIds: z.array(z.string()),
|
|
205
|
+
declinedAgentIds: z.array(z.string()).default([])
|
|
156
206
|
});
|
|
157
207
|
/** `hello_ok` — the daemon accepts an attach and reports its protocol version. */
|
|
158
208
|
const daemonHelloOkSchema = z.object({
|
|
@@ -180,7 +230,8 @@ const daemonStatusResultSchema = z.object({
|
|
|
180
230
|
machineName: z.string(),
|
|
181
231
|
friendlyName: z.string(),
|
|
182
232
|
error: z.string().nullable(),
|
|
183
|
-
grantedAgentIds: z.array(z.string())
|
|
233
|
+
grantedAgentIds: z.array(z.string()),
|
|
234
|
+
declinedAgentIds: z.array(z.string()).default([])
|
|
184
235
|
}),
|
|
185
236
|
clientCount: z.number(),
|
|
186
237
|
cwds: z.record(z.string(), z.string())
|
|
@@ -254,18 +305,21 @@ function parseDaemonMessage(line) {
|
|
|
254
305
|
/** Grace period after the last client detaches before the daemon exits. */
|
|
255
306
|
const IDLE_SHUTDOWN_MS = 3e4;
|
|
256
307
|
var PortalDaemon = class {
|
|
308
|
+
appUrl;
|
|
257
309
|
paths;
|
|
258
310
|
server = null;
|
|
259
311
|
client = null;
|
|
260
312
|
lastState = null;
|
|
261
313
|
conns = /* @__PURE__ */ new Set();
|
|
262
314
|
cwds = /* @__PURE__ */ new Map();
|
|
315
|
+
declined = /* @__PURE__ */ new Set();
|
|
263
316
|
fallbackCwd;
|
|
264
317
|
idleTimer = null;
|
|
265
318
|
sessionToken = null;
|
|
319
|
+
deviceToken = null;
|
|
266
320
|
constructor(appUrl) {
|
|
267
|
-
this.appUrl = appUrl;
|
|
268
|
-
this.paths = daemonPaths(appUrl);
|
|
321
|
+
this.appUrl = canonicalPortalAppUrl(appUrl);
|
|
322
|
+
this.paths = daemonPaths(this.appUrl);
|
|
269
323
|
this.fallbackCwd = process.env.HOME ?? process.cwd();
|
|
270
324
|
}
|
|
271
325
|
/** Start listening. Rejects if the socket is already held by another daemon. */
|
|
@@ -319,13 +373,14 @@ var PortalDaemon = class {
|
|
|
319
373
|
switch (msg.t) {
|
|
320
374
|
case "hello":
|
|
321
375
|
conn.sessionId = msg.sessionId;
|
|
322
|
-
this.
|
|
376
|
+
if (msg.tokenKind === "device") this.deviceToken = msg.token;
|
|
377
|
+
else this.sessionToken = msg.token;
|
|
323
378
|
this.ensureClient();
|
|
324
379
|
this.send(conn, {
|
|
325
380
|
t: "hello_ok",
|
|
326
381
|
v: LOCAL_PROTOCOL_VERSION
|
|
327
382
|
});
|
|
328
|
-
if (this.lastState) this.send(conn, stateMessage(this.lastState));
|
|
383
|
+
if (this.lastState) this.send(conn, stateMessage(this.lastState, this.declined));
|
|
329
384
|
return;
|
|
330
385
|
case "bind":
|
|
331
386
|
conn.conversations.add(msg.conversationId);
|
|
@@ -342,10 +397,19 @@ var PortalDaemon = class {
|
|
|
342
397
|
for (const other of this.conns) other.wantsShare = false;
|
|
343
398
|
this.client?.disable();
|
|
344
399
|
return;
|
|
400
|
+
case "decline":
|
|
401
|
+
if (msg.declined) {
|
|
402
|
+
this.declined.add(msg.agentId);
|
|
403
|
+
if ((this.lastState?.grantedAgentIds.length ?? 0) === 0) this.client?.disable();
|
|
404
|
+
} else this.declined.delete(msg.agentId);
|
|
405
|
+
this.persistState();
|
|
406
|
+
this.broadcastState();
|
|
407
|
+
return;
|
|
345
408
|
case "cwd":
|
|
346
409
|
this.fallbackCwd = msg.cwd;
|
|
347
410
|
return;
|
|
348
411
|
case "grant":
|
|
412
|
+
this.declined.delete(msg.agentId);
|
|
349
413
|
this.ensureClient();
|
|
350
414
|
this.client?.grantAgent(msg.agentId).catch((error) => {
|
|
351
415
|
this.logError("grantAgent failed", error);
|
|
@@ -375,14 +439,17 @@ var PortalDaemon = class {
|
|
|
375
439
|
}
|
|
376
440
|
/** Create the single PortalClient the first time a client needs the portal. */
|
|
377
441
|
ensureClient() {
|
|
378
|
-
if (this.client || !this.sessionToken) return;
|
|
442
|
+
if (this.client || !this.sessionToken && !this.deviceToken) return;
|
|
379
443
|
this.client = new PortalClient({
|
|
380
444
|
appUrl: this.appUrl,
|
|
381
|
-
|
|
445
|
+
credentials: () => ({
|
|
446
|
+
sessionToken: this.sessionToken,
|
|
447
|
+
deviceToken: this.deviceToken
|
|
448
|
+
}),
|
|
382
449
|
resolveCwd: (conversationId) => this.resolveCwd(conversationId),
|
|
383
450
|
onState: (state) => {
|
|
384
451
|
this.lastState = state;
|
|
385
|
-
this.
|
|
452
|
+
this.broadcastState();
|
|
386
453
|
}
|
|
387
454
|
});
|
|
388
455
|
}
|
|
@@ -400,6 +467,11 @@ var PortalDaemon = class {
|
|
|
400
467
|
broadcast(msg) {
|
|
401
468
|
for (const conn of this.conns) this.send(conn, msg);
|
|
402
469
|
}
|
|
470
|
+
/** Push the current portal state (plus the machine's declines) to every CLI. */
|
|
471
|
+
broadcastState() {
|
|
472
|
+
if (!this.lastState) return;
|
|
473
|
+
this.broadcast(stateMessage(this.lastState, this.declined));
|
|
474
|
+
}
|
|
403
475
|
armIdleTimer() {
|
|
404
476
|
this.clearIdleTimer();
|
|
405
477
|
this.idleTimer = setTimeout(() => this.shutdown(), IDLE_SHUTDOWN_MS);
|
|
@@ -417,6 +489,13 @@ var PortalDaemon = class {
|
|
|
417
489
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${context}: ${message}\n`;
|
|
418
490
|
appendFile(this.paths.logPath, line).catch((_error) => {});
|
|
419
491
|
}
|
|
492
|
+
/** Close the listener without exiting the process (tests own the process). */
|
|
493
|
+
stopForTest() {
|
|
494
|
+
this.clearIdleTimer();
|
|
495
|
+
this.client?.dispose();
|
|
496
|
+
this.server?.close();
|
|
497
|
+
this.server = null;
|
|
498
|
+
}
|
|
420
499
|
shutdown() {
|
|
421
500
|
if (this.conns.size > 0) return;
|
|
422
501
|
this.client?.dispose();
|
|
@@ -436,7 +515,8 @@ var PortalDaemon = class {
|
|
|
436
515
|
machineName: portal?.machineName ?? "",
|
|
437
516
|
friendlyName: portal?.friendlyName ?? "",
|
|
438
517
|
error: portal?.error ?? null,
|
|
439
|
-
grantedAgentIds: portal?.grantedAgentIds ?? []
|
|
518
|
+
grantedAgentIds: portal?.grantedAgentIds ?? [],
|
|
519
|
+
declinedAgentIds: [...this.declined]
|
|
440
520
|
},
|
|
441
521
|
clientCount: [...this.conns].filter((c) => c.sessionId !== null).length,
|
|
442
522
|
cwds: Object.fromEntries(this.cwds)
|
|
@@ -462,13 +542,17 @@ var PortalDaemon = class {
|
|
|
462
542
|
const parsed = JSON.parse(raw);
|
|
463
543
|
if (isRecord(parsed) && parsed.version === LOCAL_PROTOCOL_VERSION && isRecord(parsed.cwds)) {
|
|
464
544
|
for (const [id, cwd] of Object.entries(parsed.cwds)) if (typeof cwd === "string") this.cwds.set(id, cwd);
|
|
545
|
+
if (Array.isArray(parsed.declined)) {
|
|
546
|
+
for (const id of parsed.declined) if (typeof id === "string") this.declined.add(id);
|
|
547
|
+
}
|
|
465
548
|
}
|
|
466
549
|
} catch (_error) {}
|
|
467
550
|
}
|
|
468
551
|
persistState() {
|
|
469
552
|
const state = {
|
|
470
553
|
version: LOCAL_PROTOCOL_VERSION,
|
|
471
|
-
cwds: Object.fromEntries(this.cwds)
|
|
554
|
+
cwds: Object.fromEntries(this.cwds),
|
|
555
|
+
declined: [...this.declined]
|
|
472
556
|
};
|
|
473
557
|
writeFile(this.paths.statePath, JSON.stringify(state)).catch((error) => {
|
|
474
558
|
this.logError("persistState failed", error);
|
|
@@ -615,14 +699,15 @@ async function stopDaemon(appUrl) {
|
|
|
615
699
|
}
|
|
616
700
|
return await isDaemonListening(socketPath) ? "failed" : "stopped";
|
|
617
701
|
}
|
|
618
|
-
function stateMessage(state) {
|
|
702
|
+
function stateMessage(state, declined) {
|
|
619
703
|
return {
|
|
620
704
|
t: "state",
|
|
621
705
|
status: state.status,
|
|
622
706
|
machineName: state.machineName,
|
|
623
707
|
friendlyName: state.friendlyName,
|
|
624
708
|
error: state.error,
|
|
625
|
-
grantedAgentIds: state.grantedAgentIds
|
|
709
|
+
grantedAgentIds: state.grantedAgentIds,
|
|
710
|
+
declinedAgentIds: [...declined]
|
|
626
711
|
};
|
|
627
712
|
}
|
|
628
713
|
function sleep(ms) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./client-
|
|
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-
|
|
2
|
+
import "./client-Dd5sMXPv.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-C6yadKp2.mjs";
|
|
4
4
|
|
|
5
5
|
export { runPortalDaemon };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-
|
|
2
|
+
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-C6yadKp2.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
|
@@ -13,6 +13,7 @@ var PortalDaemonClient = class {
|
|
|
13
13
|
fallbackCwd = null;
|
|
14
14
|
wantEnabled = false;
|
|
15
15
|
pendingGrants = /* @__PURE__ */ new Set();
|
|
16
|
+
pendingDeclines = /* @__PURE__ */ new Map();
|
|
16
17
|
grantedAgentIds = /* @__PURE__ */ new Set();
|
|
17
18
|
constructor(opts) {
|
|
18
19
|
this.opts = opts;
|
|
@@ -35,7 +36,8 @@ var PortalDaemonClient = class {
|
|
|
35
36
|
t: "hello",
|
|
36
37
|
v: LOCAL_PROTOCOL_VERSION,
|
|
37
38
|
sessionId: this.sessionId,
|
|
38
|
-
token: this.opts.sessionToken
|
|
39
|
+
token: this.opts.sessionToken,
|
|
40
|
+
tokenKind: "session"
|
|
39
41
|
});
|
|
40
42
|
if (this.wantEnabled) this.send({ t: "enable" });
|
|
41
43
|
if (this.fallbackCwd) this.send({
|
|
@@ -52,6 +54,11 @@ var PortalDaemonClient = class {
|
|
|
52
54
|
t: "grant",
|
|
53
55
|
agentId
|
|
54
56
|
});
|
|
57
|
+
for (const [agentId, declined] of this.pendingDeclines) this.send({
|
|
58
|
+
t: "decline",
|
|
59
|
+
agentId,
|
|
60
|
+
declined
|
|
61
|
+
});
|
|
55
62
|
});
|
|
56
63
|
socket.on("data", (chunk) => {
|
|
57
64
|
for (const line of parse(chunk)) {
|
|
@@ -82,7 +89,8 @@ var PortalDaemonClient = class {
|
|
|
82
89
|
machineName: msg.machineName,
|
|
83
90
|
friendlyName: msg.friendlyName,
|
|
84
91
|
error: msg.error,
|
|
85
|
-
grantedAgentIds: msg.grantedAgentIds
|
|
92
|
+
grantedAgentIds: msg.grantedAgentIds,
|
|
93
|
+
declinedAgentIds: msg.declinedAgentIds
|
|
86
94
|
});
|
|
87
95
|
}
|
|
88
96
|
}
|
|
@@ -108,6 +116,19 @@ var PortalDaemonClient = class {
|
|
|
108
116
|
this.send({ t: "disable" });
|
|
109
117
|
}
|
|
110
118
|
/**
|
|
119
|
+
* Record that the user said no (or yes again) to one agent on this machine.
|
|
120
|
+
* The daemon owns the answer, so every attached CLI stops asking — and it
|
|
121
|
+
* closes the share when nothing else is granted.
|
|
122
|
+
*/
|
|
123
|
+
decline(agentId, declined) {
|
|
124
|
+
this.pendingDeclines.set(agentId, declined);
|
|
125
|
+
this.send({
|
|
126
|
+
t: "decline",
|
|
127
|
+
agentId,
|
|
128
|
+
declined
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
111
132
|
* Set the directory an exec runs in when the daemon can't place its
|
|
112
133
|
* conversation. `skydive portal open` shares one directory and has no
|
|
113
134
|
* conversation of its own, so this is how it routes.
|
|
@@ -14,14 +14,17 @@ 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-CKBQ8pft.mjs");
|
|
18
18
|
let signalConnected;
|
|
19
19
|
const connected = new Promise((resolve) => {
|
|
20
20
|
signalConnected = resolve;
|
|
21
21
|
});
|
|
22
22
|
const machineShare = new PortalClient({
|
|
23
23
|
appUrl,
|
|
24
|
-
|
|
24
|
+
credentials: () => ({
|
|
25
|
+
sessionToken,
|
|
26
|
+
deviceToken: null
|
|
27
|
+
}),
|
|
25
28
|
resolveCwd: () => process.cwd(),
|
|
26
29
|
onState: (state) => {
|
|
27
30
|
if (state.status === "connected") signalConnected();
|
package/package.json
CHANGED