@askalf/dario 3.31.2 → 3.31.3

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.
@@ -46,6 +46,23 @@ export interface DetectedOAuthConfig {
46
46
  ccPath?: string;
47
47
  ccHash?: string;
48
48
  }
49
+ /**
50
+ * Normalize the authorize URL to match what CC uses at runtime.
51
+ *
52
+ * The CC binary ships `CLAUDE_AI_AUTHORIZE_URL: "https://claude.com/cai/oauth/authorize"`
53
+ * as a literal string, but at runtime CC's `/login` opens
54
+ * `https://claude.ai/oauth/authorize` directly (empirically verified against
55
+ * CC v2.1.116 by tetsuco in dario#71). Historically the claude.com edge
56
+ * 307-redirected to claude.ai and the browser followed; recent Anthropic-side
57
+ * changes made the post-redirect validation start returning "Invalid request
58
+ * format", while direct requests to claude.ai continue to work. This normalizer
59
+ * rewrites the legacy URL wherever it appears (binary extraction, manual
60
+ * override, cached config) so dario matches CC's runtime behaviour.
61
+ *
62
+ * Intentionally narrow: only the exact legacy URL is rewritten. Any other
63
+ * operator-supplied URL (e.g. a staging endpoint via override) passes through.
64
+ */
65
+ export declare function normalizeAuthorizeUrl(url: string): string;
49
66
  export declare const FALLBACK_FOR_DRIFT_CHECK: Readonly<DetectedOAuthConfig>;
50
67
  /**
51
68
  * Scan binary bytes for the PROD OAuth config block.
@@ -42,12 +42,35 @@ import { existsSync } from 'node:fs';
42
42
  import { homedir, platform } from 'node:os';
43
43
  import { join, dirname } from 'node:path';
44
44
  import { createHash } from 'node:crypto';
45
+ /**
46
+ * Normalize the authorize URL to match what CC uses at runtime.
47
+ *
48
+ * The CC binary ships `CLAUDE_AI_AUTHORIZE_URL: "https://claude.com/cai/oauth/authorize"`
49
+ * as a literal string, but at runtime CC's `/login` opens
50
+ * `https://claude.ai/oauth/authorize` directly (empirically verified against
51
+ * CC v2.1.116 by tetsuco in dario#71). Historically the claude.com edge
52
+ * 307-redirected to claude.ai and the browser followed; recent Anthropic-side
53
+ * changes made the post-redirect validation start returning "Invalid request
54
+ * format", while direct requests to claude.ai continue to work. This normalizer
55
+ * rewrites the legacy URL wherever it appears (binary extraction, manual
56
+ * override, cached config) so dario matches CC's runtime behaviour.
57
+ *
58
+ * Intentionally narrow: only the exact legacy URL is rewritten. Any other
59
+ * operator-supplied URL (e.g. a staging endpoint via override) passes through.
60
+ */
61
+ export function normalizeAuthorizeUrl(url) {
62
+ if (url === 'https://claude.com/cai/oauth/authorize') {
63
+ return 'https://claude.ai/oauth/authorize';
64
+ }
65
+ return url;
66
+ }
45
67
  // Last-resort fallback if CC binary can't be found or scanned.
46
68
  // These values are the CC v2.1.104 PROD OAuth config, extracted from
47
- // the `nh$` object in the shipped binary.
69
+ // the `nh$` object in the shipped binary. authorizeUrl is normalized —
70
+ // see normalizeAuthorizeUrl() above for why this matters (dario#71).
48
71
  const FALLBACK = {
49
72
  clientId: '9d1c250a-e61b-44d9-88ed-5944d1962f5e',
50
- authorizeUrl: 'https://claude.com/cai/oauth/authorize',
73
+ authorizeUrl: 'https://claude.ai/oauth/authorize',
51
74
  tokenUrl: 'https://platform.claude.com/v1/oauth/token',
52
75
  // Scopes match CC v2.1.107+ interactive login: the 5-scope user-only set.
53
76
  // Between CC v2.1.104 and v2.1.107, Anthropic's authorize endpoint flipped
@@ -68,10 +91,12 @@ const FALLBACK = {
68
91
  // needs the exact values the runtime uses — hardcoding them in the script
69
92
  // would drift out of sync silently.
70
93
  export const FALLBACK_FOR_DRIFT_CHECK = FALLBACK;
71
- // -v4 suffix invalidates v3.x caches populated with the 6-scope list that
72
- // Anthropic now rejects (dario #42). On upgrade, users regenerate the cache
73
- // with the new FALLBACK scopes automatically no manual clear required.
74
- const CACHE_PATH = join(homedir(), '.dario', 'cc-oauth-cache-v4.json');
94
+ // -v5 suffix invalidates v3.x caches populated with the pre-normalized
95
+ // authorize URL that now fails "Invalid request format" via the 307-redirect
96
+ // hop (dario#71). On upgrade, users regenerate the cache with the normalized
97
+ // FALLBACK.authorizeUrl automatically no manual clear required. Previous
98
+ // bump was -v3 → -v4 in v3.19.4 for the 6-scope → 5-scope rotation (dario#42).
99
+ const CACHE_PATH = join(homedir(), '.dario', 'cc-oauth-cache-v5.json');
75
100
  const DEFAULT_OVERRIDE_PATH = join(homedir(), '.dario', 'oauth-config.override.json');
76
101
  function candidatePaths() {
77
102
  const home = homedir();
@@ -162,9 +187,16 @@ function applyManualOverride(config, override) {
162
187
  return config;
163
188
  warnOnNonHttpsOverride('authorizeUrl', override.authorizeUrl);
164
189
  warnOnNonHttpsOverride('tokenUrl', override.tokenUrl);
190
+ // Normalize any override-supplied authorizeUrl too — users who pasted the
191
+ // legacy claude.com URL into ~/.dario/oauth-config.override.json pre-#71
192
+ // shouldn't be silently broken after upgrade.
193
+ const normalizedOverride = { ...override };
194
+ if (normalizedOverride.authorizeUrl) {
195
+ normalizedOverride.authorizeUrl = normalizeAuthorizeUrl(normalizedOverride.authorizeUrl);
196
+ }
165
197
  return {
166
198
  ...config,
167
- ...override,
199
+ ...normalizedOverride,
168
200
  source: 'override',
169
201
  };
170
202
  }
@@ -222,7 +254,7 @@ export function scanBinaryForOAuthConfig(buf) {
222
254
  let authorizeUrl = FALLBACK.authorizeUrl;
223
255
  const authMatch = /CLAUDE_AI_AUTHORIZE_URL\s*:\s*"([^"]+)"/.exec(prodBlock);
224
256
  if (authMatch && authMatch[1])
225
- authorizeUrl = authMatch[1];
257
+ authorizeUrl = normalizeAuthorizeUrl(authMatch[1]);
226
258
  let tokenUrl = FALLBACK.tokenUrl;
227
259
  const tokenMatch = /TOKEN_URL\s*:\s*"(https:\/\/[^"]*\/oauth\/token[^"]*)"/.exec(prodBlock);
228
260
  if (tokenMatch && tokenMatch[1])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "3.31.2",
3
+ "version": "3.31.3",
4
4
  "description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
5
5
  "type": "module",
6
6
  "bin": {