@implicit-ai/relay 0.0.3 → 0.0.6
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.js +215 -37
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25,24 +25,35 @@ function detectClaudeCodeInstalled() {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
function detectClaudeAuth() {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const available = [];
|
|
29
|
+
if (hasOAuthCredentials()) available.push("oauth");
|
|
30
|
+
if (process.env.ANTHROPIC_API_KEY) available.push("api_key");
|
|
31
|
+
const preferApiKey = process.env.IMPLICIT_USE_API_KEY === "1";
|
|
32
|
+
let method;
|
|
33
|
+
if (preferApiKey && available.includes("api_key")) {
|
|
34
|
+
method = "api_key";
|
|
35
|
+
} else if (available.includes("oauth")) {
|
|
36
|
+
method = "oauth";
|
|
37
|
+
} else if (available.includes("api_key")) {
|
|
38
|
+
method = "api_key";
|
|
39
|
+
} else {
|
|
40
|
+
method = "none";
|
|
30
41
|
}
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
let apiKeyOverridden = false;
|
|
43
|
+
if (method === "oauth" && process.env.ANTHROPIC_API_KEY) {
|
|
44
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
45
|
+
apiKeyOverridden = true;
|
|
33
46
|
}
|
|
47
|
+
return { method, available, apiKeyOverridden };
|
|
48
|
+
}
|
|
49
|
+
function hasOAuthCredentials() {
|
|
50
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) return true;
|
|
34
51
|
if (process.platform === "darwin") {
|
|
35
52
|
const keychainServices = ["Claude Code-credentials", "claude.ai"];
|
|
36
53
|
for (const service of keychainServices) {
|
|
37
54
|
try {
|
|
38
|
-
const result = execaSync("security", [
|
|
39
|
-
|
|
40
|
-
"-s",
|
|
41
|
-
service
|
|
42
|
-
]);
|
|
43
|
-
if (result.exitCode === 0) {
|
|
44
|
-
return { method: "oauth" };
|
|
45
|
-
}
|
|
55
|
+
const result = execaSync("security", ["find-generic-password", "-s", service]);
|
|
56
|
+
if (result.exitCode === 0) return true;
|
|
46
57
|
} catch {
|
|
47
58
|
}
|
|
48
59
|
}
|
|
@@ -52,13 +63,11 @@ function detectClaudeAuth() {
|
|
|
52
63
|
if (fs.existsSync(credentialsPath)) {
|
|
53
64
|
const raw = fs.readFileSync(credentialsPath, "utf-8");
|
|
54
65
|
const parsed = JSON.parse(raw);
|
|
55
|
-
if (parsed !== null && typeof parsed === "object")
|
|
56
|
-
return { method: "oauth" };
|
|
57
|
-
}
|
|
66
|
+
if (parsed !== null && typeof parsed === "object") return true;
|
|
58
67
|
}
|
|
59
68
|
} catch {
|
|
60
69
|
}
|
|
61
|
-
return
|
|
70
|
+
return false;
|
|
62
71
|
}
|
|
63
72
|
function printAuthGuidance() {
|
|
64
73
|
console.log("Claude Code auth not found. To set up authentication:");
|
|
@@ -988,10 +997,13 @@ function truncateString(value, maxChars) {
|
|
|
988
997
|
|
|
989
998
|
// src/lib/ably-transport.ts
|
|
990
999
|
import Ably from "ably";
|
|
991
|
-
import
|
|
1000
|
+
import pino2 from "pino";
|
|
992
1001
|
|
|
993
1002
|
// src/lib/auth-session.ts
|
|
1003
|
+
import { createHash } from "crypto";
|
|
994
1004
|
import { createClient, isAuthRetryableFetchError } from "@supabase/supabase-js";
|
|
1005
|
+
import pino from "pino";
|
|
1006
|
+
var log = pino({ name: "auth-session" });
|
|
995
1007
|
var SUPABASE_CONFIGS = {
|
|
996
1008
|
production: {
|
|
997
1009
|
url: "https://fqvwludchxxqfpvjzktf.supabase.co",
|
|
@@ -1003,6 +1015,10 @@ var SUPABASE_CONFIGS = {
|
|
|
1003
1015
|
}
|
|
1004
1016
|
};
|
|
1005
1017
|
var REFRESH_SAFETY_MARGIN_MS = 6e4;
|
|
1018
|
+
function tokenFingerprint(token) {
|
|
1019
|
+
if (!token) return "none";
|
|
1020
|
+
return createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
1021
|
+
}
|
|
1006
1022
|
var NeedsRePairingError = class extends Error {
|
|
1007
1023
|
constructor() {
|
|
1008
1024
|
super("Your session has expired. Run `implicit pair` to re-authenticate.");
|
|
@@ -1015,16 +1031,50 @@ var TransientAuthError = class extends Error {
|
|
|
1015
1031
|
this.name = "TransientAuthError";
|
|
1016
1032
|
}
|
|
1017
1033
|
};
|
|
1034
|
+
var inFlightRefresh = null;
|
|
1018
1035
|
async function getFreshAccessToken() {
|
|
1036
|
+
if (inFlightRefresh) {
|
|
1037
|
+
log.debug("getFreshAccessToken: joining in-flight refresh");
|
|
1038
|
+
return inFlightRefresh;
|
|
1039
|
+
}
|
|
1040
|
+
inFlightRefresh = doGetFreshAccessToken().finally(() => {
|
|
1041
|
+
inFlightRefresh = null;
|
|
1042
|
+
});
|
|
1043
|
+
return inFlightRefresh;
|
|
1044
|
+
}
|
|
1045
|
+
async function doGetFreshAccessToken() {
|
|
1019
1046
|
const auth = loadAuth();
|
|
1020
1047
|
if (!auth || !auth.refreshToken) {
|
|
1048
|
+
log.warn(
|
|
1049
|
+
{ hasAuth: !!auth, hasRefreshToken: !!auth?.refreshToken },
|
|
1050
|
+
"getFreshAccessToken: no stored auth or missing refresh token \u2014 re-pair required"
|
|
1051
|
+
);
|
|
1021
1052
|
throw new NeedsRePairingError();
|
|
1022
1053
|
}
|
|
1023
|
-
|
|
1054
|
+
const now = Date.now();
|
|
1055
|
+
const env = auth.env ?? "production";
|
|
1056
|
+
const rtFp = tokenFingerprint(auth.refreshToken);
|
|
1057
|
+
const expiresInMs = auth.expiresAt - now;
|
|
1058
|
+
const baseCtx = {
|
|
1059
|
+
env,
|
|
1060
|
+
userId: auth.userId,
|
|
1061
|
+
refreshTokenFp: rtFp,
|
|
1062
|
+
accessTokenExpiresAt: new Date(auth.expiresAt).toISOString(),
|
|
1063
|
+
accessTokenExpiresInMs: expiresInMs
|
|
1064
|
+
};
|
|
1065
|
+
if (now < auth.expiresAt - REFRESH_SAFETY_MARGIN_MS) {
|
|
1066
|
+
log.debug(baseCtx, "getFreshAccessToken: returning cached access token");
|
|
1024
1067
|
return auth.accessToken;
|
|
1025
1068
|
}
|
|
1026
|
-
|
|
1027
|
-
const
|
|
1069
|
+
log.info(baseCtx, "getFreshAccessToken: token within safety margin \u2014 calling supabase.refreshSession");
|
|
1070
|
+
const { url, anonKey } = SUPABASE_CONFIGS[env];
|
|
1071
|
+
const supabase = createClient(url, anonKey, {
|
|
1072
|
+
auth: {
|
|
1073
|
+
autoRefreshToken: false,
|
|
1074
|
+
persistSession: false,
|
|
1075
|
+
detectSessionInUrl: false
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1028
1078
|
let result;
|
|
1029
1079
|
try {
|
|
1030
1080
|
result = await supabase.auth.refreshSession({
|
|
@@ -1032,31 +1082,72 @@ async function getFreshAccessToken() {
|
|
|
1032
1082
|
});
|
|
1033
1083
|
} catch (err) {
|
|
1034
1084
|
if (isAuthRetryableFetchError(err) || isNetworkLikeError(err)) {
|
|
1085
|
+
log.warn(
|
|
1086
|
+
{ ...baseCtx, errMessage: err instanceof Error ? err.message : String(err) },
|
|
1087
|
+
"refreshSession threw transient error \u2014 preserving auth"
|
|
1088
|
+
);
|
|
1035
1089
|
throw new TransientAuthError(
|
|
1036
1090
|
`Network error refreshing session: ${err instanceof Error ? err.message : String(err)}`
|
|
1037
1091
|
);
|
|
1038
1092
|
}
|
|
1093
|
+
log.error(
|
|
1094
|
+
{
|
|
1095
|
+
...baseCtx,
|
|
1096
|
+
errName: err instanceof Error ? err.name : "?",
|
|
1097
|
+
errMessage: err instanceof Error ? err.message : String(err),
|
|
1098
|
+
errStatus: err?.status ?? "?",
|
|
1099
|
+
errCode: err?.code ?? "?"
|
|
1100
|
+
},
|
|
1101
|
+
"refreshSession threw non-retryable error \u2014 re-throwing (will NOT clear auth)"
|
|
1102
|
+
);
|
|
1039
1103
|
throw err;
|
|
1040
1104
|
}
|
|
1041
1105
|
const { data, error } = result;
|
|
1042
1106
|
if (error) {
|
|
1043
1107
|
if (isAuthRetryableFetchError(error) || isNetworkLikeError(error)) {
|
|
1108
|
+
log.warn(
|
|
1109
|
+
{ ...baseCtx, errMessage: error.message },
|
|
1110
|
+
"refreshSession returned transient error \u2014 preserving auth"
|
|
1111
|
+
);
|
|
1044
1112
|
throw new TransientAuthError(`Network error refreshing session: ${error.message}`);
|
|
1045
1113
|
}
|
|
1114
|
+
log.error(
|
|
1115
|
+
{
|
|
1116
|
+
...baseCtx,
|
|
1117
|
+
errStatus: error.status ?? "?",
|
|
1118
|
+
errCode: error.code ?? "?",
|
|
1119
|
+
errName: error.name ?? "?",
|
|
1120
|
+
errMessage: error.message ?? "?"
|
|
1121
|
+
},
|
|
1122
|
+
"refreshSession rejected refresh_token \u2014 clearing auth, re-pair required"
|
|
1123
|
+
);
|
|
1046
1124
|
clearAuth();
|
|
1047
1125
|
throw new NeedsRePairingError();
|
|
1048
1126
|
}
|
|
1049
1127
|
if (!data.session) {
|
|
1128
|
+
log.error(baseCtx, "refreshSession returned no session \u2014 clearing auth, re-pair required");
|
|
1050
1129
|
clearAuth();
|
|
1051
1130
|
throw new NeedsRePairingError();
|
|
1052
1131
|
}
|
|
1132
|
+
const newExpiresAt = (data.session.expires_at ?? Math.floor(Date.now() / 1e3) + 3600) * 1e3;
|
|
1053
1133
|
const newAuth = {
|
|
1054
1134
|
...auth,
|
|
1055
1135
|
accessToken: data.session.access_token,
|
|
1056
1136
|
refreshToken: data.session.refresh_token,
|
|
1057
|
-
expiresAt:
|
|
1137
|
+
expiresAt: newExpiresAt
|
|
1058
1138
|
};
|
|
1059
1139
|
saveAuth(newAuth);
|
|
1140
|
+
log.info(
|
|
1141
|
+
{
|
|
1142
|
+
env,
|
|
1143
|
+
userId: auth.userId,
|
|
1144
|
+
oldRefreshTokenFp: rtFp,
|
|
1145
|
+
newRefreshTokenFp: tokenFingerprint(data.session.refresh_token),
|
|
1146
|
+
rotated: rtFp !== tokenFingerprint(data.session.refresh_token),
|
|
1147
|
+
newAccessTokenExpiresAt: new Date(newExpiresAt).toISOString()
|
|
1148
|
+
},
|
|
1149
|
+
"refreshSession succeeded \u2014 auth saved"
|
|
1150
|
+
);
|
|
1060
1151
|
return data.session.access_token;
|
|
1061
1152
|
}
|
|
1062
1153
|
function isNetworkLikeError(err) {
|
|
@@ -1099,7 +1190,7 @@ async function fetchAblyToken(opts) {
|
|
|
1099
1190
|
}
|
|
1100
1191
|
|
|
1101
1192
|
// src/lib/ably-transport.ts
|
|
1102
|
-
var
|
|
1193
|
+
var log2 = pino2({ name: "ably-transport" });
|
|
1103
1194
|
var AblyTransport = class {
|
|
1104
1195
|
railwayUrl;
|
|
1105
1196
|
userId;
|
|
@@ -1111,6 +1202,8 @@ var AblyTransport = class {
|
|
|
1111
1202
|
firstConnectDone = false;
|
|
1112
1203
|
/** Guards against re-registering connection state listeners on reconnect. */
|
|
1113
1204
|
connectionListenersWired = false;
|
|
1205
|
+
/** Guards against re-registering channel state listeners across attach attempts. */
|
|
1206
|
+
channelListenersWired = false;
|
|
1114
1207
|
reconnectHandlers = [];
|
|
1115
1208
|
errorHandlers = [];
|
|
1116
1209
|
connectionStateHandlers = [];
|
|
@@ -1130,13 +1223,30 @@ var AblyTransport = class {
|
|
|
1130
1223
|
this.realtime = null;
|
|
1131
1224
|
this.channel = null;
|
|
1132
1225
|
}
|
|
1226
|
+
let authCallbackInvocations = 0;
|
|
1133
1227
|
this.realtime = new Ably.Realtime({
|
|
1134
1228
|
authCallback: async (_tokenParams, callback) => {
|
|
1229
|
+
const invocation = ++authCallbackInvocations;
|
|
1230
|
+
log2.info({ invocation, userId: this.userId }, "Ably authCallback fired");
|
|
1135
1231
|
try {
|
|
1136
1232
|
const tokenDetails = await fetchAblyToken({ railwayUrl, getAccessToken: this.getAccessToken });
|
|
1233
|
+
log2.info(
|
|
1234
|
+
{ invocation, ablyTokenExpires: tokenDetails.expires ? new Date(tokenDetails.expires).toISOString() : "?" },
|
|
1235
|
+
"Ably authCallback succeeded"
|
|
1236
|
+
);
|
|
1137
1237
|
callback(null, tokenDetails);
|
|
1138
1238
|
} catch (err) {
|
|
1139
|
-
|
|
1239
|
+
const isRepair = err instanceof NeedsRePairingError;
|
|
1240
|
+
log2.error(
|
|
1241
|
+
{
|
|
1242
|
+
invocation,
|
|
1243
|
+
isRepair,
|
|
1244
|
+
errName: err instanceof Error ? err.name : "?",
|
|
1245
|
+
errMessage: err instanceof Error ? err.message : String(err)
|
|
1246
|
+
},
|
|
1247
|
+
"Ably authCallback failed"
|
|
1248
|
+
);
|
|
1249
|
+
if (isRepair) {
|
|
1140
1250
|
for (const h of this.errorHandlers) h(err.message);
|
|
1141
1251
|
}
|
|
1142
1252
|
callback(err instanceof Error ? err.message : String(err), null);
|
|
@@ -1146,42 +1256,93 @@ var AblyTransport = class {
|
|
|
1146
1256
|
if (!this.connectionListenersWired) {
|
|
1147
1257
|
this.connectionListenersWired = true;
|
|
1148
1258
|
this.realtime.connection.on("connected", () => {
|
|
1149
|
-
|
|
1259
|
+
log2.info({ connectionId: this.realtime?.connection.id, connectionKey: this.realtime?.connection.key }, "connection state: connected");
|
|
1150
1260
|
this._isConnected = true;
|
|
1151
1261
|
if (this.firstConnectDone) {
|
|
1152
|
-
|
|
1262
|
+
log2.info({ handlers: this.reconnectHandlers.length }, "reconnected \u2014 firing handlers");
|
|
1153
1263
|
for (const h of this.reconnectHandlers) h();
|
|
1154
1264
|
}
|
|
1155
1265
|
this.firstConnectDone = true;
|
|
1156
1266
|
for (const h of this.connectionStateHandlers) h("connected");
|
|
1157
1267
|
});
|
|
1158
1268
|
this.realtime.connection.on("disconnected", (stateChange) => {
|
|
1159
|
-
|
|
1269
|
+
log2.warn({ reason: stateChange?.reason?.message }, "connection state: disconnected");
|
|
1160
1270
|
this._isConnected = false;
|
|
1161
1271
|
for (const h of this.connectionStateHandlers) h("disconnected");
|
|
1162
1272
|
});
|
|
1163
1273
|
this.realtime.connection.on("failed", (stateChange) => {
|
|
1164
1274
|
const msg = stateChange?.reason?.message ?? "Ably connection failed";
|
|
1165
|
-
|
|
1275
|
+
log2.error({ reason: msg }, "connection state: failed");
|
|
1166
1276
|
this._isConnected = false;
|
|
1167
1277
|
for (const h of this.errorHandlers) h(msg);
|
|
1168
1278
|
for (const h of this.connectionStateHandlers) h("failed");
|
|
1169
1279
|
});
|
|
1170
1280
|
this.realtime.connection.on("suspended", (stateChange) => {
|
|
1171
1281
|
const msg = stateChange?.reason?.message ?? "Ably connection suspended";
|
|
1172
|
-
|
|
1282
|
+
log2.warn({ reason: msg }, "connection state: suspended");
|
|
1173
1283
|
this._isConnected = false;
|
|
1174
1284
|
for (const h of this.errorHandlers) h(msg);
|
|
1175
1285
|
for (const h of this.connectionStateHandlers) h("suspended");
|
|
1176
1286
|
});
|
|
1177
1287
|
this.realtime.connection.on("closed", () => {
|
|
1178
|
-
|
|
1288
|
+
log2.info("connection state: closed");
|
|
1179
1289
|
this._isConnected = false;
|
|
1180
1290
|
for (const h of this.connectionStateHandlers) h("closed");
|
|
1181
1291
|
});
|
|
1182
1292
|
}
|
|
1183
1293
|
this.channel = this.realtime.channels.get(this.channelName);
|
|
1184
|
-
|
|
1294
|
+
if (!this.channelListenersWired) {
|
|
1295
|
+
this.channelListenersWired = true;
|
|
1296
|
+
const ch = this.channel;
|
|
1297
|
+
ch.on("attached", (stateChange) => {
|
|
1298
|
+
log2.info(
|
|
1299
|
+
{
|
|
1300
|
+
channel: this.channelName,
|
|
1301
|
+
resumed: stateChange?.resumed,
|
|
1302
|
+
hasBacklog: stateChange?.hasBacklog
|
|
1303
|
+
},
|
|
1304
|
+
"channel state: attached"
|
|
1305
|
+
);
|
|
1306
|
+
});
|
|
1307
|
+
ch.on("failed", (stateChange) => {
|
|
1308
|
+
log2.error(
|
|
1309
|
+
{ channel: this.channelName, reason: stateChange?.reason?.message, code: stateChange?.reason?.code },
|
|
1310
|
+
"channel state: failed"
|
|
1311
|
+
);
|
|
1312
|
+
});
|
|
1313
|
+
ch.on("detached", (stateChange) => {
|
|
1314
|
+
log2.warn(
|
|
1315
|
+
{ channel: this.channelName, reason: stateChange?.reason?.message, code: stateChange?.reason?.code },
|
|
1316
|
+
"channel state: detached"
|
|
1317
|
+
);
|
|
1318
|
+
});
|
|
1319
|
+
ch.on("suspended", (stateChange) => {
|
|
1320
|
+
log2.warn(
|
|
1321
|
+
{ channel: this.channelName, reason: stateChange?.reason?.message, code: stateChange?.reason?.code },
|
|
1322
|
+
"channel state: suspended"
|
|
1323
|
+
);
|
|
1324
|
+
});
|
|
1325
|
+
ch.on("update", (stateChange) => {
|
|
1326
|
+
log2.info(
|
|
1327
|
+
{
|
|
1328
|
+
channel: this.channelName,
|
|
1329
|
+
current: stateChange?.current,
|
|
1330
|
+
previous: stateChange?.previous,
|
|
1331
|
+
reason: stateChange?.reason?.message
|
|
1332
|
+
},
|
|
1333
|
+
"channel state: update"
|
|
1334
|
+
);
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
log2.info({ channel: this.channelName }, "attaching channel");
|
|
1338
|
+
try {
|
|
1339
|
+
await this.channel.attach();
|
|
1340
|
+
log2.info({ channel: this.channelName, state: this.channel.state }, "attach() resolved");
|
|
1341
|
+
} catch (err) {
|
|
1342
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1343
|
+
log2.error({ channel: this.channelName, err: message }, "attach() rejected");
|
|
1344
|
+
throw err;
|
|
1345
|
+
}
|
|
1185
1346
|
for (const { event, handler } of this.pendingSubscriptions) {
|
|
1186
1347
|
this.channel.subscribe(event, (msg) => {
|
|
1187
1348
|
handler(msg.data);
|
|
@@ -1240,8 +1401,15 @@ var AblyTransport = class {
|
|
|
1240
1401
|
// --- Presence ---
|
|
1241
1402
|
async presenceEnter(data) {
|
|
1242
1403
|
if (!this.channel) throw new Error("AblyTransport: not connected \u2014 call connect() first");
|
|
1243
|
-
|
|
1244
|
-
|
|
1404
|
+
log2.info({ data, channelState: this.channel.state }, "entering presence");
|
|
1405
|
+
try {
|
|
1406
|
+
await this.channel.presence.enter(data);
|
|
1407
|
+
log2.info({ channel: this.channelName }, "presence.enter() resolved");
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1410
|
+
log2.error({ channel: this.channelName, err: message }, "presence.enter() rejected");
|
|
1411
|
+
throw err;
|
|
1412
|
+
}
|
|
1245
1413
|
}
|
|
1246
1414
|
async presenceLeave() {
|
|
1247
1415
|
if (!this.channel) return;
|
|
@@ -1264,7 +1432,7 @@ var AblyTransport = class {
|
|
|
1264
1432
|
}
|
|
1265
1433
|
this.channel.presence.subscribe("enter", (msg) => {
|
|
1266
1434
|
const clientId = msg.clientId ?? "unknown";
|
|
1267
|
-
|
|
1435
|
+
log2.info({ clientId }, "presence enter");
|
|
1268
1436
|
handler({ clientId, data: msg.data ?? {} });
|
|
1269
1437
|
});
|
|
1270
1438
|
}
|
|
@@ -1275,21 +1443,21 @@ var AblyTransport = class {
|
|
|
1275
1443
|
}
|
|
1276
1444
|
this.channel.presence.subscribe("leave", (msg) => {
|
|
1277
1445
|
const clientId = msg.clientId ?? "unknown";
|
|
1278
|
-
|
|
1446
|
+
log2.info({ clientId }, "presence leave");
|
|
1279
1447
|
handler({ clientId, data: msg.data ?? {} });
|
|
1280
1448
|
});
|
|
1281
1449
|
}
|
|
1282
1450
|
// --- History ---
|
|
1283
1451
|
async fetchHistory(limit) {
|
|
1284
1452
|
if (!this.channel) throw new Error("AblyTransport: not connected");
|
|
1285
|
-
|
|
1453
|
+
log2.info({ limit }, "fetching channel history");
|
|
1286
1454
|
const result = await this.channel.history({ limit, direction: "backwards" });
|
|
1287
1455
|
const messages = (result.items ?? []).map((msg) => ({
|
|
1288
1456
|
event: msg.name ?? "",
|
|
1289
1457
|
data: msg.data ?? {},
|
|
1290
1458
|
timestamp: msg.timestamp ?? 0
|
|
1291
1459
|
}));
|
|
1292
|
-
|
|
1460
|
+
log2.info({ count: messages.length }, "history fetched");
|
|
1293
1461
|
return messages;
|
|
1294
1462
|
}
|
|
1295
1463
|
};
|
|
@@ -1324,7 +1492,17 @@ async function connect(options) {
|
|
|
1324
1492
|
printAuthGuidance();
|
|
1325
1493
|
process.exit(1);
|
|
1326
1494
|
}
|
|
1327
|
-
if (!isHeadless)
|
|
1495
|
+
if (!isHeadless) {
|
|
1496
|
+
console.log(` \u2713 Claude auth detected (${claudeAuth.method})`);
|
|
1497
|
+
if (claudeAuth.apiKeyOverridden) {
|
|
1498
|
+
console.log(
|
|
1499
|
+
" \u21B3 ANTHROPIC_API_KEY found but ignored \u2014 using Claude Pro/Max OAuth."
|
|
1500
|
+
);
|
|
1501
|
+
console.log(
|
|
1502
|
+
" Set IMPLICIT_USE_API_KEY=1 to bill via API instead."
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1328
1506
|
let auth = options.token ? authFromAccessToken(options.token, env) : loadAuth();
|
|
1329
1507
|
if (!auth) {
|
|
1330
1508
|
if (isHeadless) {
|