linkshell-cli 0.10.71 → 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.
@@ -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.71",
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",
@@ -34,9 +34,9 @@
34
34
  "node-pty": "^1.1.0",
35
35
  "qrcode-terminal": "^0.12.0",
36
36
  "ws": "^8.21.0",
37
+ "@linkshell/gateway": "0.6.38",
37
38
  "@linkshell/host": "0.4.67",
38
- "@linkshell/wire": "0.2.20",
39
- "@linkshell/gateway": "0.6.38"
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
- try {
69
- const res = await fetch(
70
- `${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`,
71
- {
72
- method: "POST",
73
- headers: {
74
- "Content-Type": "application/json",
75
- apikey: SUPABASE_ANON_KEY,
76
- },
77
- body: JSON.stringify({ refresh_token: auth.refreshToken }),
78
- signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(8_000)]) : AbortSignal.timeout(8_000),
79
- },
80
- );
81
- if (!res.ok) return (await refused(res)) ? signedOutElsewhere(auth) : rotatedElsewhere(auth);
82
- const body = (await res.json()) as {
83
- access_token: string;
84
- refresh_token: string;
85
- expires_in: number;
86
- user: { id: string; email?: string };
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.