@implicit-ai/relay 0.0.3 → 0.0.4

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.
Files changed (2) hide show
  1. package/dist/cli.js +100 -21
  2. 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
- if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
29
- return { method: "oauth" };
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
- if (process.env.ANTHROPIC_API_KEY) {
32
- return { method: "api_key" };
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
- "find-generic-password",
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 { method: "none" };
70
+ return false;
62
71
  }
63
72
  function printAuthGuidance() {
64
73
  console.log("Claude Code auth not found. To set up authentication:");
@@ -1111,6 +1120,8 @@ var AblyTransport = class {
1111
1120
  firstConnectDone = false;
1112
1121
  /** Guards against re-registering connection state listeners on reconnect. */
1113
1122
  connectionListenersWired = false;
1123
+ /** Guards against re-registering channel state listeners across attach attempts. */
1124
+ channelListenersWired = false;
1114
1125
  reconnectHandlers = [];
1115
1126
  errorHandlers = [];
1116
1127
  connectionStateHandlers = [];
@@ -1146,7 +1157,7 @@ var AblyTransport = class {
1146
1157
  if (!this.connectionListenersWired) {
1147
1158
  this.connectionListenersWired = true;
1148
1159
  this.realtime.connection.on("connected", () => {
1149
- log.info("connection state: connected");
1160
+ log.info({ connectionId: this.realtime?.connection.id, connectionKey: this.realtime?.connection.key }, "connection state: connected");
1150
1161
  this._isConnected = true;
1151
1162
  if (this.firstConnectDone) {
1152
1163
  log.info({ handlers: this.reconnectHandlers.length }, "reconnected \u2014 firing handlers");
@@ -1181,7 +1192,58 @@ var AblyTransport = class {
1181
1192
  });
1182
1193
  }
1183
1194
  this.channel = this.realtime.channels.get(this.channelName);
1184
- await this.channel.attach();
1195
+ if (!this.channelListenersWired) {
1196
+ this.channelListenersWired = true;
1197
+ const ch = this.channel;
1198
+ ch.on("attached", (stateChange) => {
1199
+ log.info(
1200
+ {
1201
+ channel: this.channelName,
1202
+ resumed: stateChange?.resumed,
1203
+ hasBacklog: stateChange?.hasBacklog
1204
+ },
1205
+ "channel state: attached"
1206
+ );
1207
+ });
1208
+ ch.on("failed", (stateChange) => {
1209
+ log.error(
1210
+ { channel: this.channelName, reason: stateChange?.reason?.message, code: stateChange?.reason?.code },
1211
+ "channel state: failed"
1212
+ );
1213
+ });
1214
+ ch.on("detached", (stateChange) => {
1215
+ log.warn(
1216
+ { channel: this.channelName, reason: stateChange?.reason?.message, code: stateChange?.reason?.code },
1217
+ "channel state: detached"
1218
+ );
1219
+ });
1220
+ ch.on("suspended", (stateChange) => {
1221
+ log.warn(
1222
+ { channel: this.channelName, reason: stateChange?.reason?.message, code: stateChange?.reason?.code },
1223
+ "channel state: suspended"
1224
+ );
1225
+ });
1226
+ ch.on("update", (stateChange) => {
1227
+ log.info(
1228
+ {
1229
+ channel: this.channelName,
1230
+ current: stateChange?.current,
1231
+ previous: stateChange?.previous,
1232
+ reason: stateChange?.reason?.message
1233
+ },
1234
+ "channel state: update"
1235
+ );
1236
+ });
1237
+ }
1238
+ log.info({ channel: this.channelName }, "attaching channel");
1239
+ try {
1240
+ await this.channel.attach();
1241
+ log.info({ channel: this.channelName, state: this.channel.state }, "attach() resolved");
1242
+ } catch (err) {
1243
+ const message = err instanceof Error ? err.message : String(err);
1244
+ log.error({ channel: this.channelName, err: message }, "attach() rejected");
1245
+ throw err;
1246
+ }
1185
1247
  for (const { event, handler } of this.pendingSubscriptions) {
1186
1248
  this.channel.subscribe(event, (msg) => {
1187
1249
  handler(msg.data);
@@ -1240,8 +1302,15 @@ var AblyTransport = class {
1240
1302
  // --- Presence ---
1241
1303
  async presenceEnter(data) {
1242
1304
  if (!this.channel) throw new Error("AblyTransport: not connected \u2014 call connect() first");
1243
- log.info({ data }, "entering presence");
1244
- await this.channel.presence.enter(data);
1305
+ log.info({ data, channelState: this.channel.state }, "entering presence");
1306
+ try {
1307
+ await this.channel.presence.enter(data);
1308
+ log.info({ channel: this.channelName }, "presence.enter() resolved");
1309
+ } catch (err) {
1310
+ const message = err instanceof Error ? err.message : String(err);
1311
+ log.error({ channel: this.channelName, err: message }, "presence.enter() rejected");
1312
+ throw err;
1313
+ }
1245
1314
  }
1246
1315
  async presenceLeave() {
1247
1316
  if (!this.channel) return;
@@ -1324,7 +1393,17 @@ async function connect(options) {
1324
1393
  printAuthGuidance();
1325
1394
  process.exit(1);
1326
1395
  }
1327
- if (!isHeadless) console.log(` \u2713 Claude auth detected (${claudeAuth.method})`);
1396
+ if (!isHeadless) {
1397
+ console.log(` \u2713 Claude auth detected (${claudeAuth.method})`);
1398
+ if (claudeAuth.apiKeyOverridden) {
1399
+ console.log(
1400
+ " \u21B3 ANTHROPIC_API_KEY found but ignored \u2014 using Claude Pro/Max OAuth."
1401
+ );
1402
+ console.log(
1403
+ " Set IMPLICIT_USE_API_KEY=1 to bill via API instead."
1404
+ );
1405
+ }
1406
+ }
1328
1407
  let auth = options.token ? authFromAccessToken(options.token, env) : loadAuth();
1329
1408
  if (!auth) {
1330
1409
  if (isHeadless) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@implicit-ai/relay",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Control Claude Code from your phone with Implicit",
5
5
  "type": "module",
6
6
  "bin": {