linkshell-cli 0.10.70 → 0.10.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/src/auth.d.ts +7 -0
- package/dist/cli/src/auth.js +90 -33
- package/dist/cli/src/auth.js.map +1 -1
- package/dist/cli/src/commands/desktop.d.ts +6 -6
- package/dist/cli/src/commands/host.js +4 -2
- package/dist/cli/src/commands/host.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/host/src/drivers/claude/tasks.d.ts +1 -1
- package/dist/host/src/drivers/claude/transcript.d.ts +13 -13
- package/dist/host/src/hub.d.ts +9 -9
- package/dist/wire/src/content.d.ts +4 -4
- package/dist/wire/src/editor.d.ts +28 -28
- package/dist/wire/src/live-activity.d.ts +10 -10
- package/dist/wire/src/model.d.ts +59 -59
- package/dist/wire/src/rpc.d.ts +543 -543
- package/dist/wire/src/updates.d.ts +95 -95
- package/dist/wire/src/watch.d.ts +22 -22
- package/package.json +3 -3
- package/src/auth.ts +97 -38
- package/src/commands/host.ts +4 -2
package/dist/wire/src/watch.d.ts
CHANGED
|
@@ -151,8 +151,8 @@ export declare const watchTimelineItemSchema: z.ZodObject<{
|
|
|
151
151
|
imageBase64: z.ZodOptional<z.ZodString>;
|
|
152
152
|
imageMimeType: z.ZodOptional<z.ZodString>;
|
|
153
153
|
}, "strip", z.ZodTypeAny, {
|
|
154
|
-
text: string;
|
|
155
154
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
155
|
+
text: string;
|
|
156
156
|
id: string;
|
|
157
157
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
158
158
|
title?: string | undefined;
|
|
@@ -166,8 +166,8 @@ export declare const watchTimelineItemSchema: z.ZodObject<{
|
|
|
166
166
|
imageBase64?: string | undefined;
|
|
167
167
|
imageMimeType?: string | undefined;
|
|
168
168
|
}, {
|
|
169
|
-
text: string;
|
|
170
169
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
170
|
+
text: string;
|
|
171
171
|
id: string;
|
|
172
172
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
173
173
|
title?: string | undefined;
|
|
@@ -313,8 +313,8 @@ export declare const watchSessionSchema: z.ZodObject<{
|
|
|
313
313
|
imageBase64: z.ZodOptional<z.ZodString>;
|
|
314
314
|
imageMimeType: z.ZodOptional<z.ZodString>;
|
|
315
315
|
}, "strip", z.ZodTypeAny, {
|
|
316
|
-
text: string;
|
|
317
316
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
317
|
+
text: string;
|
|
318
318
|
id: string;
|
|
319
319
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
320
320
|
title?: string | undefined;
|
|
@@ -328,8 +328,8 @@ export declare const watchSessionSchema: z.ZodObject<{
|
|
|
328
328
|
imageBase64?: string | undefined;
|
|
329
329
|
imageMimeType?: string | undefined;
|
|
330
330
|
}, {
|
|
331
|
-
text: string;
|
|
332
331
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
332
|
+
text: string;
|
|
333
333
|
id: string;
|
|
334
334
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
335
335
|
title?: string | undefined;
|
|
@@ -403,8 +403,8 @@ export declare const watchSessionSchema: z.ZodObject<{
|
|
|
403
403
|
} | undefined;
|
|
404
404
|
hasEarlier?: boolean | undefined;
|
|
405
405
|
items?: {
|
|
406
|
-
text: string;
|
|
407
406
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
407
|
+
text: string;
|
|
408
408
|
id: string;
|
|
409
409
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
410
410
|
title?: string | undefined;
|
|
@@ -474,8 +474,8 @@ export declare const watchSessionSchema: z.ZodObject<{
|
|
|
474
474
|
} | undefined;
|
|
475
475
|
hasEarlier?: boolean | undefined;
|
|
476
476
|
items?: {
|
|
477
|
-
text: string;
|
|
478
477
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
478
|
+
text: string;
|
|
479
479
|
id: string;
|
|
480
480
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
481
481
|
title?: string | undefined;
|
|
@@ -703,8 +703,8 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
703
703
|
imageBase64: z.ZodOptional<z.ZodString>;
|
|
704
704
|
imageMimeType: z.ZodOptional<z.ZodString>;
|
|
705
705
|
}, "strip", z.ZodTypeAny, {
|
|
706
|
-
text: string;
|
|
707
706
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
707
|
+
text: string;
|
|
708
708
|
id: string;
|
|
709
709
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
710
710
|
title?: string | undefined;
|
|
@@ -718,8 +718,8 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
718
718
|
imageBase64?: string | undefined;
|
|
719
719
|
imageMimeType?: string | undefined;
|
|
720
720
|
}, {
|
|
721
|
-
text: string;
|
|
722
721
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
722
|
+
text: string;
|
|
723
723
|
id: string;
|
|
724
724
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
725
725
|
title?: string | undefined;
|
|
@@ -793,8 +793,8 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
793
793
|
} | undefined;
|
|
794
794
|
hasEarlier?: boolean | undefined;
|
|
795
795
|
items?: {
|
|
796
|
-
text: string;
|
|
797
796
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
797
|
+
text: string;
|
|
798
798
|
id: string;
|
|
799
799
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
800
800
|
title?: string | undefined;
|
|
@@ -864,8 +864,8 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
864
864
|
} | undefined;
|
|
865
865
|
hasEarlier?: boolean | undefined;
|
|
866
866
|
items?: {
|
|
867
|
-
text: string;
|
|
868
867
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
868
|
+
text: string;
|
|
869
869
|
id: string;
|
|
870
870
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
871
871
|
title?: string | undefined;
|
|
@@ -975,8 +975,8 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
975
975
|
} | undefined;
|
|
976
976
|
hasEarlier?: boolean | undefined;
|
|
977
977
|
items?: {
|
|
978
|
-
text: string;
|
|
979
978
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
979
|
+
text: string;
|
|
980
980
|
id: string;
|
|
981
981
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
982
982
|
title?: string | undefined;
|
|
@@ -1084,8 +1084,8 @@ export declare const watchSnapshotSchema: z.ZodObject<{
|
|
|
1084
1084
|
} | undefined;
|
|
1085
1085
|
hasEarlier?: boolean | undefined;
|
|
1086
1086
|
items?: {
|
|
1087
|
-
text: string;
|
|
1088
1087
|
kind: "user" | "assistant" | "image" | "diff" | "tool" | "subagent" | "question" | "error" | "thought" | "plan" | "permission";
|
|
1088
|
+
text: string;
|
|
1089
1089
|
id: string;
|
|
1090
1090
|
status?: "completed" | "failed" | "running" | "waiting" | undefined;
|
|
1091
1091
|
title?: string | undefined;
|
|
@@ -1183,6 +1183,7 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodO
|
|
|
1183
1183
|
questionId?: string | undefined;
|
|
1184
1184
|
}>;
|
|
1185
1185
|
}, "strip", z.ZodTypeAny, {
|
|
1186
|
+
kind: "action";
|
|
1186
1187
|
action: {
|
|
1187
1188
|
kind: "answer" | "permission" | "async-answer";
|
|
1188
1189
|
id: string;
|
|
@@ -1194,9 +1195,9 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodO
|
|
|
1194
1195
|
computerKey: string;
|
|
1195
1196
|
questionId?: string | undefined;
|
|
1196
1197
|
};
|
|
1197
|
-
kind: "action";
|
|
1198
1198
|
id: string;
|
|
1199
1199
|
}, {
|
|
1200
|
+
kind: "action";
|
|
1200
1201
|
action: {
|
|
1201
1202
|
kind: "answer" | "permission" | "async-answer";
|
|
1202
1203
|
id: string;
|
|
@@ -1208,7 +1209,6 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodO
|
|
|
1208
1209
|
computerKey: string;
|
|
1209
1210
|
questionId?: string | undefined;
|
|
1210
1211
|
};
|
|
1211
|
-
kind: "action";
|
|
1212
1212
|
id: string;
|
|
1213
1213
|
}>, z.ZodObject<{
|
|
1214
1214
|
id: z.ZodString;
|
|
@@ -1218,15 +1218,15 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodO
|
|
|
1218
1218
|
text: z.ZodString;
|
|
1219
1219
|
now: z.ZodOptional<z.ZodBoolean>;
|
|
1220
1220
|
}, "strip", z.ZodTypeAny, {
|
|
1221
|
-
text: string;
|
|
1222
1221
|
kind: "prompt";
|
|
1222
|
+
text: string;
|
|
1223
1223
|
id: string;
|
|
1224
1224
|
sessionId: string;
|
|
1225
1225
|
computerKey: string;
|
|
1226
1226
|
now?: boolean | undefined;
|
|
1227
1227
|
}, {
|
|
1228
|
-
text: string;
|
|
1229
1228
|
kind: "prompt";
|
|
1229
|
+
text: string;
|
|
1230
1230
|
id: string;
|
|
1231
1231
|
sessionId: string;
|
|
1232
1232
|
computerKey: string;
|
|
@@ -1284,15 +1284,15 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodO
|
|
|
1284
1284
|
optionId: z.ZodString;
|
|
1285
1285
|
value: z.ZodString;
|
|
1286
1286
|
}, "strip", z.ZodTypeAny, {
|
|
1287
|
-
value: string;
|
|
1288
1287
|
kind: "set-config";
|
|
1288
|
+
value: string;
|
|
1289
1289
|
id: string;
|
|
1290
1290
|
optionId: string;
|
|
1291
1291
|
sessionId: string;
|
|
1292
1292
|
computerKey: string;
|
|
1293
1293
|
}, {
|
|
1294
|
-
value: string;
|
|
1295
1294
|
kind: "set-config";
|
|
1295
|
+
value: string;
|
|
1296
1296
|
id: string;
|
|
1297
1297
|
optionId: string;
|
|
1298
1298
|
sessionId: string;
|
|
@@ -1335,15 +1335,15 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodO
|
|
|
1335
1335
|
cwd: z.ZodString;
|
|
1336
1336
|
prompt: z.ZodOptional<z.ZodString>;
|
|
1337
1337
|
}, "strip", z.ZodTypeAny, {
|
|
1338
|
-
agent: string;
|
|
1339
1338
|
kind: "create-session";
|
|
1339
|
+
agent: string;
|
|
1340
1340
|
id: string;
|
|
1341
1341
|
cwd: string;
|
|
1342
1342
|
computerKey: string;
|
|
1343
1343
|
prompt?: string | undefined;
|
|
1344
1344
|
}, {
|
|
1345
|
-
agent: string;
|
|
1346
1345
|
kind: "create-session";
|
|
1346
|
+
agent: string;
|
|
1347
1347
|
id: string;
|
|
1348
1348
|
cwd: string;
|
|
1349
1349
|
computerKey: string;
|
|
@@ -1388,14 +1388,14 @@ export declare const watchCommandSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodO
|
|
|
1388
1388
|
terminalId: z.ZodString;
|
|
1389
1389
|
data: z.ZodString;
|
|
1390
1390
|
}, "strip", z.ZodTypeAny, {
|
|
1391
|
-
data: string;
|
|
1392
1391
|
kind: "terminal-input";
|
|
1392
|
+
data: string;
|
|
1393
1393
|
terminalId: string;
|
|
1394
1394
|
id: string;
|
|
1395
1395
|
computerKey: string;
|
|
1396
1396
|
}, {
|
|
1397
|
-
data: string;
|
|
1398
1397
|
kind: "terminal-input";
|
|
1398
|
+
data: string;
|
|
1399
1399
|
terminalId: string;
|
|
1400
1400
|
id: string;
|
|
1401
1401
|
computerKey: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkshell-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.72",
|
|
4
4
|
"description": "Follow, steer and approve the coding agents on your computer (Claude Code, Codex, Gemini, Copilot and more) from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/src/index.js",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"qrcode-terminal": "^0.12.0",
|
|
36
36
|
"ws": "^8.21.0",
|
|
37
37
|
"@linkshell/gateway": "0.6.38",
|
|
38
|
-
"@linkshell/
|
|
39
|
-
"@linkshell/
|
|
38
|
+
"@linkshell/host": "0.4.67",
|
|
39
|
+
"@linkshell/wire": "0.2.20"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/ws": "^8.5.14",
|
package/src/auth.ts
CHANGED
|
@@ -65,44 +65,25 @@ export async function refreshAccessToken(options: { signal?: AbortSignal } = {})
|
|
|
65
65
|
if (!auth?.refreshToken) return null;
|
|
66
66
|
if (auth.expiresAt - Date.now() >= 60_000) return auth;
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
const tokens: AuthTokens = {
|
|
89
|
-
accessToken: body.access_token,
|
|
90
|
-
refreshToken: body.refresh_token,
|
|
91
|
-
expiresAt: Date.now() + body.expires_in * 1000,
|
|
92
|
-
userId: body.user.id,
|
|
93
|
-
email: body.user.email ?? auth.email,
|
|
94
|
-
};
|
|
95
|
-
// A login or logout may finish while the refresh is in flight.
|
|
96
|
-
const current = loadAuth();
|
|
97
|
-
if (tokens.userId !== auth.userId || current?.userId !== auth.userId) return null;
|
|
98
|
-
if (current.refreshToken !== auth.refreshToken) return rotatedElsewhere(auth);
|
|
99
|
-
// Saved even when the caller gave up: the old refresh token is spent now, and presenting it
|
|
100
|
-
// again later would make Supabase revoke the whole session.
|
|
101
|
-
saveAuth(tokens);
|
|
102
|
-
return options.signal?.aborted ? null : tokens;
|
|
103
|
-
} catch {
|
|
104
|
-
return rotatedElsewhere(auth);
|
|
105
|
-
}
|
|
68
|
+
const outcome = await exchangeRefreshToken(auth.refreshToken, options.signal);
|
|
69
|
+
if (outcome.kind === "dead") return signedOutElsewhere(auth);
|
|
70
|
+
if (outcome.kind === "retryable") return rotatedElsewhere(auth);
|
|
71
|
+
const body = outcome.body;
|
|
72
|
+
const tokens: AuthTokens = {
|
|
73
|
+
accessToken: body.access_token,
|
|
74
|
+
refreshToken: body.refresh_token,
|
|
75
|
+
expiresAt: Date.now() + body.expires_in * 1000,
|
|
76
|
+
userId: body.user.id,
|
|
77
|
+
email: body.user.email ?? auth.email,
|
|
78
|
+
};
|
|
79
|
+
// A login or logout may finish while the refresh is in flight.
|
|
80
|
+
const current = loadAuth();
|
|
81
|
+
if (tokens.userId !== auth.userId || current?.userId !== auth.userId) return null;
|
|
82
|
+
if (current.refreshToken !== auth.refreshToken) return rotatedElsewhere(auth);
|
|
83
|
+
// Saved even when the caller gave up: the old refresh token is spent now, and presenting it
|
|
84
|
+
// again later would make Supabase revoke the whole session.
|
|
85
|
+
saveAuth(tokens);
|
|
86
|
+
return options.signal?.aborted ? null : tokens;
|
|
106
87
|
})().finally(() => {
|
|
107
88
|
inflightRefresh = null;
|
|
108
89
|
});
|
|
@@ -136,6 +117,71 @@ async function refused(res: Response): Promise<boolean> {
|
|
|
136
117
|
}
|
|
137
118
|
}
|
|
138
119
|
|
|
120
|
+
// itool.tech proxies the Supabase token exchange so clients in regions that can't reach
|
|
121
|
+
// supabase.co directly can still refresh. Set LINKSHELL_TOKEN_PROXY=off to skip it (kill switch).
|
|
122
|
+
const TOKEN_PROXY_URL = "https://itool.tech/api/linkshell/token";
|
|
123
|
+
|
|
124
|
+
interface TokenResponse {
|
|
125
|
+
access_token: string;
|
|
126
|
+
refresh_token: string;
|
|
127
|
+
expires_in: number;
|
|
128
|
+
user: { id: string; email?: string };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
type ExchangeOutcome =
|
|
132
|
+
| { kind: "ok"; body: TokenResponse }
|
|
133
|
+
| { kind: "dead" } // the refresh token can never work again → sign out
|
|
134
|
+
| { kind: "retryable" }; // outage, network, rate limit → keep the tokens and try again later
|
|
135
|
+
|
|
136
|
+
function exchangeSignal(signal?: AbortSignal): AbortSignal {
|
|
137
|
+
return signal ? AbortSignal.any([signal, AbortSignal.timeout(8_000)]) : AbortSignal.timeout(8_000);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Tries the itool.tech proxy. Returns an outcome to act on, or null meaning the proxy itself
|
|
142
|
+
* couldn't give a verdict (unreachable, 5xx, unexpected shape) so the caller should fall back to
|
|
143
|
+
* Supabase directly. Only a definite `invalid_grant` from the proxy signs the account out.
|
|
144
|
+
*/
|
|
145
|
+
async function exchangeViaProxy(refreshToken: string, signal?: AbortSignal): Promise<ExchangeOutcome | null> {
|
|
146
|
+
try {
|
|
147
|
+
const res = await fetch(TOKEN_PROXY_URL, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: { "Content-Type": "application/json" },
|
|
150
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
151
|
+
signal: exchangeSignal(signal),
|
|
152
|
+
});
|
|
153
|
+
if (res.ok) return { kind: "ok", body: (await res.json()) as TokenResponse };
|
|
154
|
+
const body = (await res.json().catch(() => ({}))) as { code?: string };
|
|
155
|
+
if (body.code === "invalid_grant") return { kind: "dead" };
|
|
156
|
+
return null; // temporarily_unavailable, invalid_request, anything else → fall back to direct
|
|
157
|
+
} catch {
|
|
158
|
+
return null; // couldn't reach itool.tech → fall back to direct
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function exchangeDirect(refreshToken: string, signal?: AbortSignal): Promise<ExchangeOutcome> {
|
|
163
|
+
try {
|
|
164
|
+
const res = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "Content-Type": "application/json", apikey: SUPABASE_ANON_KEY },
|
|
167
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
168
|
+
signal: exchangeSignal(signal),
|
|
169
|
+
});
|
|
170
|
+
if (res.ok) return { kind: "ok", body: (await res.json()) as TokenResponse };
|
|
171
|
+
return (await refused(res)) ? { kind: "dead" } : { kind: "retryable" };
|
|
172
|
+
} catch {
|
|
173
|
+
return { kind: "retryable" };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function exchangeRefreshToken(refreshToken: string, signal?: AbortSignal): Promise<ExchangeOutcome> {
|
|
178
|
+
if (process.env.LINKSHELL_TOKEN_PROXY !== "off") {
|
|
179
|
+
const viaProxy = await exchangeViaProxy(refreshToken, signal);
|
|
180
|
+
if (viaProxy) return viaProxy;
|
|
181
|
+
}
|
|
182
|
+
return exchangeDirect(refreshToken, signal);
|
|
183
|
+
}
|
|
184
|
+
|
|
139
185
|
/**
|
|
140
186
|
* The account was signed out on the server. Keeping its dead tokens would leave every client
|
|
141
187
|
* showing a signed-in account that nothing can use, so this computer signs out too.
|
|
@@ -163,3 +209,16 @@ export async function getValidToken(options: { signal?: AbortSignal } = {}): Pro
|
|
|
163
209
|
|
|
164
210
|
return auth.accessToken;
|
|
165
211
|
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The access token on hand right now, without ever blocking on the network. Returns whatever is
|
|
215
|
+
* saved (even if expired), and kicks off a background refresh when it is close to expiring. The
|
|
216
|
+
* gateway keeps a peer's account binding when an expired token is presented, so a stale token here
|
|
217
|
+
* still connects — the point is that a slow or unreachable Supabase can't stall the handshake.
|
|
218
|
+
*/
|
|
219
|
+
export function getCachedToken(): string | null {
|
|
220
|
+
const auth = loadAuth();
|
|
221
|
+
if (!auth) return null;
|
|
222
|
+
if (auth.expiresAt - Date.now() < 120_000) void refreshAccessToken().catch(() => {});
|
|
223
|
+
return auth.accessToken;
|
|
224
|
+
}
|
package/src/commands/host.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import type { GatewayStatus } from "@linkshell/wire";
|
|
5
|
-
import {
|
|
5
|
+
import { getCachedToken, isLoggedIn, loadAuth } from "../auth.js";
|
|
6
6
|
import * as daemon from "../utils/daemon.js";
|
|
7
7
|
|
|
8
8
|
// node:sqlite prints an ExperimentalWarning on load; it is expected here and
|
|
@@ -111,7 +111,9 @@ export async function runHostForeground(version: string): Promise<void> {
|
|
|
111
111
|
url: () => resolveGateway(home),
|
|
112
112
|
// The account is LinkShell's, so its token goes to LinkShell's gateway and to no other: a
|
|
113
113
|
// gateway of one's own has no accounts, and would refuse a computer that offered one.
|
|
114
|
-
|
|
114
|
+
// Never blocks: the cached token (even expired) goes out at once so a slow Supabase can't
|
|
115
|
+
// stall the gateway handshake; a background refresh keeps it fresh for the next connection.
|
|
116
|
+
token: async () => (sameGateway(resolveGateway(home), OFFICIAL_GATEWAY) ? (getCachedToken() ?? undefined) : undefined),
|
|
115
117
|
},
|
|
116
118
|
tcpPort: devPort,
|
|
117
119
|
claudeCommand: process.env.LINKSHELL_CLAUDE_COMMAND || undefined,
|