surf-skill 2.0.0
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 +175 -0
- package/LICENSE +21 -0
- package/README.md +430 -0
- package/SKILL.md +278 -0
- package/bin/surf-skill.mjs +539 -0
- package/package.json +55 -0
- package/references/COSTS.md +72 -0
- package/references/parallel-api.md +155 -0
- package/references/tavily-api.md +90 -0
- package/src/env.mjs +125 -0
- package/src/index.mjs +22 -0
- package/src/install/postinstall.mjs +73 -0
- package/src/install/preuninstall.mjs +25 -0
- package/src/lib/api/crawl.mjs +55 -0
- package/src/lib/api/extract.mjs +46 -0
- package/src/lib/api/map.mjs +43 -0
- package/src/lib/api/research.mjs +96 -0
- package/src/lib/api/search.mjs +92 -0
- package/src/lib/audit.mjs +34 -0
- package/src/lib/cache.mjs +46 -0
- package/src/lib/cost.mjs +90 -0
- package/src/lib/dispatch.mjs +320 -0
- package/src/lib/flags.mjs +63 -0
- package/src/lib/format.mjs +110 -0
- package/src/lib/harness-install.mjs +149 -0
- package/src/lib/keys-cmd.mjs +138 -0
- package/src/lib/progress.mjs +81 -0
- package/src/lib/project-config.mjs +145 -0
- package/src/lib/providers/index.mjs +32 -0
- package/src/lib/providers/parallel.mjs +270 -0
- package/src/lib/providers/tavily.mjs +245 -0
- package/src/lib/setup.mjs +111 -0
- package/src/lib/state.mjs +197 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v2.0.0 — npm package, cross-OS install, library mode
|
|
4
|
+
|
|
5
|
+
### What's new
|
|
6
|
+
|
|
7
|
+
- **One-liner cross-OS install via npm.** `npm i -g surf-skill` works on
|
|
8
|
+
Linux, macOS, and Windows. Postinstall script creates symlinks into all
|
|
9
|
+
4 supported agent harnesses (Claude Code, OpenCode, Codex CLI, Pi
|
|
10
|
+
Coding Agent), initializes `~/.config/surf/keys.json`, and cleans up
|
|
11
|
+
legacy symlinks from prior versions. Falls back to recursive copy on
|
|
12
|
+
Windows without Developer Mode.
|
|
13
|
+
- **Library mode for Node / Next.js / Express.** Import named functions:
|
|
14
|
+
```js
|
|
15
|
+
import { search, extract, research } from 'surf-skill';
|
|
16
|
+
const r = await search('claude api', { max: 3 });
|
|
17
|
+
```
|
|
18
|
+
Auto-discovers keys from `opts → process.env → .env → ~/.config/surf/keys.json`
|
|
19
|
+
(each level can contribute; results merged + deduped).
|
|
20
|
+
- **Multi-key wizard.** `surf-skill setup` now prompts for N keys per
|
|
21
|
+
provider (Enter empty to finish that provider). Add 1+ Tavily + 1+
|
|
22
|
+
Parallel keys in one pass.
|
|
23
|
+
- **Auto-wizard on first TTY use.** Running any command that needs keys
|
|
24
|
+
in a TTY with empty config auto-launches the wizard, then resumes the
|
|
25
|
+
command. CI/non-TTY behavior unchanged (clear actionable error).
|
|
26
|
+
- **Batch search in the library too.** `search(['q1', 'q2', 'q3'], opts)`
|
|
27
|
+
returns `{ summary, data: { batches } }` — same shape as CLI batch.
|
|
28
|
+
|
|
29
|
+
### Breaking changes
|
|
30
|
+
|
|
31
|
+
- **Distribution moved from `git clone + install.sh` to `npm i -g`.**
|
|
32
|
+
If you installed via the old `install.sh`:
|
|
33
|
+
```bash
|
|
34
|
+
# Remove old install
|
|
35
|
+
rm -f ~/.local/bin/surf-skill
|
|
36
|
+
rm -rf ~/.agents/skills/surf-skill ~/.claude/skills/surf-skill \
|
|
37
|
+
~/.codex/skills/surf-skill ~/.pi/agent/skills/surf-skill
|
|
38
|
+
# Install via npm
|
|
39
|
+
npm i -g surf-skill
|
|
40
|
+
# Your ~/.config/surf/keys.json is preserved.
|
|
41
|
+
surf-skill keys list
|
|
42
|
+
```
|
|
43
|
+
- **Repo layout**: `skills/surf-skill/*` moved to root. The package now
|
|
44
|
+
lives directly at the repo root for npm publishing. The `install.sh`
|
|
45
|
+
script is gone (replaced by `src/install/postinstall.mjs`).
|
|
46
|
+
- **Package name** unchanged (`surf-skill`).
|
|
47
|
+
- **CLI surface unchanged** — all commands, flags, and behavior identical.
|
|
48
|
+
- **State location unchanged** — `~/.config/surf/keys.json` and
|
|
49
|
+
`~/.cache/surf/` preserved.
|
|
50
|
+
|
|
51
|
+
### Files added
|
|
52
|
+
|
|
53
|
+
- `src/index.mjs` — library entry (named exports)
|
|
54
|
+
- `src/env.mjs` — key discovery hierarchy + dotenv loader
|
|
55
|
+
- `src/install/postinstall.mjs` — cross-OS postinstall (idempotent)
|
|
56
|
+
- `src/install/preuninstall.mjs` — clean up symlinks on `npm rm`
|
|
57
|
+
- `src/lib/harness-install.mjs` — `symlinkOrCopy` helper, legacy cleanup
|
|
58
|
+
- `src/lib/api/{search,extract,crawl,map,research}.mjs` — library wrappers
|
|
59
|
+
|
|
60
|
+
### Files modified
|
|
61
|
+
|
|
62
|
+
- `package.json` — `type: module`, `bin`, `main`, `exports`, `files`,
|
|
63
|
+
`postinstall`/`preuninstall` scripts; version 1.0.0 → 2.0.0
|
|
64
|
+
- `bin/surf-skill.mjs` — imports point to `../src/lib/`; VERSION 2.0.0;
|
|
65
|
+
auto-wizard block on first TTY use
|
|
66
|
+
- `src/lib/setup.mjs` — multi-key loop (N keys per provider)
|
|
67
|
+
- `src/lib/dispatch.mjs` — accepts `runCtx.state` for in-memory library mode
|
|
68
|
+
- `README.md` — one-liner install, library section, bonus features
|
|
69
|
+
- `SKILL.md` — `metadata.version: "2.0.0"`, npm install in requires
|
|
70
|
+
|
|
71
|
+
### Files removed
|
|
72
|
+
|
|
73
|
+
- `skills/surf-skill/install.sh` — replaced by postinstall.mjs
|
|
74
|
+
- `skills/` directory — dissolved into root (npm-friendly layout)
|
|
75
|
+
- All `CHANGELOG-v2.x.md` (consolidated into this CHANGELOG.md in v1.0.0)
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## v1.0.0 — Initial release
|
|
80
|
+
|
|
81
|
+
`surf-skill` is a multi-provider web skill for AI coding agents that fronts
|
|
82
|
+
**Tavily** and **Parallel AI** behind a single bash CLI. The agent calling
|
|
83
|
+
this skill never picks the provider — `surf-skill` does, with automatic
|
|
84
|
+
key rotation, provider fallback, and last-known-good persistence.
|
|
85
|
+
|
|
86
|
+
### Capabilities
|
|
87
|
+
|
|
88
|
+
| Operation | Tavily | Parallel | Default order |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `search` | ✓ | ✓ | tavily → parallel |
|
|
91
|
+
| `extract` | ✓ | ✓ | tavily → parallel |
|
|
92
|
+
| `crawl` | ✓ | ✗ | tavily only |
|
|
93
|
+
| `map` | ✓ | ✗ | tavily only |
|
|
94
|
+
| `research-start` / `research` | ✓ | ✓ | parallel → tavily |
|
|
95
|
+
| `research-poll` | by `request_id` prefix | by `request_id` prefix | sticky |
|
|
96
|
+
|
|
97
|
+
### Features
|
|
98
|
+
|
|
99
|
+
- **Multi-provider fallback.** Tavily ↔ Parallel AI by capability map.
|
|
100
|
+
- **Multi-key rotation per provider.** Burn on `401/403/402` or persistent
|
|
101
|
+
`5xx`; burned keys auto-reset on the first day of the next calendar month.
|
|
102
|
+
- **Provider chain memory.** `last_ok_provider` persisted in
|
|
103
|
+
`~/.config/surf/keys.json` so the next call starts on the hot path.
|
|
104
|
+
- **`--provider <tavily|parallel>`** forces a specific provider (disables
|
|
105
|
+
fallback for that call). `--no-fallback` pins to the default provider.
|
|
106
|
+
- **Batch search.** Pass multiple positional args to `search` and each is
|
|
107
|
+
an independent query, run sequentially, with partial failures reported
|
|
108
|
+
inline.
|
|
109
|
+
- **Progress logs to stderr.** One self-contained line per event
|
|
110
|
+
(`[surf HH:MM:SS] ▸/✓/✗/↻/⚠/⏱`). Stable format for agent parsing.
|
|
111
|
+
Stdout stays clean for JSON/Markdown. `--quiet` / `SURF_QUIET=1`
|
|
112
|
+
silences.
|
|
113
|
+
- **Interactive onboarding.** `surf-skill setup` (TTY) wizard prompts for
|
|
114
|
+
keys and persists to `~/.config/surf/keys.json` (chmod 600). On error,
|
|
115
|
+
TTY users see `→ Run 'surf-skill setup' to configure keys interactively.`
|
|
116
|
+
- **Per-project bash-timeout config.** `surf-skill project-config`
|
|
117
|
+
auto-detects the harness via `.github/`, `.claude/`, `.pi/` markers and
|
|
118
|
+
writes the right config to raise the bash timeout. Required for GH
|
|
119
|
+
Copilot CLI (default 30 s), recommended for Claude Code / Pi.
|
|
120
|
+
- **Self-budget timeout guard.** Reads the harness bash timeout from env
|
|
121
|
+
vars (`BASH_DEFAULT_TIMEOUT_MS`, `PI_BASH_DEFAULT_TIMEOUT_SECONDS`,
|
|
122
|
+
`OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS`, or `SURF_AGENT_BUDGET_MS`
|
|
123
|
+
override). Aborts early with `LikelyAgentTimeout` instead of being
|
|
124
|
+
killed silently by SIGTERM. Unknown harness → assume 30 s.
|
|
125
|
+
- **SIGTERM / SIGINT handler.** Defense in depth: surfaces a
|
|
126
|
+
`KilledBySignal` message with the same `project-config` hint before
|
|
127
|
+
exit 143.
|
|
128
|
+
- **Per-project config writer** detects `.github/`, `.claude/`, `.pi/` and
|
|
129
|
+
writes only what the harness in this project needs.
|
|
130
|
+
- **Local response cache** (`~/.cache/surf/`, TTL 6 h) keyed by
|
|
131
|
+
`sha256(operation, args)`; `--no-cache` bypasses; cache survives provider
|
|
132
|
+
fallbacks.
|
|
133
|
+
- **Local usage ledger** (`~/.cache/surf/usage.jsonl`) per-provider
|
|
134
|
+
breakdown via `surf-skill cost`.
|
|
135
|
+
- **Audit log** (`~/.cache/surf/audit.log`) records provider name and key
|
|
136
|
+
INDEX, never the key itself.
|
|
137
|
+
- **Cost guard.** Estimates > 10 credits are blocked unless
|
|
138
|
+
`--confirm-expensive` (or `SURF_ALLOW_EXPENSIVE=1`).
|
|
139
|
+
- **Predictable JSON.** `--json` returns a normalized envelope with the
|
|
140
|
+
same shape across providers. `--raw-json` exposes the provider response
|
|
141
|
+
for debugging.
|
|
142
|
+
|
|
143
|
+
### Default behavior
|
|
144
|
+
|
|
145
|
+
- `search --depth` defaults to `advanced` (better quality, ~3–10 s,
|
|
146
|
+
2 credits). Pass `--depth basic` for the cheaper/faster path.
|
|
147
|
+
- `surf-skill research` is capped at 50 s and refuses `--model pro`/`ultra`
|
|
148
|
+
(use `research-start` + `research-poll` for those).
|
|
149
|
+
|
|
150
|
+
### Provider notes (verified 2026-05-20 against live APIs)
|
|
151
|
+
|
|
152
|
+
- **Tavily** `POST /search`: `Authorization: Bearer <key>`. Body accepts
|
|
153
|
+
`query`, `search_depth`, `max_results`, `topic`, `time_range`,
|
|
154
|
+
`include_domains`, `exclude_domains`, `country`, `include_answer`,
|
|
155
|
+
`include_raw_content`, etc.
|
|
156
|
+
- **Parallel AI** `POST /v1/search`: `x-api-key: <key>`. Body accepts
|
|
157
|
+
ONLY `{ objective, search_queries }`. Any other field (e.g. `processor`,
|
|
158
|
+
`max_results`) is rejected with `Extra inputs are not permitted`.
|
|
159
|
+
Tavily-only knobs are silently ignored when the call lands on Parallel.
|
|
160
|
+
- **Parallel** has no crawl / no URL map / no public usage endpoint.
|
|
161
|
+
|
|
162
|
+
### Supported harnesses
|
|
163
|
+
|
|
164
|
+
| Harness | Default bash | Max | Coverage after install |
|
|
165
|
+
|---|---|---|---|
|
|
166
|
+
| **Claude Code** | 120 s | 600 s (hard) | 300 s default via `~/.claude/settings.json` |
|
|
167
|
+
| **Pi Coding Agent** | 120 s | 600 s | 300 s default via `~/.pi/agent/settings.json` |
|
|
168
|
+
| **GH Copilot CLI** | **30 s** | not documented | per-project `.github/copilot-hooks.json` (run `surf-skill project-config`) |
|
|
169
|
+
| **OpenCode** | varies | 600 s | 600 s default via `~/.config/opencode/opencode.json` |
|
|
170
|
+
| **Codex CLI** | n/a | n/a | symlinked under `~/.codex/skills/surf-skill/` |
|
|
171
|
+
|
|
172
|
+
### Stack
|
|
173
|
+
|
|
174
|
+
Node ≥ 18, bash, **zero npm dependencies**. The full CLI is under 500 LOC
|
|
175
|
+
in `skills/surf-skill/bin/surf-skill.mjs` + `skills/surf-skill/lib/*.mjs`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# surf-skill
|
|
2
|
+
|
|
3
|
+
**One bash command. Two providers. Zero MCP.** A multi-provider web skill
|
|
4
|
+
for AI coding agents that fronts **Tavily** and **Parallel AI** behind a
|
|
5
|
+
single CLI (`surf-skill`). The agent calling this skill **never picks the
|
|
6
|
+
provider** — `surf-skill` does, with automatic key rotation, provider
|
|
7
|
+
fallback, and last-known-good persistence.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
search ─┐
|
|
11
|
+
extract ┤ ┌──▶ Tavily (search, extract, crawl, map, research)
|
|
12
|
+
crawl ──┼──▶ surf ───┤
|
|
13
|
+
map ───┤ └──▶ Parallel (search, extract, research async)
|
|
14
|
+
research┘
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| | |
|
|
18
|
+
|---|---|
|
|
19
|
+
| **Status** | v1.0.0 |
|
|
20
|
+
| **Runtime** | Node ≥ 18, bash. Zero npm deps. |
|
|
21
|
+
| **Storage** | `~/.config/surf/keys.json` (chmod 600). Never read from env at runtime. |
|
|
22
|
+
| **Supported agents** | Claude Code · GitHub Copilot CLI · Pi Coding Agent · OpenCode · Codex CLI |
|
|
23
|
+
| **Spec** | [Anthropic Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills) |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quickstart (30 seconds)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# One-liner cross-OS install (Linux, macOS, Windows)
|
|
31
|
+
npm i -g surf-skill
|
|
32
|
+
|
|
33
|
+
# That's it — postinstall creates symlinks into all supported harnesses,
|
|
34
|
+
# initializes ~/.config/surf/keys.json, and prints a hint.
|
|
35
|
+
# On first run, an interactive wizard auto-launches in TTY:
|
|
36
|
+
|
|
37
|
+
surf-skill search "your query"
|
|
38
|
+
# → "No keys configured. Launching setup wizard…"
|
|
39
|
+
# → prompts for Tavily key #1, #2, …, Parallel key #1, #2, …
|
|
40
|
+
# → resumes your command
|
|
41
|
+
|
|
42
|
+
# In each project where you'll use surf-skill (REQUIRED for GH Copilot CLI):
|
|
43
|
+
cd path/to/your-project
|
|
44
|
+
surf-skill project-config
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You can also run `surf-skill setup` manually anytime to add more keys.
|
|
48
|
+
|
|
49
|
+
### Use as a Node library
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm i surf-skill
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
import { search, extract, research } from 'surf-skill';
|
|
57
|
+
|
|
58
|
+
// Auto-discovers keys: opts > process.env > .env > ~/.config/surf/keys.json
|
|
59
|
+
const r = await search('claude api', { max: 3 });
|
|
60
|
+
console.log(r.data.results[0].url);
|
|
61
|
+
|
|
62
|
+
// Or pass keys explicitly (great for serverless / Next.js API routes)
|
|
63
|
+
const r2 = await search('x', {
|
|
64
|
+
tavilyKeys: [process.env.MY_TAVILY_1, process.env.MY_TAVILY_2],
|
|
65
|
+
depth: 'advanced',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Batch search (single call, N queries, partial-failure tolerant)
|
|
69
|
+
const batch = await search(['topic A', 'topic B', 'topic C'], { max: 2 });
|
|
70
|
+
|
|
71
|
+
// Deep research
|
|
72
|
+
const job = await research('compare X vs Y', { model: 'mini' });
|
|
73
|
+
console.log(job.data.content);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Library works server-side (Node / Next.js API routes / Express). Not for
|
|
77
|
+
browser bundles — Tavily and Parallel don't enable CORS for browser origins.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Why this exists
|
|
82
|
+
|
|
83
|
+
You have a Tavily key. Maybe a Parallel one too. Maybe several Tavily keys
|
|
84
|
+
to spread cost across accounts. Today every agent skill is **1-to-1** with
|
|
85
|
+
a provider — when a key dies or a provider has an outage, your agent loop
|
|
86
|
+
breaks.
|
|
87
|
+
|
|
88
|
+
`surf-skill` is a connector:
|
|
89
|
+
|
|
90
|
+
- **Multi-key per provider.** Add as many keys as you want; rotation is
|
|
91
|
+
automatic on `401`/`403`/`402` (auth, insufficient credits) or persistent
|
|
92
|
+
`5xx`. Burned keys auto-reset on the first day of the next calendar
|
|
93
|
+
month (assuming monthly billing).
|
|
94
|
+
- **Provider fallback.** If all Tavily keys are burned, `search`/`extract`
|
|
95
|
+
fail over to Parallel — transparently. `crawl` and `map` stay on Tavily
|
|
96
|
+
(Parallel doesn't have them). `research` defaults to Parallel first
|
|
97
|
+
because its Task API is the strongest deep-research surface.
|
|
98
|
+
- **Hot-path memory.** The last successful provider/key is remembered in
|
|
99
|
+
`~/.config/surf/keys.json`. The next call starts there — no cold-start
|
|
100
|
+
cost.
|
|
101
|
+
- **Predictable output.** `--json` returns the same normalized envelope
|
|
102
|
+
no matter which provider answered.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Supported agents
|
|
107
|
+
|
|
108
|
+
> The installer configures every harness it can. The user only has to
|
|
109
|
+
> manually configure GitHub Copilot CLI (per project) because it has no
|
|
110
|
+
> global timeout setting.
|
|
111
|
+
|
|
112
|
+
### Claude Code
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm i -g surf-skill
|
|
116
|
+
# Installer writes ~/.claude/settings.json:
|
|
117
|
+
# { "env": { "BASH_DEFAULT_TIMEOUT_MS": "300000",
|
|
118
|
+
# "BASH_MAX_TIMEOUT_MS": "600000" } }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The skill becomes available at `~/.claude/skills/surf-skill/`. In a Claude
|
|
122
|
+
Code session, just ask: "search the web for X" — the agent will invoke
|
|
123
|
+
`surf-skill` via Bash. For commands that may exceed 5 min, the agent can
|
|
124
|
+
pass `timeout: 600000` on the Bash call (10 min hard cap), or set
|
|
125
|
+
`run_in_background: true` and monitor via `/tasks`.
|
|
126
|
+
|
|
127
|
+
### GitHub Copilot CLI
|
|
128
|
+
|
|
129
|
+
⚠️ **Default bash timeout is 30 s — the most fragile of the three.**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm i -g surf-skill
|
|
133
|
+
# Symlink created at ~/.copilot/skills/ (via ~/.agents/skills/surf-skill).
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Per-project**, run inside the project root:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
surf-skill project-config
|
|
140
|
+
# writes .github/copilot-hooks.json with { "timeoutSec": 300 }
|
|
141
|
+
# detects .github/ automatically; use --harness copilot --yes to force
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Without this, any `surf-skill` command other than `--help`, `--version`,
|
|
145
|
+
`keys list/add`, or `search --max 1` will time out. With it, you can use
|
|
146
|
+
the full command set up to ~5 min per call.
|
|
147
|
+
|
|
148
|
+
For longer operations, use Copilot CLI's async pattern: `/delegate` the
|
|
149
|
+
`surf-skill research-start ...` call, then poll with `surf-skill
|
|
150
|
+
research-poll <id>` from a regular session.
|
|
151
|
+
|
|
152
|
+
If surf-skill detects the agent will likely kill the call before it can
|
|
153
|
+
finish, it now aborts early with `LikelyAgentTimeout` and tells the agent
|
|
154
|
+
to suggest `surf-skill project-config` to the user — instead of dying
|
|
155
|
+
silently to SIGTERM.
|
|
156
|
+
|
|
157
|
+
### Pi Coding Agent
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm i -g surf-skill
|
|
161
|
+
# Installer writes ~/.pi/agent/settings.json:
|
|
162
|
+
# { "env": { "PI_BASH_DEFAULT_TIMEOUT_SECONDS": "300",
|
|
163
|
+
# "PI_BASH_MAX_TIMEOUT_SECONDS": "600" } }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The skill becomes available at `~/.pi/agent/skills/surf-skill/`. Pi reads
|
|
167
|
+
the timeout from env, so the settings.json above is enough. For
|
|
168
|
+
long-running work, Pi supports subagents with `--bg` and the `await` tool.
|
|
169
|
+
|
|
170
|
+
### OpenCode & Codex CLI
|
|
171
|
+
|
|
172
|
+
Also auto-configured by the installer (`~/.agents/skills/surf-skill/` and
|
|
173
|
+
`~/.codex/skills/surf-skill/`). OpenCode gets `mcp_timeout` + `bash.timeout_ms`
|
|
174
|
+
set to 600 000 ms in `~/.config/opencode/opencode.json`.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Timeouts at a glance
|
|
179
|
+
|
|
180
|
+
| Agent | Default bash | Max | After install | Most likely to time out? |
|
|
181
|
+
|---|---|---|---|---|
|
|
182
|
+
| **Claude Code** | 120 s | 600 s (hard) | 300 s default | Long crawls > 5 min |
|
|
183
|
+
| **GitHub Copilot CLI** | **30 s** | NÃO DOCUMENTADO | unchanged (no global config) | **YES — most commands** |
|
|
184
|
+
| **Pi Coding Agent** | 120 s | 600 s | 300 s default | Long crawls > 5 min |
|
|
185
|
+
| **OpenCode** | varies | 600 s | 600 s default | Rarely |
|
|
186
|
+
|
|
187
|
+
If you see timeouts, the order of fixes:
|
|
188
|
+
|
|
189
|
+
1. Use `surf-skill research-start` + `research-poll` instead of sync
|
|
190
|
+
`research`.
|
|
191
|
+
2. Reduce `--limit` / `--max` / `--max-depth`.
|
|
192
|
+
3. Bump the per-harness timeout (see the relevant card above).
|
|
193
|
+
4. Set `SURF_TIMEOUT_MS=300000` (caps the HTTP request itself at 5 min).
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Commands
|
|
198
|
+
|
|
199
|
+
| Command | What it does | Provider(s) |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `setup` | Interactive wizard to add keys (TTY) | n/a |
|
|
202
|
+
| `project-config` | Write per-project bash-timeout config | n/a |
|
|
203
|
+
| `search <q> [q2 ...]` | Web search; multiple positional args = **batch** | tavily, parallel |
|
|
204
|
+
| `extract <url> ...` | Pull markdown from URLs | tavily, parallel |
|
|
205
|
+
| `crawl <url>` | Recursive site crawl | tavily |
|
|
206
|
+
| `map <url>` | Sitemap discovery | tavily |
|
|
207
|
+
| `research <topic>` | Sync deep research (50 s budget) | parallel, tavily |
|
|
208
|
+
| `research-start <topic>` | Start async research | parallel, tavily |
|
|
209
|
+
| `research-poll <id>` | Poll an async research job | (sticky to provider) |
|
|
210
|
+
| `usage --provider <name>` | Provider's usage endpoint | per provider |
|
|
211
|
+
| `cache-clear` | Purge response cache | n/a |
|
|
212
|
+
| `cost [--reset]` | Local credit ledger (per-provider) | n/a |
|
|
213
|
+
| `keys <subcmd>` | `add`, `remove`, `list`, `reset`, `clear` | n/a |
|
|
214
|
+
|
|
215
|
+
Full reference: `skills/surf-skill/SKILL.md`.
|
|
216
|
+
|
|
217
|
+
Global flags every command accepts:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
--provider <tavily|parallel> Force provider (disables fallback)
|
|
221
|
+
--no-fallback Keep default provider, no cross-provider fallback
|
|
222
|
+
--no-cache Skip response cache
|
|
223
|
+
--json Normalized envelope as JSON
|
|
224
|
+
--raw-json Raw provider response (bypasses cache)
|
|
225
|
+
--confirm-expensive Allow operations estimated > 10 credits
|
|
226
|
+
--quiet Silence progress logs (stderr)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Batch your queries
|
|
232
|
+
|
|
233
|
+
When you need to research **multiple angles** of the same topic, batch them
|
|
234
|
+
in a single call. Each positional arg is an independent query:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
surf-skill search "compare X vs Y" "alternatives to X" "X security issues"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- Runs sequentially (avoids rate-limit thrashing on a single key).
|
|
241
|
+
- Partial failures are reported inline — the command exits `0` if at least
|
|
242
|
+
one query succeeded.
|
|
243
|
+
- Total credits and timing surface in the markdown header and `--json` envelope.
|
|
244
|
+
- Progress logs (see below) show `[i/N]` per query.
|
|
245
|
+
|
|
246
|
+
This is the recommended way for an agent to gather multi-source context in
|
|
247
|
+
one shot, instead of looping with N separate bash calls.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Progress logs (stderr)
|
|
252
|
+
|
|
253
|
+
Every operation emits one self-contained line per event to **stderr**, so
|
|
254
|
+
both humans and the calling LLM can see what's happening without parsing
|
|
255
|
+
the main result on stdout.
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
[surf 17:58:12] ▸ search → tavily (key #0)
|
|
259
|
+
[surf 17:58:14] ✓ search tavily 1234ms (2 credits)
|
|
260
|
+
[surf 17:58:14] ↻ tavily 429 — backoff 1500ms (attempt 1/3)
|
|
261
|
+
[surf 17:58:18] ⚠ tavily key #0 burned (401)
|
|
262
|
+
[surf 17:58:18] ▸ search → parallel (key #0)
|
|
263
|
+
[surf 17:58:20] ✓ search parallel 2102ms (2 credits)
|
|
264
|
+
[surf 17:58:20] ⏱ batch done: 3/3 ok, 0 failed (8200ms, 6 credits)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The format is stable for grep/parse. Use `--quiet` or `SURF_QUIET=1` to
|
|
268
|
+
silence (CI, piping, tests). Stdout stays clean either way.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Multi-key & fallback
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
state.json (per provider):
|
|
276
|
+
keys: [key0, key1, key2]
|
|
277
|
+
current: 1 ← starts here next call
|
|
278
|
+
burned: [{ index: 0, reason: "401", at: "2026-05-15..." }]
|
|
279
|
+
← auto-reset on the 1st of next month
|
|
280
|
+
|
|
281
|
+
call flow:
|
|
282
|
+
┌─ load state, auto-reset burned ──┐
|
|
283
|
+
│ │
|
|
284
|
+
└─▶ chain = [last_ok_provider, ─┤
|
|
285
|
+
...rest_of_capability_chain]
|
|
286
|
+
│
|
|
287
|
+
for provider in chain: │
|
|
288
|
+
for key in usable_keys(provider):│
|
|
289
|
+
try call │
|
|
290
|
+
200 ─▶ save last_ok, return │
|
|
291
|
+
401/403/402 ─▶ burn key, next│
|
|
292
|
+
5xx x3 ─▶ burn key, next │
|
|
293
|
+
429 ─▶ backoff, retry │
|
|
294
|
+
4xx ─▶ raise (no fallback) │
|
|
295
|
+
(no usable keys) ─▶ next provider│
|
|
296
|
+
raise AllProvidersExhausted ───────┘
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Force a specific provider for debugging:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
surf-skill search "x" --provider parallel
|
|
303
|
+
# 'parallel' fails ⇒ command fails (no fallback when --provider is set)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Onboarding (3 ways)
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# 1. Wizard (recommended in a TTY)
|
|
312
|
+
surf-skill setup
|
|
313
|
+
|
|
314
|
+
# 2. Direct
|
|
315
|
+
surf-skill keys add --provider tavily tvly-...
|
|
316
|
+
surf-skill keys add --provider parallel <key>
|
|
317
|
+
|
|
318
|
+
# 3. Auto-launch in TTY: just run any command without keys
|
|
319
|
+
surf-skill search "test"
|
|
320
|
+
# → "No keys configured. Launching setup wizard…" → prompts → resumes search
|
|
321
|
+
|
|
322
|
+
# 4. Library mode: env vars / .env / explicit opts (no setup needed)
|
|
323
|
+
TAVILY_API_KEY=tvly-... node -e "import('surf-skill').then(m => m.search('x'))"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Inspect what was stored (keys are masked):
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
surf-skill keys list
|
|
330
|
+
# **Surf keys** (config: ~/.config/surf/keys.json)
|
|
331
|
+
# last_ok_provider: `tavily`
|
|
332
|
+
# ## tavily (2 keys)
|
|
333
|
+
# - [0] tvly-…ab12 *(current)*
|
|
334
|
+
# - [1] tvly-…cd34
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Troubleshooting
|
|
340
|
+
|
|
341
|
+
**`❌ Error [NoProviderAvailable]: operation 'X' requires one of [...]`**
|
|
342
|
+
→ The op needs a key for a provider you haven't configured. In a TTY the
|
|
343
|
+
error already suggests `surf-skill setup`. Outside TTY, run
|
|
344
|
+
`surf-skill keys add --provider <name> <key>`.
|
|
345
|
+
|
|
346
|
+
**`❌ Error [AllProvidersExhausted]: ...`**
|
|
347
|
+
→ Every key on every eligible provider failed. Check `surf-skill keys list`
|
|
348
|
+
— if everything is `burned`, you've either rotated keys mid-billing-cycle
|
|
349
|
+
or the providers are down. Run `surf-skill keys reset` to retry.
|
|
350
|
+
|
|
351
|
+
**Command timed out in GH Copilot CLI**
|
|
352
|
+
→ Run `surf-skill project-config` inside the project root. See the
|
|
353
|
+
Copilot CLI card above.
|
|
354
|
+
|
|
355
|
+
**`❌ Error [LikelyAgentTimeout]: ...`**
|
|
356
|
+
→ surf-skill detected the harness will kill the call before it finishes
|
|
357
|
+
(typical on Copilot CLI without per-project config). Run `surf-skill
|
|
358
|
+
project-config` in the project, then retry. Don't retry the same call
|
|
359
|
+
without fixing the timeout first.
|
|
360
|
+
|
|
361
|
+
**`❌ Error [KilledBySignal]: surf-skill received SIGTERM/SIGINT`**
|
|
362
|
+
→ The harness killed us mid-flight. Same fix as `LikelyAgentTimeout`. The
|
|
363
|
+
SIGTERM handler exists as a fallback — the self-budget check should fire
|
|
364
|
+
first when env vars are set.
|
|
365
|
+
|
|
366
|
+
**`❌ Error: EXPENSIVE_BLOCKED ...`**
|
|
367
|
+
→ Pass `--confirm-expensive` after confirming the cost with the user. Or
|
|
368
|
+
export `SURF_ALLOW_EXPENSIVE=1` for the session.
|
|
369
|
+
|
|
370
|
+
**`Refusing sync research with model=pro`**
|
|
371
|
+
→ Use `surf-skill research-start --model pro ...` then `surf-skill
|
|
372
|
+
research-poll <id>`. Sync research is capped at 50 s on purpose.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Repository layout
|
|
377
|
+
|
|
378
|
+
```text
|
|
379
|
+
.
|
|
380
|
+
├── package.json
|
|
381
|
+
├── README.md ← you're here
|
|
382
|
+
├── CHANGELOG.md
|
|
383
|
+
├── LICENSE
|
|
384
|
+
└── skills/
|
|
385
|
+
└── surf-skill/
|
|
386
|
+
├── SKILL.md
|
|
387
|
+
├── install.sh
|
|
388
|
+
├── bin/
|
|
389
|
+
│ └── surf-skill.mjs
|
|
390
|
+
├── lib/
|
|
391
|
+
│ ├── state.mjs ← keys.json I/O, monthly auto-reset
|
|
392
|
+
│ ├── cache.mjs ← TTL response cache
|
|
393
|
+
│ ├── audit.mjs ← audit + usage JSONL
|
|
394
|
+
│ ├── flags.mjs ← parsing + helpers
|
|
395
|
+
│ ├── cost.mjs ← estimateCredits + guard
|
|
396
|
+
│ ├── format.mjs ← markdown formatters
|
|
397
|
+
│ ├── dispatch.mjs ← provider/key fallback + self-budget
|
|
398
|
+
│ ├── keys-cmd.mjs ← surf-skill keys add/remove/...
|
|
399
|
+
│ ├── setup.mjs ← interactive onboarding
|
|
400
|
+
│ ├── project-config.mjs ← surf-skill project-config
|
|
401
|
+
│ ├── progress.mjs ← stderr progress events
|
|
402
|
+
│ └── providers/
|
|
403
|
+
│ ├── index.mjs
|
|
404
|
+
│ ├── tavily.mjs
|
|
405
|
+
│ └── parallel.mjs
|
|
406
|
+
└── references/
|
|
407
|
+
├── tavily-api.md
|
|
408
|
+
├── parallel-api.md
|
|
409
|
+
└── COSTS.md
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Security
|
|
415
|
+
|
|
416
|
+
- This repository contains **no real API keys**. The installer only uses
|
|
417
|
+
placeholders.
|
|
418
|
+
- Keys are stored exclusively in `~/.config/surf/keys.json` (chmod 600).
|
|
419
|
+
`surf-skill` does not read keys from env at runtime.
|
|
420
|
+
- The audit log records only `provider` name and key **index**, never the
|
|
421
|
+
key itself. `surf-skill keys list` masks every key (`tvly-…ab12`).
|
|
422
|
+
- The skill never executes content returned from the web — it just prints it.
|
|
423
|
+
- Review any skill before installing. Skills can instruct agents to run
|
|
424
|
+
commands.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
MIT.
|