Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Follow these rules when editing code in this project.

`sibyl` is a CLI web search/crawl tool for AI Agents (`bin: sibyl` → `dist/cli.js`) with a filesystem-based plugin system. Key modules:

- `src/cli.ts` — entry point. Ensures dirs + config exist, loads plugins, builds a `PluginContext` (`buildPluginContext`), and dispatches commands (`search`, `fetch`, `help`/`--help`/`-h`, `version`/`--version`). Only `search` and `fetch` are wired up via the async `handleSearch`/`handleFetch` helpers (awaited by `main`), each passing the context as the last arg to the selected plugin's `fn`. The `fetch` command prints the fetch plugin's output directly — the CLI doesn't dispatch a separate `parse` step, but a fetch plugin may itself run the configured parse plugin via `context.configuredPlugins.parse` (`builtin-brightdata-fetch`, `builtin-crawl4ai-fetch`, and `builtin-alterlab-fetch` do; `builtin-firecrawl-fetch` does only in its raw-HTML mode; `builtin-exa-fetch` returns content as-is). `ask` is part of the contract but not dispatched by any command. `main` is exported and only auto-runs when the file is the actual CLI entry (`import.meta.url` vs `process.argv[1]` guard), so tests can import it without side effects.
- `src/cli.ts` — entry point. Ensures dirs + config exist, loads plugins, builds a `PluginContext` (`buildPluginContext`), and dispatches commands (`search`, `fetch`, `ask`, `help`/`--help`/`-h`, `version`/`--version`). `search`, `fetch`, and `ask` are wired up via the async `handleSearch`/`handleFetch`/`handleAsk` helpers (awaited by `main`), each passing the context as the last arg to the selected plugin's `fn`. The `fetch` command prints the fetch plugin's output directly — the CLI doesn't dispatch a separate `parse` step, but a fetch plugin may itself run the configured parse plugin via `context.configuredPlugins.parse` (`builtin-brightdata-fetch`, `builtin-crawl4ai-fetch`, and `builtin-alterlab-fetch` do; `builtin-firecrawl-fetch` does only in its raw-HTML mode; `builtin-exa-fetch` returns content as-is). The `ask` command (`sibyl ask <url> <question>`) passes the URL as the ask plugin's first arg; analogously, an ask plugin may itself fetch that URL via `context.configuredPlugins.fetch` before answering (`builtin-ai-ask` does). `main` is exported and only auto-runs when the file is the actual CLI entry (`import.meta.url` vs `process.argv[1]` guard), so tests can import it without side effects.
- `src/setup.ts` — ensures `~/.sibyl` and `~/.sibyl/plugins` exist, and loads/creates/validates `~/.sibyl/config.json` (all on every invocation).
- `src/plugin-loader.ts` — assembles the active plugin set: builtin plugins + external (on-disk) plugins; validates the external ones.
- `src/plugins/config.ts` — `getBuiltinPlugins()`, the in-repo builtin plugin registry.
Expand All @@ -48,7 +48,7 @@ Plugins live in `~/.sibyl/plugins/<name>/main.js` (note: `.js`, loaded at runtim
3. `fn` — the function implementing the plugin's logic. Every `fn` receives a `PluginContext` as its **last** argument; its signature otherwise depends on `type` (`src/@types/plugin.ts`):
- `search`: `(query, context) => Promise<string>`
- `fetch`: `(url, context) => Promise<string>`
- `ask`: `(parsedContent, query, context) => Promise<string>`
- `ask`: `(src, query, context) => Promise<string>` (the dispatched `ask` command passes a URL as `src`)
- `parse`: `(html, context) => Promise<string>`

`PluginContext` (`src/@types/plugin.ts`) lets a plugin reach the rest of the plugin system: `{ configuredPlugins: Partial<Record<PluginType, PluginTypeDeclaration>>, allPlugins: PluginTypeDeclaration[], getPlugin(name): PluginTypeDeclaration | null }`. `configuredPlugins` is keyed by type (the per-type selection from config), `allPlugins` is everything loaded, and `getPlugin` looks up by name. It's built once in `cli.ts` and threaded to every `fn`; plugins consume it only if needed (a 1-arg `fn` still satisfies the contract via structural typing). This is how a fetch plugin runs the configured parser: `context.configuredPlugins.parse?.fn(html, context)`.
Expand All @@ -65,15 +65,16 @@ When changing the plugin shape, update all three together: `src/@types/plugin.ts

`loadPlugins()` (`plugin-loader.ts`) returns `[...getBuiltinPlugins(), ...externalPlugins]`.

- Builtins are **compiled into the binary, not loaded from disk**. `src/plugins/config.ts` statically imports each builtin's `SilbylPlugin` (e.g. from `src/plugins/builtin-exa-search/main.ts`) and returns them as `PluginTypeDeclaration` objects — they bypass `validatePlugin` and the `main.js` discovery path. Each builtin `main.ts` types its `SilbylPlugin` with the matching interface (`SearchPlugin` / `FetchPlugin` / `ParsePlugin`) so `type` stays a literal.
- Builtins are **compiled into the binary, not loaded from disk**. `src/plugins/config.ts` statically imports each builtin's `SilbylPlugin` (e.g. from `src/plugins/builtin-exa-search/main.ts`) and returns them as `PluginTypeDeclaration` objects — they bypass `validatePlugin` and the `main.js` discovery path. Each builtin `main.ts` types its `SilbylPlugin` with the matching interface (`SearchPlugin` / `FetchPlugin` / `AskPlugin` / `ParsePlugin`) so `type` stays a literal.
- Builtin names are prefixed `builtin-` by convention. External plugin folders starting with `builtin-` are rejected during discovery (reserved namespace), so user plugins cannot shadow a builtin.
- To add a builtin: create `src/plugins/builtin-<x>/main.ts` exporting a typed `SilbylPlugin` (with `fn`), then register it in `getBuiltinPlugins()`.
- `builtin-ai-ask` (the `ask` builtin) reads a URL via the configured fetch plugin, then answers a question over that content using the **Vercel AI SDK**, with the provider selectable via `SIBYL_AI_PROVIDER` (`openai` / `anthropic` / `ollama` / `openrouter`), `SIBYL_MODEL_NAME`, and a per-provider key (`OPENAI_API_KEY` etc.; Ollama uses `OLLAMA_BASE_URL`, no key). It loads `ai` and each provider package (`@ai-sdk/*`, `ollama-ai-provider-v2`, `@openrouter/ai-sdk-provider`) via **dynamic `import()`** inside the `fn` — never at module top level — because `getBuiltinPlugins()` imports every builtin module on every CLI run, so top-level SDK imports would slow `search`/`fetch` too.

### Config (`~/.sibyl/config.json`)

Shape: `SibylConfig` (`src/@types/sibyl-config.ts`) — `{ plugins: Partial<Record<PluginType, string>>, variables: { name, value }[] }`. `plugins` maps `type` → plugin name (e.g. `{ "search": "builtin-exa-search" }`); keying by type structurally enforces at most one plugin per type. `variables` is a list of `{ name, value }` pairs injected into `process.env`.

- `loadOrCreateConfigFile()` (`setup.ts`) writes a default config (`writeDefaultSibylConfig`) when the file is missing or empty, then parses, validates, and injects variables. The default selects `builtin-searxng-search` / `builtin-crawl4ai-fetch` / `builtin-parse-htmlToMd` with no `variables` (fully local, no-API-key backends).
- `loadOrCreateConfigFile()` (`setup.ts`) writes a default config (`writeDefaultSibylConfig`) when the file is missing or empty, then parses, validates, and injects variables. The default selects `builtin-searxng-search` / `builtin-crawl4ai-fetch` / `builtin-parse-htmlToMd` / `builtin-ai-ask` plus one variable (`SIBYL_SHOW_SEARCH_DESCRIPTION=true`); the search/fetch/parse backends are fully local and need no API key, while the `ask` command additionally needs `SIBYL_AI_PROVIDER` / `SIBYL_MODEL_NAME` (and a provider API key unless using Ollama) to be set.
- `injectConfigVariables()` (`setup.ts`) sets `process.env[name] = value` for each config variable. **Config wins over the environment** — a variable named in config overrides any existing env var; names absent from config fall back to their existing env value. (Plugins like `builtin-exa-search` read `process.env.EXA_API_KEY` at call time, so they pick up either source.)
- `validateConfig()` checks each entry's name is a non-empty string; on failure it `console.error`s and `process.exit(1)` (hard exit, not a skip-with-warning like plugin loading).
- Plugin selection: `loadPlugins()` loads _all_ available plugins (builtins + disk), then `cli.ts` picks the one to run **by name from config** — e.g. the `search` command looks up `config.plugins.search` and finds the loaded plugin whose `type === "search"` and `name` matches. Missing config entry or no matching loaded plugin → `console.error` + non-zero exit.
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
[![sibyl License Page](https://img.shields.io/badge/license-Apache_2.0-brightgreen)](https://raw.githubusercontent.com/postapsis/sibyl/refs/heads/main/LICENSE)
[![sibyl CI Status](https://github.com/postapsis/sibyl/actions/workflows/ci.yaml/badge.svg)](https://github.com/postapsis/sibyl/actions/workflows/ci.yaml)
[![codecov](https://codecov.io/gh/postapsis/sibyl/branch/main/graph/badge.svg?token=NOTP4DPWO4)](https://codecov.io/gh/postapsis/sibyl)
<br/>

---

**_Sibyl_** gives your AI Agent the web, without the bloat — extensible and lightweight by design 🕷️
Local-first web search and exploration for your AI agents, without the bloat.\
Extensible and lightweight by design 🕷️

---

Expand Down Expand Up @@ -76,13 +76,13 @@ Get a working setup in a few steps:

## Commands

| Command | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `search` | Searches the web <br/>`sibyl search "react vite boostrap"` |
| `fetch` | Gets the content of a site in token-efficient markdown <br/>`sibyl fetch https://vite.dev/guide` |
| `ask` | Asks a query using LLM from a site's content <br/>`sibyl ask https://vite.dev/guide "how to start a react project with vite"` |
| `--help`, `-h` | Show help. |
| `--version` | Show version. |
| Command | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| search <query> | Searches the web <br/>`sibyl search "react vite"` |
| fetch <url> | Prints the content of a site in token-efficient markdown <br/>`sibyl fetch https://vite.dev/guide` |
| ask <url> <question> | Asks a query using LLM from a site's content <br/>`sibyl ask https://vite.dev/guide "how to start a react project with vite"` |
| `--help`, `-h` | Shows help. |
| `--version` | Shows version. |

## Configuration

Expand Down
2 changes: 1 addition & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ coverage:
threshold: 2%
patch:
default:
target: 90%
target: 85%

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid weakening the patch coverage gate without documented rationale.

Reducing the patch target from 90% to 85% lowers quality enforcement on changed code. Please keep 90% or add a clear justification in PR docs/description and a follow-up plan to restore it.

Suggested change
-        target: 85%
+        target: 90%
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
target: 85%
target: 90%
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codecov.yml` at line 9, The patch coverage target value has been reduced from
90% to 85% in the codecov configuration, which weakens quality enforcement on
changed code. Either revert the target back to 90% to maintain the original
quality gate, or if the reduction is intentional, add clear documentation and
rationale in the PR description explaining why the target was lowered and
include a follow-up plan to restore it to 90%.

threshold: 2%

comment:
Expand Down
Loading
Loading