surf-skill 2.1.0 → 2.1.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.1.1 — Robust key rotation: Brave 422 now burns the key
4
+
5
+ ### Bug
6
+
7
+ When a Brave key was malformed (wrong length/charset), the API returns
8
+ HTTP **422** rather than 401. v2.1.0 classified 422 as `caller_4xx`, which
9
+ made dispatch throw without burning the key or trying the next one. That
10
+ violated the cross-provider fallback contract: a single bad-format key
11
+ could short-circuit the chain instead of rotating.
12
+
13
+ ### Fix
14
+
15
+ `src/lib/providers/brave.mjs::mapError` now classifies 422 as `auth`
16
+ (burn key, rotate). The trade-off:
17
+
18
+ - **Real malformed token** → burns the key, dispatch tries the next key
19
+ (or next provider if `--provider` not set). The user gets a result.
20
+ - **Genuinely bad query param** → all keys fail with 422; surfaces as
21
+ `AllProvidersExhausted` with a hint, still actionable.
22
+
23
+ ### Verified
24
+
25
+ Re-ran the 3 fallback tests with v2.1.1:
26
+
27
+ - T1 same-provider rotation (tavily key #0 bad → key #1 succeeds): ✓
28
+ - T2 cross-provider fallback (tavily/parallel both 401 → brave 200): ✓
29
+ - T3 all keys bad incl. malformed Brave: **now burns Brave key + reports
30
+ AllProvidersExhausted** (instead of throwing on the 422)
31
+
32
+ ### Also fixed
33
+
34
+ - `src/lib/dispatch.mjs::VERSION` was stuck at `1.0.0` since the initial
35
+ release; bumped to `2.1.1` so the `X-Client-Name` header surfaces
36
+ the correct CLI version to providers.
37
+ - `SKILL.md::metadata.version` was missed in the v2.1.0 bump (still
38
+ showed `2.0.0`); now `2.1.1`.
39
+
40
+ No breaking changes.
41
+
42
+ ---
43
+
3
44
  ## v2.1.0 — Brave Search as 3rd provider + `--mode` flag
4
45
 
5
46
  ### What's new
package/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: Web search, content extraction, site crawl, URL mapping, and deep r
4
4
  license: MIT
5
5
  allowed-tools: bash
6
6
  metadata:
7
- version: "2.0.0"
7
+ version: "2.1.1"
8
8
  requires: "node>=18; install via `npm i -g surf-skill`; keys via 'surf-skill setup' (multi-key wizard); per-project bash timeout via 'surf-skill project-config'"
9
9
  ---
10
10
 
@@ -15,7 +15,7 @@ import { runProjectConfig, formatProjectConfigResult } from '../src/lib/project-
15
15
  import { providerFromRequestId } from '../src/lib/providers/index.mjs';
16
16
  import { progress, setSilent } from '../src/lib/progress.mjs';
17
17
 
18
- const VERSION = '2.1.0';
18
+ const VERSION = '2.1.1';
19
19
 
20
20
  // Catch SIGTERM/SIGINT so a harness-driven kill surfaces a useful message
21
21
  // instead of dying silently. This is defense-in-depth: dispatch already
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surf-skill",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Multi-provider web skill (Tavily + Parallel AI + Brave Search) for AI coding agents — CLI + Node library + Anthropic Agent Skill. Auto-fallback, multi-key rotation, --mode tiers, per-project timeout config.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -13,7 +13,7 @@ import { sleep } from './flags.mjs';
13
13
  import { progress } from './progress.mjs';
14
14
 
15
15
  const CACHEABLE = new Set(['search', 'extract', 'map']);
16
- const VERSION = '1.0.0';
16
+ const VERSION = '2.1.1';
17
17
 
18
18
  // Detect the agent harness's bash timeout from env vars. The number is the
19
19
  // total time (ms) the harness will allow our process to live before SIGTERM.
@@ -90,7 +90,13 @@ function mapError(status, body) {
90
90
  if (status === 401) return { kind: 'auth', statusCode: status, message: 'invalid Brave key' };
91
91
  if (status === 402) return { kind: 'auth', statusCode: status, message: msg || 'Brave: insufficient credits / billing required' };
92
92
  if (status === 403) return { kind: 'auth', statusCode: status, message: msg || 'forbidden (plan/billing)' };
93
- if (status === 422) return { kind: 'caller_4xx', statusCode: status, message: msg || 'invalid params' };
93
+ // Brave returns 422 for several reasons: malformed token (length/charset
94
+ // wrong, fails BEFORE auth check), OR bad query params. We classify 422 as
95
+ // `auth` so the key gets burned and dispatch rotates. The trade-off: a
96
+ // genuinely-bad query param will fail across ALL keys and surface as
97
+ // AllProvidersExhausted, still actionable. A malformed token is the
98
+ // dominant cause in practice (real tokens hit 401 instead).
99
+ if (status === 422) return { kind: 'auth', statusCode: status, message: msg || 'Brave: malformed token or invalid params (key rotation will retry; if all keys fail, you likely have a bad query)' };
94
100
  if (status === 429) return { kind: 'rate_limit_429', statusCode: status, message: msg || 'Brave rate limit (50 RPS search)' };
95
101
  if (status >= 500) return { kind: 'server_5xx', statusCode: status, message: msg || 'Brave server error' };
96
102
  if (status >= 400) return { kind: 'caller_4xx', statusCode: status, message: msg || `HTTP ${status}` };