From 8fb2fb8de62422d61d339b622f19d96b2aec1f0f Mon Sep 17 00:00:00 2001 From: Jamius Siam Date: Tue, 14 Jul 2026 21:09:20 +0600 Subject: [PATCH 1/3] Removed "Status" section from README --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index afb49f4..0301c02 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,6 @@ Extensible and lightweight by design 🕷️ --- -## Status - -⚠️ Currently in development. - ## Quickstart Sibyl uses **SearXNG** for web search and **Crawl4AI** for webpage fetching by default. Both run From bfc76d7d3d441ac66aeebdc0c0d0c03c4f2ca6bc Mon Sep 17 00:00:00 2001 From: Jamius Siam Date: Tue, 14 Jul 2026 21:15:53 +0600 Subject: [PATCH 2/3] Bumped version to 0.1.1 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c742159..a9523c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postapsis/sibyl", - "version": "0.1.0", + "version": "0.1.1", "description": "Give your AI Agent the web, without the bloat — extensible and lightweight by design 🕷️", "type": "module", "packageManager": "pnpm@11.5.1", From b62560253cca23b8d4f726f633fb3842900f67e4 Mon Sep 17 00:00:00 2001 From: Jamius Siam Date: Tue, 14 Jul 2026 21:18:54 +0600 Subject: [PATCH 3/3] Added per-target subagent model handling in setup command --- CLAUDE.md | 4 +-- src/instructions.ts | 56 +++++++++++++++++++++------------------ src/setup-command.test.ts | 36 +++++++++++++++++++++++-- src/setup-command.ts | 31 +++++++++++++++------- 4 files changed, 87 insertions(+), 40 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ae4cc81..0784ad0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,7 @@ Follow these rules when editing code in this project. - `src/cli.ts` — entry point. Ensures dirs + config exist, loads plugins, builds a `PluginContext` (`buildPluginContext`), and dispatches commands (`search`, `fetch`, `ask`, `setup`, `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 `) 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). The `setup` command is handled synchronously by `runSetup(rest)` (`src/setup-command.ts`) and uses neither plugins nor the `PluginContext` (see The `setup` 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/setup.ts` — ensures `~/.sibyl` and `~/.sibyl/plugins` exist, and loads/creates/validates `~/.sibyl/config.json` (all on every invocation). - `src/setup-command.ts` — implements the `setup` command (`runSetup`): installs the instructions doc into agent tools' global instruction files (see The `setup` command). Distinct from `setup.ts`, which is config bootstrap. -- `src/instructions.ts` — the bundled `SIBYL.md` content (`SIBYL_INSTRUCTIONS`) plus the import line, marker, and notice constants used by `setup-command.ts`. Inlined as a string constant (not read from disk) so it ships in `dist`. +- `src/instructions.ts` — the bundled `SIBYL.md` content (`buildSibylInstructions(subagentModel)`, which interpolates a per-target cheap model into the "run subagents with …" line) plus the import line, marker, and notice constants used by `setup-command.ts`. Inlined as a string builder (not read from disk) so it ships in `dist`. - `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. - `src/utils.ts` — pure helpers: `isValidHttpUrl`, `stripSearchResultDatePrefix` (strips localized SERP date prefixes), `collapseBlankLines`, and the search-setting readers `getSearchResultsLimit` / `shouldShowSearchDescription` (see Conventions). @@ -91,7 +91,7 @@ Shape: `SibylConfig` (`src/@types/sibyl-config.ts`) — `{ plugins: Partial` (repeatable; `--other=` also accepted) → embeds the content into an arbitrary file. -Two mechanisms: Claude and opencode **reference** a `SIBYL.md` file each target owns; Codex, Antigravity, and `--other` **embed** the content between `` / `` markers, followed by a do-not-edit notice line. Everything is idempotent and refreshes in place on re-run — doc files are overwritten, the marker block (and its trailing notice) is regex-replaced, and the `@SIBYL.md` line / opencode entry are added at most once. The content is inlined in `src/instructions.ts` (a bundled string constant) because the `tsc`-only build has no asset-copy step, so a raw `.md` would never reach `dist`. +Two mechanisms: Claude and opencode **reference** a `SIBYL.md` file each target owns; Codex, Antigravity, and `--other` **embed** the content between `` / `` markers, followed by a do-not-edit notice line. Everything is idempotent and refreshes in place on re-run — doc files are overwritten, the marker block (and its trailing notice) is regex-replaced, and the `@SIBYL.md` line / opencode entry are added at most once. The content is inlined in `src/instructions.ts` (a bundled string builder) because the `tsc`-only build has no asset-copy step, so a raw `.md` would never reach `dist`. Each target renders the doc with a cheap model for its own tool (`SUBAGENT_MODEL` in `setup-command.ts`: `claude` → Claude Haiku, `codex` → GPT-5 Mini, `antigravity` → Gemini Flash; `opencode` and `--other` use the generic "a cheap model"). ## Conventions diff --git a/src/instructions.ts b/src/instructions.ts index e526a8a..1497aef 100644 --- a/src/instructions.ts +++ b/src/instructions.ts @@ -5,32 +5,36 @@ // The bundled `SIBYL.md` instructions doc installed by `sibyl setup`. Inlined here (rather // than read from disk) because the build is `tsc`-only with no asset-copy step, so a raw -// `.md` file would never reach `dist`. Assembled as a line array joined with newlines so the -// fenced code block below doesn't need its backticks escaped inside a template literal. -export const SIBYL_INSTRUCTIONS = [ - "# Search the web using `sibyl`", - "Use `sibyl` CLI for searching google/fetching webpages.", - "", - "## Usage", - "```", - "Commands:", - " search Search the web", - " fetch Fetch the content of a URL", - " help Show this help", - " version Show version", - "", - "Examples:", - ' sibyl search "react vite bootstrap"', - " sibyl fetch https://vite.dev/guide", - "```", - "", - "Always search the web first when installing a new library/framework to get the latest docs. Also search when you want to find out information/API DOC etc.", - "Search more often than not. Conduct multiple search and fetches using subagents (with Haiku) and then proceed.", - "Use your judgment when interpreting web search results. Do not rely only on the first result you find.", - "If the information is incomplete, unclear, or insufficient to make a confident decision, perform a few more searches or fetch additional sources before making changes.", - "", - "Collect all the necessary information thoroughly before proceeding with creating/editing code.", -].join("\n"); +// `.md` file would never reach `dist`. Built per target so `subagentModel` names a cheap model +// the installing tool can actually run (Claude → Claude Haiku, Codex → GPT-5 Mini, etc.), rather than +// hardcoding one tool's model. Assembled as a line array joined with newlines so the fenced +// code block below doesn't need its backticks escaped inside a template literal. +export function buildSibylInstructions(subagentModel: string): string { + return [ + "# Search the web using `sibyl`", + "Use `sibyl` CLI for searching google/fetching webpages.", + "", + "## Usage", + "```", + "Commands:", + " search Search the web", + " fetch Fetch the content of a URL", + " help Show this help", + " version Show version", + "", + "Examples:", + ' sibyl search "react vite bootstrap"', + " sibyl fetch https://vite.dev/guide", + "```", + "", + "Always search the web first when installing a new library/framework to get the latest docs. Also search when you want to find out information/API DOC etc.", + `Search more often than not. Conduct multiple search and fetches using subagents (with ${subagentModel}) and then proceed.`, + "Use your judgment when interpreting web search results. Do not rely only on the first result you find.", + "If the information is incomplete, unclear, or insufficient to make a confident decision, perform a few more searches or fetch additional sources before making changes.", + "", + "Collect all the necessary information thoroughly before proceeding with creating/editing code.", + ].join("\n"); +} // Filename for the standalone doc that reference-based targets (Claude, opencode) point at. export const SIBYL_DOC_FILENAME = "SIBYL.md"; diff --git a/src/setup-command.test.ts b/src/setup-command.test.ts index 03344a6..749b68b 100644 --- a/src/setup-command.test.ts +++ b/src/setup-command.test.ts @@ -18,7 +18,7 @@ import { SIBYL_BLOCK_END, SIBYL_BLOCK_NOTICE, SIBYL_BLOCK_START, - SIBYL_INSTRUCTIONS, + buildSibylInstructions, } from "./instructions.ts"; let home: string; @@ -66,7 +66,7 @@ describe("claude target", () => { it("writes SIBYL.md and the @SIBYL.md import, leaving other tools untouched", () => { runSetup(["--claude"]); - expect(read(".claude", "SIBYL.md").trimEnd()).toBe(SIBYL_INSTRUCTIONS); + expect(read(".claude", "SIBYL.md").trimEnd()).toBe(buildSibylInstructions("Claude Haiku")); expect(read(".claude", "CLAUDE.md")).toContain("@SIBYL.md"); expect(fs.existsSync(path.join(home, ".config", "opencode"))).toBe(false); @@ -165,3 +165,35 @@ describe("embed targets", () => { expect(content.split(SIBYL_BLOCK_NOTICE)).toHaveLength(2); }); }); + +describe("subagent model per target", () => { + it("names Claude's cheap model (Claude Haiku) in the claude doc", () => { + runSetup(["--claude"]); + expect(read(".claude", "SIBYL.md")).toContain("subagents (with Claude Haiku)"); + }); + + it("uses the generic phrasing for opencode (model-agnostic)", () => { + runSetup(["--opencode"]); + expect(read(".config", "opencode", "SIBYL.md")).toContain("subagents (with a cheap model)"); + }); + + it("names GPT-5 Mini for codex and drops the Claude-specific Haiku", () => { + runSetup(["--codex"]); + + const content = read(".codex", "AGENTS.md"); + expect(content).toContain("subagents (with GPT-5 Mini)"); + expect(content).not.toContain("Haiku"); + }); + + it("names Gemini Flash for antigravity", () => { + runSetup(["--antigravity"]); + expect(read(".gemini", "GEMINI.md")).toContain("subagents (with Gemini Flash)"); + }); + + it("uses the generic phrasing for --other targets", () => { + const target = path.join(home, "notes.md"); + + runSetup(["--other", target]); + expect(fs.readFileSync(target, "utf8")).toContain("subagents (with a cheap model)"); + }); +}); diff --git a/src/setup-command.ts b/src/setup-command.ts index b03efbf..1e0eee0 100644 --- a/src/setup-command.ts +++ b/src/setup-command.ts @@ -12,11 +12,22 @@ import { SIBYL_BLOCK_START, SIBYL_DOC_FILENAME, SIBYL_IMPORT_LINE, - SIBYL_INSTRUCTIONS, + buildSibylInstructions, } from "./instructions.ts"; type SetupTarget = "claude" | "opencode" | "codex" | "antigravity"; +// Cheap/fast model each target's agent should spawn subagents with, interpolated into the +// instructions doc so every tool references a model it can actually run. opencode is +// provider-agnostic (no canonical cheap model), so it falls back to the generic phrasing. +const GENERIC_SUBAGENT_MODEL = "a cheap model"; +const SUBAGENT_MODEL: Record = { + claude: "Claude Haiku", + opencode: GENERIC_SUBAGENT_MODEL, + codex: "GPT-5 Mini", + antigravity: "Gemini Flash", +}; + // Path opencode stores in its `instructions[]` array. Kept in tilde form (opencode expands // `~`) so it stays portable, while the doc itself is written to the expanded path. const OPENCODE_INSTRUCTION_REF = `~/.config/opencode/${SIBYL_DOC_FILENAME}`; @@ -37,25 +48,25 @@ export function runSetup(args: string[]): void { const home = os.homedir(); if (targets.has("claude")) { - writeDoc(path.join(home, ".claude", SIBYL_DOC_FILENAME)); + writeDoc(path.join(home, ".claude", SIBYL_DOC_FILENAME), SUBAGENT_MODEL.claude); addImportLine(path.join(home, ".claude", "CLAUDE.md")); } if (targets.has("opencode")) { - writeDoc(path.join(home, ".config", "opencode", SIBYL_DOC_FILENAME)); + writeDoc(path.join(home, ".config", "opencode", SIBYL_DOC_FILENAME), SUBAGENT_MODEL.opencode); addOpencodeInstruction(path.join(home, ".config", "opencode", "opencode.json")); } if (targets.has("codex")) { - embedBlock(path.join(home, ".codex", "AGENTS.md")); + embedBlock(path.join(home, ".codex", "AGENTS.md"), SUBAGENT_MODEL.codex); } if (targets.has("antigravity")) { - embedBlock(path.join(home, ".gemini", "GEMINI.md")); + embedBlock(path.join(home, ".gemini", "GEMINI.md"), SUBAGENT_MODEL.antigravity); } for (const other of otherPaths) { - embedBlock(path.resolve(other)); + embedBlock(path.resolve(other), GENERIC_SUBAGENT_MODEL); } } catch (error) { console.error(`Error running setup: ${error}`); @@ -113,9 +124,9 @@ function usageError(message: string): never { } // Writes the standalone SIBYL.md doc, overwriting any existing copy (refresh in place). -function writeDoc(file: string): void { +function writeDoc(file: string, subagentModel: string): void { ensureDir(file); - fs.writeFileSync(file, `${SIBYL_INSTRUCTIONS}\n`); + fs.writeFileSync(file, `${buildSibylInstructions(subagentModel)}\n`); console.log(`Wrote instructions doc: ${file}`); } @@ -159,11 +170,11 @@ function addOpencodeInstruction(jsonPath: string): void { // Embeds the instructions inside sentinel markers followed by a do-not-edit notice. On // re-run the existing block (and its trailing notice) is replaced in place, not duplicated. -function embedBlock(file: string): void { +function embedBlock(file: string, subagentModel: string): void { ensureDir(file); const current = readIfExists(file); - const block = `${SIBYL_BLOCK_START}\n${SIBYL_INSTRUCTIONS}\n${SIBYL_BLOCK_END}\n${SIBYL_BLOCK_NOTICE}`; + const block = `${SIBYL_BLOCK_START}\n${buildSibylInstructions(subagentModel)}\n${SIBYL_BLOCK_END}\n${SIBYL_BLOCK_NOTICE}`; const blockRe = new RegExp( `${escapeRegExp(SIBYL_BLOCK_START)}[\\s\\S]*?${escapeRegExp(SIBYL_BLOCK_END)}` + `(?:\\n${escapeRegExp(SIBYL_BLOCK_NOTICE)})?`,