Skip to content

Commit 07bd4b7

Browse files
Merge pull request #8 from New1Direction/feat/claude-api-key-only
feat: Claude is API-key only; remove subscription OAuth; docs explainer (v1.6.0)
2 parents 6ac2e77 + 15a7e86 commit 07bd4b7

12 files changed

Lines changed: 208 additions & 481 deletions

File tree

CHANGELOG.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,39 @@ Every release of RepoLens, newest first. Want the friendly highlights instead of
44
the full detail? See **[What's new](README.md)** in the README.
55

66
This project follows [Semantic Versioning](https://semver.org/) and groups changes
7-
by theme. Dates are when the release landed on `main` — 1.1.0 through 1.5.0 shipped
7+
by theme. Dates are when the release landed on `main` — 1.1.0 through 1.6.0 shipped
88
the same day, as a rapid burst of improvements, so they share a date.
99

10+
## [1.6.0] — 2026-06-13 · _Claude is API-key only_
11+
12+
### Removed
13+
14+
- **The Claude *subscription* sign-in ("Sign in with Claude").** It never reliably
15+
worked, and it can't: Anthropic binds Claude Pro/Max OAuth tokens to their own
16+
**Claude Code** client (validated server-side via an identity system prompt + beta
17+
flags) and, as of 2026, its terms **prohibit using subscription authentication in
18+
third-party products**. The only way to make it "work" is to impersonate Claude
19+
Code — which is a terms violation that risks getting **the user's own Anthropic
20+
account banned** and breaks whenever Anthropic rotates a flag. So we took it out
21+
rather than ship spoofing.
22+
23+
### Changed
24+
25+
- **Claude now connects with a Console API key only** (`sk-ant-api…` from
26+
console.anthropic.com). The Anthropic card's *Connect* opens the key field directly;
27+
`callAnthropic` is a clean `x-api-key` request with no OAuth/exchange branches.
28+
- Dropped the now-unused `claude.ai`, `platform.claude.com`, and
29+
`console.anthropic.com` host permissions (kept `api.anthropic.com` for inference).
30+
- Deleted the dead `oauth-anthropic.js` module and its callback interception.
31+
32+
### Notes
33+
34+
- **This does not affect the working sign-ins.** **Grok** (Grok CLI device flow),
35+
**OpenRouter**, and **OpenAI** (Sign in with ChatGPT, added in 1.5.0) still use
36+
one-click OAuth — those vendors *support* third-party OAuth. Anthropic is the one
37+
that doesn't.
38+
- **Free is still easy:** local **Ollama** (no key) or **Gemini's** free tier.
39+
1040
## [1.5.0] — 2026-06-13 · _Sign in with ChatGPT_
1141

1242
### Added

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
![Zero build](https://img.shields.io/badge/build-none-0e1722)
1111
![Vanilla ES modules](https://img.shields.io/badge/vanilla-ES_modules-f7df1e?logo=javascript&logoColor=black)
1212
![Tests](https://img.shields.io/badge/tests-420%2B_passing-4ade80)
13-
![Version](https://img.shields.io/badge/version-1.5.0-7c5cff)
13+
![Version](https://img.shields.io/badge/version-1.6.0-7c5cff)
1414
![Storage](https://img.shields.io/badge/storage-in--browser_IndexedDB-38bdf8)
1515

1616
</div>
@@ -43,6 +43,11 @@ Plus **SKTPG** (a one-tap State / Known-pitfalls / Trajectory / Proof / Growth r
4343

4444
Newest first — the highlights. Full, detailed notes live in the **[changelog](CHANGELOG.md)**.
4545

46+
### v1.6.0 — Claude is API-key only
47+
48+
- 🔑 **Removed the Claude *subscription* sign-in.** Anthropic locks Claude Pro/Max tokens to their own Claude Code app and, as of 2026, prohibits subscription sign-in in third-party tools — so that login could never work here without impersonating Claude Code (which risks getting **your** account banned). Connect Claude with a **Console API key** instead.
49+
- 🆓 **Want $0?** Use **local Ollama** (no key) or **Gemini's free tier** — both already supported. See the [How models & sign-in work](website/content/docs/how-it-works.mdx) guide.
50+
4651
### v1.5.0 — Sign in with ChatGPT
4752

4853
- 🔓 **Connect OpenAI without a key***Sign in with ChatGPT* uses the **same login the Codex CLI does**: approve it on OpenAI's page and RepoLens handles the rest. Joins Claude (Claude Code login) and Grok (Grok CLI login) — the three big CLI sign-ins are now all here.
@@ -92,7 +97,7 @@ No accounts. No backend. Your keys, your machine.
9297

9398
## Models — your keys, your call
9499

95-
Bring your own provider. Five are **first-class** (OAuth where available — Anthropic, Grok, OpenRouter — otherwise an API key) and fan out across a **smart fallback chain**: RepoLens tries them in order and drops to the next if one errors, so a single key is enough to start.
100+
Bring your own provider. Five are **first-class** (one-click sign-in where the vendor allows it — **Grok**, **OpenRouter**, and **OpenAI/ChatGPT** — otherwise an API key; **Claude** is API-key only) and fan out across a **smart fallback chain**: RepoLens tries them in order and drops to the next if one errors, so a single key is enough to start.
96101

97102
**Nous** (Nous Research) **→ Gemini → OpenRouter → Grok → Anthropic**
98103

background.js

Lines changed: 14 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,6 @@ import { nodeIdFor, edgeIdFor, ideaIdFor } from './graph.js';
2626
import { deriveCapabilities } from './taxonomy.js';
2727
import { combineCandidates } from './combinator.js';
2828
import { buildCombinatorPrompt, parseCombinator } from './combinator-prompt.js';
29-
import {
30-
ANTHROPIC_OAUTH_ERROR_KEY,
31-
ANTHROPIC_OAUTH_STATE_KEY,
32-
ANTHROPIC_OAUTH_VERIFIER_KEY,
33-
clearAnthropicCredentials,
34-
exchangeAnthropicCode,
35-
getAnthropicCredentials,
36-
isAnthropicOAuthCallbackUrl,
37-
refreshAnthropicToken,
38-
} from './oauth-anthropic.js';
3929
import { refreshXaiToken, XAI_CHAT_PROXY } from './oauth-xai.js';
4030
import {
4131
OPENAI_OAUTH_ERROR_KEY,
@@ -218,80 +208,10 @@ function resolveCompetitor(input) {
218208
return { platform: 'github', repoId };
219209
}
220210

221-
// ─── Anthropic OAuth callback handling (robust intercept for claude.ai → console.anthropic.com) ───
222-
223-
// One redirect can fire BOTH webNavigation.onCompleted and tabs.onUpdated, and the
224-
// auth code is single-use — only the first handler may run the exchange.
211+
// One redirect can fire BOTH a navigation event and tabs.onUpdated, and the auth
212+
// code is single-use — this de-dups so only the first handler runs the exchange.
225213
const _handledOAuthCodes = new Set();
226214

227-
async function handleAnthropicOAuthCallback(rawUrl, tabId) {
228-
if (!rawUrl || !isAnthropicOAuthCallbackUrl(rawUrl)) return;
229-
230-
console.log('[RepoLens OAuth] Callback detected:', rawUrl.split('?')[0]); // strip the single-use ?code=…
231-
232-
let url;
233-
try {
234-
url = new URL(rawUrl);
235-
} catch {
236-
return;
237-
}
238-
239-
const code = url.searchParams.get('code');
240-
const state = url.searchParams.get('state');
241-
const error = url.searchParams.get('error');
242-
const errorDesc = url.searchParams.get('error_description');
243-
244-
// Clean verifier/state (the "pending flow" markers). Leave ERROR_KEY for the waiting UI to consume.
245-
const cleanupFlowMarkers = async () => {
246-
await chrome.storage.local.remove([
247-
ANTHROPIC_OAUTH_VERIFIER_KEY,
248-
ANTHROPIC_OAUTH_STATE_KEY,
249-
]).catch(() => {});
250-
};
251-
252-
if (error) {
253-
const msg = errorDesc || error;
254-
console.warn('[RepoLens OAuth] Provider returned error:', msg);
255-
await chrome.storage.local.set({ [ANTHROPIC_OAUTH_ERROR_KEY]: `Claude OAuth error: ${msg}` });
256-
await cleanupFlowMarkers();
257-
if (tabId) chrome.tabs.remove(tabId).catch(() => {});
258-
return;
259-
}
260-
261-
if (!code) {
262-
console.warn('[RepoLens OAuth] No code in callback URL');
263-
await cleanupFlowMarkers();
264-
if (tabId) chrome.tabs.remove(tabId).catch(() => {});
265-
return;
266-
}
267-
268-
if (_handledOAuthCodes.has(code)) return; // the other listener got here first
269-
_handledOAuthCodes.add(code);
270-
271-
const stored = await chrome.storage.local.get([ANTHROPIC_OAUTH_VERIFIER_KEY, ANTHROPIC_OAUTH_STATE_KEY]);
272-
const verifier = stored[ANTHROPIC_OAUTH_VERIFIER_KEY];
273-
const storedState = stored[ANTHROPIC_OAUTH_STATE_KEY];
274-
275-
if (!verifier) {
276-
console.warn('[RepoLens OAuth] No stored verifier — flow may have been interrupted or was for another extension');
277-
await cleanupFlowMarkers();
278-
if (tabId) chrome.tabs.remove(tabId).catch(() => {});
279-
return;
280-
}
281-
282-
try {
283-
await exchangeAnthropicCode({ code, state, verifier, storedState });
284-
console.log('[RepoLens OAuth] Success — tokens stored');
285-
await cleanupFlowMarkers();
286-
if (tabId) chrome.tabs.remove(tabId).catch(() => {});
287-
} catch (err) {
288-
console.error('[RepoLens OAuth] Exchange error:', err.message);
289-
await chrome.storage.local.set({ [ANTHROPIC_OAUTH_ERROR_KEY]: err.message });
290-
await cleanupFlowMarkers();
291-
if (tabId) chrome.tabs.remove(tabId).catch(() => {});
292-
}
293-
}
294-
295215
// ─── OpenAI OAuth callback handling ("Sign in with ChatGPT", Codex CLI flow) ───
296216
//
297217
// The redirect lands on http://localhost:1455/auth/callback — the loopback server
@@ -366,11 +286,6 @@ async function handleOpenAIOAuthCallback(rawUrl, tabId) {
366286
}
367287
}
368288

369-
chrome.webNavigation.onCompleted.addListener((details) => {
370-
if (details.frameId !== 0) return;
371-
handleAnthropicOAuthCallback(details.url, details.tabId);
372-
});
373-
374289
// The OpenAI loopback redirect can't load (no local server), so onCompleted never
375290
// fires for it — onBeforeNavigate runs first and still carries the ?code=.
376291
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
@@ -380,7 +295,6 @@ chrome.webNavigation.onBeforeNavigate.addListener((details) => {
380295

381296
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
382297
if (changeInfo.url) {
383-
handleAnthropicOAuthCallback(changeInfo.url, tabId);
384298
handleOpenAIOAuthCallback(changeInfo.url, tabId);
385299
}
386300
});
@@ -978,44 +892,22 @@ async function callAIInner(keys, prompt, part) {
978892
throw new Error(rankErrors(failures).userMessage);
979893
}
980894

895+
// Anthropic Messages API with a standard Console API key (sk-ant-api…) via x-api-key.
896+
// Subscription/OAuth sign-in was removed: Anthropic binds Claude-subscription tokens to
897+
// the Claude Code client (server-side identity checks) and, as of 2026, prohibits using
898+
// subscription auth in third-party apps — so the only supported path is a Console key.
981899
async function callAnthropic(model = 'claude-sonnet-4-6', prompt) {
982-
// Two credential styles share the `anthropicKey` slot:
983-
// • a standard API key (sk-ant-api…) → sent via x-api-key ← supported, reliable
984-
// • an OAuth access token (has a refresh token) → exchanged for an API key, or
985-
// sent raw with the OAuth beta flags as a fallback
986-
//
987-
// Uses Hermes-hardened getAnthropicCredentials + refreshAnthropicToken (60s skew + inflight dedup)
988-
const { anthropicKey, anthropicRefresh } = await chrome.storage.local.get(['anthropicKey', 'anthropicRefresh']);
989-
const creds = await getAnthropicCredentials().catch(() => null);
990-
991-
const isOAuth = !!(anthropicRefresh || creds?.refresh_token);
992-
let token = isOAuth ? await refreshAnthropicToken() : anthropicKey;
993-
if (!token) throw new Error('No Anthropic credential — add a key in Settings');
994-
995-
// OAuth tokens need exchange via create_api_key to get a usable API key.
996-
// Direct x-api-key with OAuth tokens may fail (401 "invalid x-api-key").
997-
// The Claude Code SDK does this exchange internally.
998-
let usingRawOAuthToken = false;
999-
if (isOAuth) {
1000-
const exchanged = await exchangeAnthropicOAuthForApiKey(token);
1001-
usingRawOAuthToken = exchanged === token;
1002-
token = exchanged;
1003-
}
1004-
1005-
const headers = {
1006-
'anthropic-version': '2023-06-01',
1007-
'anthropic-dangerous-direct-browser-access': 'true',
1008-
'Content-Type': 'application/json',
1009-
'x-api-key': token,
1010-
};
1011-
// A raw OAuth access token (exchange unavailable) only works with the beta flags.
1012-
if (usingRawOAuthToken) {
1013-
headers['anthropic-beta'] = 'oauth-2025-04-20,claude-code-20250219';
1014-
}
900+
const { anthropicKey } = await chrome.storage.local.get('anthropicKey');
901+
if (!anthropicKey) throw new Error('No Anthropic API key — add one in Settings');
1015902

1016903
const res = await fetch('https://api.anthropic.com/v1/messages', {
1017904
method: 'POST',
1018-
headers,
905+
headers: {
906+
'anthropic-version': '2023-06-01',
907+
'anthropic-dangerous-direct-browser-access': 'true',
908+
'Content-Type': 'application/json',
909+
'x-api-key': anthropicKey,
910+
},
1019911
body: JSON.stringify({
1020912
model,
1021913
max_tokens: 4096,
@@ -1024,20 +916,6 @@ async function callAnthropic(model = 'claude-sonnet-4-6', prompt) {
1024916
});
1025917
if (!res.ok) {
1026918
const err = await res.json().catch(() => ({}));
1027-
// Only clear stored OAuth tokens on a real auth failure — never on transient errors.
1028-
if (res.status === 401 && isOAuth) {
1029-
// The cached exchanged key may be the stale credential being rejected — drop it
1030-
// so a reconnect takes effect immediately instead of after the SW restarts.
1031-
_anthropicExchangedKey = null;
1032-
_anthropicExchangedKeyExpiry = 0;
1033-
// Hermes-hardened clear (structured + legacy flat keys)
1034-
if (typeof clearAnthropicCredentials === 'function') {
1035-
await clearAnthropicCredentials();
1036-
} else {
1037-
await chrome.storage.local.remove(['anthropicKey', 'anthropicRefresh', 'anthropicExpiry', 'anthropicCredentials']);
1038-
}
1039-
throw new Error('Anthropic session expired — please reconnect in Settings');
1040-
}
1041919
throw new Error(err.error?.message ?? `Anthropic API error ${res.status}`);
1042920
}
1043921
const data = await res.json();
@@ -1046,44 +924,6 @@ async function callAnthropic(model = 'claude-sonnet-4-6', prompt) {
1046924
return text;
1047925
}
1048926

1049-
// Cache the exchanged API key (short-lived, ~8h) to avoid hammering the endpoint.
1050-
let _anthropicExchangedKey = null;
1051-
let _anthropicExchangedKeyExpiry = 0;
1052-
1053-
async function exchangeAnthropicOAuthForApiKey(oauthToken) {
1054-
// Return cached key if still valid (with 5min skew)
1055-
if (_anthropicExchangedKey && Date.now() < _anthropicExchangedKeyExpiry - 300_000) {
1056-
return _anthropicExchangedKey;
1057-
}
1058-
1059-
try {
1060-
const res = await fetch('https://api.anthropic.com/api/oauth/claude_cli/create_api_key', {
1061-
method: 'POST',
1062-
headers: {
1063-
'Content-Type': 'application/json',
1064-
'Authorization': `Bearer ${oauthToken}`,
1065-
},
1066-
body: JSON.stringify({}),
1067-
});
1068-
1069-
if (res.ok) {
1070-
const data = await res.json();
1071-
if (data.api_key) {
1072-
_anthropicExchangedKey = data.api_key;
1073-
_anthropicExchangedKeyExpiry = Date.now() + (data.expires_in || 28800) * 1000;
1074-
return data.api_key;
1075-
}
1076-
}
1077-
// If exchange fails (endpoint might not exist or token type not supported),
1078-
// fall back to using the raw OAuth token (callAnthropic adds the beta flags).
1079-
console.warn('[RepoLens] create_api_key exchange failed, falling back to raw OAuth token');
1080-
} catch (err) {
1081-
console.warn('[RepoLens] create_api_key exchange error:', err.message);
1082-
}
1083-
1084-
return oauthToken;
1085-
}
1086-
1087927
async function callGemini(key, model = 'gemini-2.5-flash', prompt) {
1088928
const url = 'https://generativelanguage.googleapis.com/v1beta/models/' + encodeURIComponent(model) + ':generateContent?key=' + encodeURIComponent(key);
1089929
const res = await fetch(url, {

manifest.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "RepoLens",
4-
"version": "1.5.0",
4+
"version": "1.6.0",
55
"description": "One-click repo explainer. Powered by Claude.",
66
"permissions": ["storage", "activeTab", "tabs", "identity", "webNavigation"],
77
"host_permissions": [
@@ -13,9 +13,6 @@
1313
"https://registry.npmjs.org/*",
1414
"https://pypi.org/pypi/*",
1515
"https://api.anthropic.com/*",
16-
"https://console.anthropic.com/*",
17-
"https://claude.ai/*",
18-
"https://platform.claude.com/*",
1916
"https://generativelanguage.googleapis.com/*",
2017
"https://openrouter.ai/*",
2118
"https://api.x.ai/*",

0 commit comments

Comments
 (0)