Add multi-provider search support (Gemini, OpenAI, Claude)#5
Merged
Conversation
… update - Fix allFailed logic in job-runner: was based on providerErrors.size which accumulated entries from any keyword failure even when the provider later succeeded. Now uses totalSnapshotsInserted === 0 as the guard so a run is only marked failed when no data was written at all. - Add canonry settings provider <name> --api-key <key> CLI command to satisfy CLAUDE.md surface parity: the PUT /settings/providers/:name API endpoint and its ProviderConfigForm UI counterpart shipped without a corresponding CLI command. Adds setProvider() in commands/settings.ts, updateProvider() on ApiClient, and the settings provider <name> subcommand in cli.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements full multi-provider visibility runs in parallel across Gemini, OpenAI, and Claude/Anthropic. Each provider has its own adapter, rate limiting, and per-minute quota window. Run status reflects partial failures across providers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. --provider flag was a no-op: POST /runs now accepts a providers[] override that is threaded through onRunCreated -> executeRun -> registry.getForProject, so single-provider runs work correctly. 2. Daily quota over-counted by multiplying keywords * providers, then comparing against a single-provider limit. Changed to compare keywords-per-provider (projectKeywords.length) against the minimum per-provider daily quota. 3. Timeline produced fake emerging/lost transitions within one run when multiple providers emitted snapshots for the same keyword. Added a deduplication step (prefer 'cited', one entry per runId+keywordId) before computing transitions. 4. Updating a provider key without --model dropped the live model override until restart: server.ts re-registered the adapter with model: model || undefined instead of model: model || existing?.model. 5. apps/api never passed providerSummary to apiRoutes so GET /api/v1/settings always returned an empty providers list even when env-backed providers were configured. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. Usage counter incremented by totalSnapshotsInserted (keywords x providers) but quota check used queriesPerProvider (keywords only), causing valid later runs to be blocked too early. Changed to increment by queriesPerProvider. 2. POST /runs accepted invalid provider names and silently queued a run that would fail inside the job runner. Added a 400 validation guard at the API boundary before the run is inserted. 3. buildEvidenceFromTimeline used the aggregated timeline transition for each per-provider evidence row, producing contradictory labels (e.g. 'emerging' for a provider that returned 'not-cited'). For multi-provider runs the label now derives from the provider's own citation state. 4. snapshots/diff built the per-run keyword maps with a plain Map constructor that let the last provider snapshot win, collapsing multi-provider results arbitrarily. Now uses prefer-cited deduplication matching the timeline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Polish homepage layout: projects first, tighter typography - Move project list to top of portfolio page as primary content - Relocate "What changed" and "Activity" into a compact secondary grid below projects (only shows when there are attention items) - Reduce heading sizes and font weights for a more refined feel - Tighten padding, spacing, and font sizes across cards and rows - Use negative letter-spacing on headings for premium typographic tone - Soften borders and hover states for subtler interactivity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add scrollable containers for projects, attention, and activity lists Cap project list at 32rem, attention and activity lists at 18rem. Overflow scrolls with a bottom fade mask and thin scrollbar styling so the page stays compact even with many items. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Ensure database directory exists before opening SQLite file createClient now calls mkdirSync with recursive: true on the parent directory so `canonry serve` works without a prior `canonry init`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Switch to keyword-level visibility scoring and add info tooltips Visibility score now measures "% of keywords visible in at least one AI engine" instead of raw snapshot counts. This makes the metric resilient to partial runs and provider count changes — adding a provider can only help your score, never hurt it. - computeKeywordVisibility: groups snapshots by keyword, marks visible if any provider cites it - Project page gauges and portfolio cards updated to new metric - InfoTooltip component: CSS-only hover tooltip on gauge labels - Tooltips added to all four gauges (visibility, readiness, pressure, run status) and the provider breakdown section header - Provider breakdown still uses per-snapshot scoring as a diagnostic Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1. Quota check used project-scoped query counts (aggregated across all
providers) against per-provider limits. A provider that had never been
used could be falsely blocked by another provider's accumulated usage.
Now tracks and checks usage with scope "${projectId}:${providerName}"
per provider, and increments the same scoped counter after each run.
2. getForProject iterated the provider list without deduplicating, so a
project or run-override like ["gemini","gemini"] would dispatch the
same provider twice per keyword. Added a seen-set guard to skip
duplicate entries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 'Portfolio ranking' text was removed from the overview page; the current subtitle is 'Visibility and execution state across all projects'. Update the test to match what is actually rendered. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
arberx
added a commit
that referenced
this pull request
Mar 18, 2026
Issue #1 — trailing-slash mismatch on root route: Register both '/canonry/' and '/canonry' (bare) variants so either URL shape returns the SPA without a 404. Issue #2 — SPA fallback over-broad scope: When basePath is set, only serve index.html for paths that start with basePath; return JSON 404 for everything else so co-hosted apps on the same origin are not hijacked. Issue #3 — bare '/' base path causing duplicate-route error: Normalised base path equal to '/' is now treated as undefined (no base path), keeping the fastify-static prefix at '/' and avoiding a duplicate route registration error. Issue #4 — routePrefix without leading slash silently mis-routes: Validate opts.routePrefix at startup and throw a descriptive error if it does not start with '/'. Issue #5 — copy-pasted health handler body: Extract to a shared healthHandler const; both /health and ${basePath}health now reference the same function.
arberx
added a commit
that referenced
this pull request
Mar 18, 2026
* fix: base-path-aware API route prefix and SPA fallback
When `--base-path /canonry/` is set, three things were broken:
1. API routes were registered at /api/v1 regardless of base path,
so requests to /canonry/api/v1/* had no matching route.
2. setNotFoundHandler checked request.url.startsWith('/api/') —
correct without base path, but missed /canonry/api/v1/* URLs,
causing the SPA catch-all to return 200 HTML for all API routes.
3. @fastify/static and root route handler were prefix-unaware,
so static assets and the root index.html were not served under
the configured base path.
Fix:
- Add `routePrefix` option to ApiRoutesOptions (named to avoid
collision with Fastify's reserved `prefix` register option).
- Compute basePath once before apiRoutes registration and use it
to set routePrefix = `${basePath}api/v1` or '/api/v1' default.
- Register @fastify/static and root handler at `basePath ?? '/'`.
- Update setNotFoundHandler guard to check both '/api/' and
'${basePath}api/' so it works with and without --base-path.
- Register /health at both '/health' and '${basePath}health'.
Closes #113
* fix: address review feedback on base-path routing
Issue #1 — trailing-slash mismatch on root route:
Register both '/canonry/' and '/canonry' (bare) variants so
either URL shape returns the SPA without a 404.
Issue #2 — SPA fallback over-broad scope:
When basePath is set, only serve index.html for paths that
start with basePath; return JSON 404 for everything else so
co-hosted apps on the same origin are not hijacked.
Issue #3 — bare '/' base path causing duplicate-route error:
Normalised base path equal to '/' is now treated as undefined
(no base path), keeping the fastify-static prefix at '/' and
avoiding a duplicate route registration error.
Issue #4 — routePrefix without leading slash silently mis-routes:
Validate opts.routePrefix at startup and throw a descriptive
error if it does not start with '/'.
Issue #5 — copy-pasted health handler body:
Extract to a shared healthHandler const; both /health and
${basePath}health now reference the same function.
---------
Co-authored-by: Claw (AINYC Agent) <agent@ainyc.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ProviderAdapterinterface inpackages/contractsand provider packages for OpenAI and ClaudeJobRunnerto fan out across all configured providers per keyword, with per-provider rate limiting and partial-failure trackingallFailedrun status was incorrectly based onproviderErrors.sizewhich accumulated entries from transient keyword failures even when the provider later succeeded on other keywords. Now correctly usestotalSnapshotsInserted === 0as the guard.PUT /settings/providers/:nameAPI andProviderConfigFormUI shipped without a corresponding CLI command. Addscanonry settings provider <name> --api-key <key> [--model <model>].Test plan
pnpm run typecheckpasses across all packagespnpm run testpassescanonry initprompts for Gemini, OpenAI, and Anthropic API keyscanonry run <project>fans out to all configured providerscanonry settingsshows all provider statusescanonry settings provider gemini --api-key <key>updates a provider keypartial, notfailed🤖 Generated with Claude Code