Merge pull request #2827 from diegosouzapw/refactor/pages-v3-15-skills-pages-redesign

feat(skills): redesign agent-skills + omni-skills with dynamic 42-skill catalog + MCP/A2A discovery
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-05-30 03:37:42 -03:00
committed by GitHub
129 changed files with 16972 additions and 1346 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,62 +1,63 @@
// @ts-nocheck
import { default as __fd_glob_69 } from "../docs/security/meta.json?collection=docs"
import { default as __fd_glob_68 } from "../docs/routing/meta.json?collection=docs"
import { default as __fd_glob_67 } from "../docs/reference/openapi.yaml?collection=docs"
import { default as __fd_glob_66 } from "../docs/reference/meta.json?collection=docs"
import { default as __fd_glob_65 } from "../docs/ops/meta.json?collection=docs"
import { default as __fd_glob_64 } from "../docs/guides/meta.json?collection=docs"
import { default as __fd_glob_63 } from "../docs/frameworks/meta.json?collection=docs"
import { default as __fd_glob_62 } from "../docs/compression/meta.json?collection=docs"
import { default as __fd_glob_61 } from "../docs/architecture/meta.json?collection=docs"
import { default as __fd_glob_60 } from "../docs/meta.json?collection=docs"
import * as __fd_glob_59 from "../docs/security/STEALTH_GUIDE.md?collection=docs"
import * as __fd_glob_58 from "../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs"
import * as __fd_glob_57 from "../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"
import * as __fd_glob_56 from "../docs/security/PUBLIC_CREDS.md?collection=docs"
import * as __fd_glob_55 from "../docs/security/GUARDRAILS.md?collection=docs"
import * as __fd_glob_54 from "../docs/security/ERROR_SANITIZATION.md?collection=docs"
import * as __fd_glob_53 from "../docs/security/COMPLIANCE.md?collection=docs"
import * as __fd_glob_52 from "../docs/security/CLI_TOKEN_AUTH.md?collection=docs"
import * as __fd_glob_51 from "../docs/security/CLI_TOKEN.md?collection=docs"
import * as __fd_glob_50 from "../docs/routing/REASONING_REPLAY.md?collection=docs"
import * as __fd_glob_49 from "../docs/routing/QUOTA_SHARE.md?collection=docs"
import * as __fd_glob_48 from "../docs/routing/AUTO-COMBO.md?collection=docs"
import * as __fd_glob_47 from "../docs/reference/PROVIDER_REFERENCE.md?collection=docs"
import * as __fd_glob_46 from "../docs/reference/FREE_TIERS.md?collection=docs"
import * as __fd_glob_45 from "../docs/reference/ENVIRONMENT.md?collection=docs"
import * as __fd_glob_44 from "../docs/reference/CLI-TOOLS.md?collection=docs"
import * as __fd_glob_43 from "../docs/reference/API_REFERENCE.md?collection=docs"
import * as __fd_glob_42 from "../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"
import * as __fd_glob_41 from "../docs/ops/TUNNELS_GUIDE.md?collection=docs"
import * as __fd_glob_40 from "../docs/ops/SQLITE_RUNTIME.md?collection=docs"
import * as __fd_glob_39 from "../docs/ops/RELEASE_CHECKLIST.md?collection=docs"
import * as __fd_glob_38 from "../docs/ops/PROXY_GUIDE.md?collection=docs"
import * as __fd_glob_37 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"
import * as __fd_glob_36 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"
import * as __fd_glob_35 from "../docs/ops/COVERAGE_PLAN.md?collection=docs"
import * as __fd_glob_34 from "../docs/guides/USER_GUIDE.md?collection=docs"
import * as __fd_glob_33 from "../docs/guides/UNINSTALL.md?collection=docs"
import * as __fd_glob_32 from "../docs/guides/TROUBLESHOOTING.md?collection=docs"
import * as __fd_glob_31 from "../docs/guides/TERMUX_GUIDE.md?collection=docs"
import * as __fd_glob_30 from "../docs/guides/SETUP_GUIDE.md?collection=docs"
import * as __fd_glob_29 from "../docs/guides/PWA_GUIDE.md?collection=docs"
import * as __fd_glob_28 from "../docs/guides/KIRO_SETUP.md?collection=docs"
import * as __fd_glob_27 from "../docs/guides/I18N.md?collection=docs"
import * as __fd_glob_26 from "../docs/guides/FEATURES.md?collection=docs"
import * as __fd_glob_25 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs"
import * as __fd_glob_24 from "../docs/guides/DOCKER_GUIDE.md?collection=docs"
import * as __fd_glob_23 from "../docs/frameworks/WEBHOOKS.md?collection=docs"
import * as __fd_glob_22 from "../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs"
import * as __fd_glob_21 from "../docs/frameworks/SKILLS.md?collection=docs"
import * as __fd_glob_20 from "../docs/frameworks/OPENCODE.md?collection=docs"
import * as __fd_glob_19 from "../docs/frameworks/MEMORY.md?collection=docs"
import * as __fd_glob_18 from "../docs/frameworks/MCP-SERVER.md?collection=docs"
import * as __fd_glob_17 from "../docs/frameworks/GAMIFICATION.md?collection=docs"
import * as __fd_glob_16 from "../docs/frameworks/EVALS.md?collection=docs"
import * as __fd_glob_15 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"
import * as __fd_glob_14 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs"
import * as __fd_glob_13 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"
import * as __fd_glob_12 from "../docs/frameworks/AGENTBRIDGE.md?collection=docs"
import { default as __fd_glob_70 } from "../docs/security/meta.json?collection=docs"
import { default as __fd_glob_69 } from "../docs/routing/meta.json?collection=docs"
import { default as __fd_glob_68 } from "../docs/reference/openapi.yaml?collection=docs"
import { default as __fd_glob_67 } from "../docs/reference/meta.json?collection=docs"
import { default as __fd_glob_66 } from "../docs/ops/meta.json?collection=docs"
import { default as __fd_glob_65 } from "../docs/guides/meta.json?collection=docs"
import { default as __fd_glob_64 } from "../docs/frameworks/meta.json?collection=docs"
import { default as __fd_glob_63 } from "../docs/compression/meta.json?collection=docs"
import { default as __fd_glob_62 } from "../docs/architecture/meta.json?collection=docs"
import { default as __fd_glob_61 } from "../docs/meta.json?collection=docs"
import * as __fd_glob_60 from "../docs/security/STEALTH_GUIDE.md?collection=docs"
import * as __fd_glob_59 from "../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs"
import * as __fd_glob_58 from "../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"
import * as __fd_glob_57 from "../docs/security/PUBLIC_CREDS.md?collection=docs"
import * as __fd_glob_56 from "../docs/security/GUARDRAILS.md?collection=docs"
import * as __fd_glob_55 from "../docs/security/ERROR_SANITIZATION.md?collection=docs"
import * as __fd_glob_54 from "../docs/security/COMPLIANCE.md?collection=docs"
import * as __fd_glob_53 from "../docs/security/CLI_TOKEN_AUTH.md?collection=docs"
import * as __fd_glob_52 from "../docs/security/CLI_TOKEN.md?collection=docs"
import * as __fd_glob_51 from "../docs/routing/REASONING_REPLAY.md?collection=docs"
import * as __fd_glob_50 from "../docs/routing/QUOTA_SHARE.md?collection=docs"
import * as __fd_glob_49 from "../docs/routing/AUTO-COMBO.md?collection=docs"
import * as __fd_glob_48 from "../docs/reference/PROVIDER_REFERENCE.md?collection=docs"
import * as __fd_glob_47 from "../docs/reference/FREE_TIERS.md?collection=docs"
import * as __fd_glob_46 from "../docs/reference/ENVIRONMENT.md?collection=docs"
import * as __fd_glob_45 from "../docs/reference/CLI-TOOLS.md?collection=docs"
import * as __fd_glob_44 from "../docs/reference/API_REFERENCE.md?collection=docs"
import * as __fd_glob_43 from "../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"
import * as __fd_glob_42 from "../docs/ops/TUNNELS_GUIDE.md?collection=docs"
import * as __fd_glob_41 from "../docs/ops/SQLITE_RUNTIME.md?collection=docs"
import * as __fd_glob_40 from "../docs/ops/RELEASE_CHECKLIST.md?collection=docs"
import * as __fd_glob_39 from "../docs/ops/PROXY_GUIDE.md?collection=docs"
import * as __fd_glob_38 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"
import * as __fd_glob_37 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"
import * as __fd_glob_36 from "../docs/ops/COVERAGE_PLAN.md?collection=docs"
import * as __fd_glob_35 from "../docs/guides/USER_GUIDE.md?collection=docs"
import * as __fd_glob_34 from "../docs/guides/UNINSTALL.md?collection=docs"
import * as __fd_glob_33 from "../docs/guides/TROUBLESHOOTING.md?collection=docs"
import * as __fd_glob_32 from "../docs/guides/TERMUX_GUIDE.md?collection=docs"
import * as __fd_glob_31 from "../docs/guides/SETUP_GUIDE.md?collection=docs"
import * as __fd_glob_30 from "../docs/guides/PWA_GUIDE.md?collection=docs"
import * as __fd_glob_29 from "../docs/guides/KIRO_SETUP.md?collection=docs"
import * as __fd_glob_28 from "../docs/guides/I18N.md?collection=docs"
import * as __fd_glob_27 from "../docs/guides/FEATURES.md?collection=docs"
import * as __fd_glob_26 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs"
import * as __fd_glob_25 from "../docs/guides/DOCKER_GUIDE.md?collection=docs"
import * as __fd_glob_24 from "../docs/frameworks/WEBHOOKS.md?collection=docs"
import * as __fd_glob_23 from "../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs"
import * as __fd_glob_22 from "../docs/frameworks/SKILLS.md?collection=docs"
import * as __fd_glob_21 from "../docs/frameworks/OPENCODE.md?collection=docs"
import * as __fd_glob_20 from "../docs/frameworks/MEMORY.md?collection=docs"
import * as __fd_glob_19 from "../docs/frameworks/MCP-SERVER.md?collection=docs"
import * as __fd_glob_18 from "../docs/frameworks/GAMIFICATION.md?collection=docs"
import * as __fd_glob_17 from "../docs/frameworks/EVALS.md?collection=docs"
import * as __fd_glob_16 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"
import * as __fd_glob_15 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs"
import * as __fd_glob_14 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"
import * as __fd_glob_13 from "../docs/frameworks/AGENTBRIDGE.md?collection=docs"
import * as __fd_glob_12 from "../docs/frameworks/AGENT-SKILLS.md?collection=docs"
import * as __fd_glob_11 from "../docs/frameworks/A2A-SERVER.md?collection=docs"
import * as __fd_glob_10 from "../docs/compression/RTK_COMPRESSION.md?collection=docs"
import * as __fd_glob_9 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"
@@ -77,4 +78,4 @@ const create = server<typeof Config, import("fumadocs-mdx/runtime/types").Intern
}
}>({"doc":{"passthroughs":["extractedReferences"]}});
export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_60, "architecture/meta.json": __fd_glob_61, "compression/meta.json": __fd_glob_62, "frameworks/meta.json": __fd_glob_63, "guides/meta.json": __fd_glob_64, "ops/meta.json": __fd_glob_65, "reference/meta.json": __fd_glob_66, "reference/openapi.yaml": __fd_glob_67, "routing/meta.json": __fd_glob_68, "security/meta.json": __fd_glob_69, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/MONITORING_SECTIONS.md": __fd_glob_3, "architecture/REPOSITORY_MAP.md": __fd_glob_4, "architecture/RESILIENCE_GUIDE.md": __fd_glob_5, "compression/COMPRESSION_ENGINES.md": __fd_glob_6, "compression/COMPRESSION_GUIDE.md": __fd_glob_7, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_8, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_9, "compression/RTK_COMPRESSION.md": __fd_glob_10, "frameworks/A2A-SERVER.md": __fd_glob_11, "frameworks/AGENTBRIDGE.md": __fd_glob_12, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_13, "frameworks/CLOUD_AGENT.md": __fd_glob_14, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_15, "frameworks/EVALS.md": __fd_glob_16, "frameworks/GAMIFICATION.md": __fd_glob_17, "frameworks/MCP-SERVER.md": __fd_glob_18, "frameworks/MEMORY.md": __fd_glob_19, "frameworks/OPENCODE.md": __fd_glob_20, "frameworks/SKILLS.md": __fd_glob_21, "frameworks/TRAFFIC_INSPECTOR.md": __fd_glob_22, "frameworks/WEBHOOKS.md": __fd_glob_23, "guides/DOCKER_GUIDE.md": __fd_glob_24, "guides/ELECTRON_GUIDE.md": __fd_glob_25, "guides/FEATURES.md": __fd_glob_26, "guides/I18N.md": __fd_glob_27, "guides/KIRO_SETUP.md": __fd_glob_28, "guides/PWA_GUIDE.md": __fd_glob_29, "guides/SETUP_GUIDE.md": __fd_glob_30, "guides/TERMUX_GUIDE.md": __fd_glob_31, "guides/TROUBLESHOOTING.md": __fd_glob_32, "guides/UNINSTALL.md": __fd_glob_33, "guides/USER_GUIDE.md": __fd_glob_34, "ops/COVERAGE_PLAN.md": __fd_glob_35, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_36, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_37, "ops/PROXY_GUIDE.md": __fd_glob_38, "ops/RELEASE_CHECKLIST.md": __fd_glob_39, "ops/SQLITE_RUNTIME.md": __fd_glob_40, "ops/TUNNELS_GUIDE.md": __fd_glob_41, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_42, "reference/API_REFERENCE.md": __fd_glob_43, "reference/CLI-TOOLS.md": __fd_glob_44, "reference/ENVIRONMENT.md": __fd_glob_45, "reference/FREE_TIERS.md": __fd_glob_46, "reference/PROVIDER_REFERENCE.md": __fd_glob_47, "routing/AUTO-COMBO.md": __fd_glob_48, "routing/QUOTA_SHARE.md": __fd_glob_49, "routing/REASONING_REPLAY.md": __fd_glob_50, "security/CLI_TOKEN.md": __fd_glob_51, "security/CLI_TOKEN_AUTH.md": __fd_glob_52, "security/COMPLIANCE.md": __fd_glob_53, "security/ERROR_SANITIZATION.md": __fd_glob_54, "security/GUARDRAILS.md": __fd_glob_55, "security/PUBLIC_CREDS.md": __fd_glob_56, "security/ROUTE_GUARD_TIERS.md": __fd_glob_57, "security/SOCKET_DEV_FINDINGS.md": __fd_glob_58, "security/STEALTH_GUIDE.md": __fd_glob_59, });
export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_61, "architecture/meta.json": __fd_glob_62, "compression/meta.json": __fd_glob_63, "frameworks/meta.json": __fd_glob_64, "guides/meta.json": __fd_glob_65, "ops/meta.json": __fd_glob_66, "reference/meta.json": __fd_glob_67, "reference/openapi.yaml": __fd_glob_68, "routing/meta.json": __fd_glob_69, "security/meta.json": __fd_glob_70, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/MONITORING_SECTIONS.md": __fd_glob_3, "architecture/REPOSITORY_MAP.md": __fd_glob_4, "architecture/RESILIENCE_GUIDE.md": __fd_glob_5, "compression/COMPRESSION_ENGINES.md": __fd_glob_6, "compression/COMPRESSION_GUIDE.md": __fd_glob_7, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_8, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_9, "compression/RTK_COMPRESSION.md": __fd_glob_10, "frameworks/A2A-SERVER.md": __fd_glob_11, "frameworks/AGENT-SKILLS.md": __fd_glob_12, "frameworks/AGENTBRIDGE.md": __fd_glob_13, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_14, "frameworks/CLOUD_AGENT.md": __fd_glob_15, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_16, "frameworks/EVALS.md": __fd_glob_17, "frameworks/GAMIFICATION.md": __fd_glob_18, "frameworks/MCP-SERVER.md": __fd_glob_19, "frameworks/MEMORY.md": __fd_glob_20, "frameworks/OPENCODE.md": __fd_glob_21, "frameworks/SKILLS.md": __fd_glob_22, "frameworks/TRAFFIC_INSPECTOR.md": __fd_glob_23, "frameworks/WEBHOOKS.md": __fd_glob_24, "guides/DOCKER_GUIDE.md": __fd_glob_25, "guides/ELECTRON_GUIDE.md": __fd_glob_26, "guides/FEATURES.md": __fd_glob_27, "guides/I18N.md": __fd_glob_28, "guides/KIRO_SETUP.md": __fd_glob_29, "guides/PWA_GUIDE.md": __fd_glob_30, "guides/SETUP_GUIDE.md": __fd_glob_31, "guides/TERMUX_GUIDE.md": __fd_glob_32, "guides/TROUBLESHOOTING.md": __fd_glob_33, "guides/UNINSTALL.md": __fd_glob_34, "guides/USER_GUIDE.md": __fd_glob_35, "ops/COVERAGE_PLAN.md": __fd_glob_36, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_37, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_38, "ops/PROXY_GUIDE.md": __fd_glob_39, "ops/RELEASE_CHECKLIST.md": __fd_glob_40, "ops/SQLITE_RUNTIME.md": __fd_glob_41, "ops/TUNNELS_GUIDE.md": __fd_glob_42, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_43, "reference/API_REFERENCE.md": __fd_glob_44, "reference/CLI-TOOLS.md": __fd_glob_45, "reference/ENVIRONMENT.md": __fd_glob_46, "reference/FREE_TIERS.md": __fd_glob_47, "reference/PROVIDER_REFERENCE.md": __fd_glob_48, "routing/AUTO-COMBO.md": __fd_glob_49, "routing/QUOTA_SHARE.md": __fd_glob_50, "routing/REASONING_REPLAY.md": __fd_glob_51, "security/CLI_TOKEN.md": __fd_glob_52, "security/CLI_TOKEN_AUTH.md": __fd_glob_53, "security/COMPLIANCE.md": __fd_glob_54, "security/ERROR_SANITIZATION.md": __fd_glob_55, "security/GUARDRAILS.md": __fd_glob_56, "security/PUBLIC_CREDS.md": __fd_glob_57, "security/ROUTE_GUARD_TIERS.md": __fd_glob_58, "security/SOCKET_DEV_FINDINGS.md": __fd_glob_59, "security/STEALTH_GUIDE.md": __fd_glob_60, });

View File

@@ -0,0 +1,133 @@
# Task 15 — Skill Generator Output
Timestamp: 2026-05-28T00:00:00.000Z
---
## Dry-run summary
- Generated: 42 SKILL.md files
- Unchanged: 0 (initial dry-run baseline)
- Orphans detected: 18 (old omniroute-* directories)
- Custom blocks found: 0 (dry-run; no existing custom blocks at time of initial pass)
---
## Apply summary
- Generated: 42 SKILL.md files written (22 API + 20 CLI)
- Unchanged: 0 (all freshly generated on first apply)
- Pruned: 18 orphan directories moved to `_orchestration/15-pruned-archive/`
---
## Generated skill IDs
### API Skills (22)
1. `omni-auth`
2. `omni-providers`
3. `omni-models`
4. `omni-combos-routing`
5. `omni-api-keys`
6. `omni-usage-logs`
7. `omni-budget`
8. `omni-settings`
9. `omni-proxies`
10. `omni-cache`
11. `omni-compression`
12. `omni-context-rtk`
13. `omni-resilience`
14. `omni-cli-tools`
15. `omni-tunnels`
16. `omni-sync-cloud`
17. `omni-db-backups`
18. `omni-webhooks`
19. `omni-mcp`
20. `omni-agents-a2a`
21. `omni-version-manager`
22. `omni-inference`
### CLI Skills (20)
1. `cli-serve`
2. `cli-health`
3. `cli-providers`
4. `cli-keys`
5. `cli-models`
6. `cli-chat`
7. `cli-routing`
8. `cli-resilience`
9. `cli-compression`
10. `cli-contexts`
11. `cli-cost-usage`
12. `cli-mcp`
13. `cli-a2a`
14. `cli-tunnel`
15. `cli-backup-sync`
16. `cli-policy-audit`
17. `cli-batches`
18. `cli-eval`
19. `cli-plugins-skills`
20. `cli-setup`
---
## Pruned orphan IDs (18)
These directories were present in `skills/` but have no matching entry in
`CURATED_SKILLS`. They were moved to `_orchestration/15-pruned-archive/` for
reference and will not be served by the catalog.
1. `omniroute`
2. `omniroute-a2a`
3. `omniroute-chat`
4. `omniroute-cli`
5. `omniroute-cli-admin`
6. `omniroute-cli-cloud`
7. `omniroute-cli-eval`
8. `omniroute-cli-providers`
9. `omniroute-compression`
10. `omniroute-embeddings`
11. `omniroute-image`
12. `omniroute-mcp`
13. `omniroute-monitoring`
14. `omniroute-routing`
15. `omniroute-stt`
16. `omniroute-tts`
17. `omniroute-web-fetch`
18. `omniroute-web-search`
Archive location: `_tasks/features-v3.8.6/refactorpages/_orchestration/15-pruned-archive/`
---
## Idempotency confirmation
A second apply run with the same 42-entry `CURATED_SKILLS` produced:
- Generated: 0 (all files already up-to-date)
- Unchanged: 42
- Pruned: 0
The generator correctly detects that all output files are current and skips
regeneration, confirming idempotent behaviour.
---
## Custom blocks preserved (10)
The following skills contained `<!-- skill:custom-start --> ... <!-- skill:custom-end -->`
blocks with manually authored content. The generator re-injected these blocks
unchanged after regenerating the surrounding scaffold:
1. `omni-auth`
2. `omni-resilience`
3. `omni-mcp`
4. `omni-combos-routing`
5. `omni-compression`
6. `omni-agents-a2a`
7. `omni-inference`
8. `cli-serve`
9. `cli-providers`
10. `cli-eval`

View File

@@ -274,7 +274,7 @@ table groups the actual directories and notable top-level files.
| Module | Purpose |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (5 skills: cost analysis, health report, provider discovery, quota management, smart routing) |
| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (6 skills: cost analysis, health report, provider discovery, quota management, smart routing, list-capabilities) |
| `acp/` | Agent-Control-Protocol: `index.ts`, `manager.ts`, `registry.ts` |
| `api/` | Internal API helpers: `requireManagementAuth.ts`, `requireCliToolsAuth.ts`, `errorResponse.ts` |
| `auth/` | `managementPassword.ts` (password reset / hashing) |
@@ -302,6 +302,7 @@ table groups the actual directories and notable top-level files.
| `runtime/` | Runtime feature detection |
| `search/` | `executeWebSearch.ts` |
| `services/` | Embedded services framework: `ServiceSupervisor.ts` (generic child-process supervisor with operation lock, ring buffer, health checker), `bootstrap.ts` (process-level registration and auto-start), `registry.ts` (tool → supervisor map), `apiKey.ts` (AES-256-GCM key store), `modelSync.ts` (periodic model sync), `ringBuffer.ts` (5 MB circular log buffer), `healthCheck.ts` (HTTP health probe), `types.ts`, `embedWsProxy.ts` (WebSocket proxy), `installers/{ninerouter,cliproxy}.ts`. See `docs/frameworks/EMBEDDED-SERVICES.md` |
| `agentSkills/` | Agent Skills catalog + generator: `catalog.ts` (getCatalog/getSkillById/filterCatalog/computeCoverage), `generator.ts` (generateAgentSkills → writes `skills/{id}/SKILL.md`), `openapiParser.ts` (extracts REST endpoints from OpenAPI spec), `cliRegistryParser.ts` (extracts CLI subcommands from bin/cli-registry), `schemas.ts` (Zod: AgentSkillSchema, SkillCoverageSchema, ListQuerySchema, GenerateBodySchema), `types.ts` (AgentSkill, SkillCoverage, SkillMarkdown, GeneratorReport). Consumed by REST routes (`/api/agent-skills/*`), MCP tools (`omniroute_agent_skills_*`), and A2A skill `list-capabilities`. See [AGENT-SKILLS.md](../frameworks/AGENT-SKILLS.md). |
| `skills/` | Skill framework: `registry.ts`, `executor.ts`, `interception.ts`, `injection.ts`, `sandbox.ts`, `custom.ts`, `hybrid.ts`, `builtins.ts`, `a2a.ts`, `providerSettings.ts`, `schemas.ts`, `skillssh.ts`, `types.ts`, plus `builtin/browser.ts` |
| `spend/` | `batchWriter.ts` (write-behind buffer) |
| `sync/` | `bundle.ts`, `tokens.ts` (Cloud Sync) |

View File

@@ -139,18 +139,32 @@ curl -X POST http://localhost:20128/a2a \
## Available Skills
OmniRoute exposes 5 A2A skills wired in `src/lib/a2a/taskExecution.ts::A2A_SKILL_HANDLERS`. Each skill module lives in `src/lib/a2a/skills/`.
OmniRoute exposes 6 A2A skills wired in `src/lib/a2a/taskExecution.ts::A2A_SKILL_HANDLERS`. Each skill module lives in `src/lib/a2a/skills/`.
| Skill | ID | Description |
| :----------------- | :------------------- | :------------------------------------------------------------------------------------------ |
| Smart Routing | `smart-routing` | Routes a prompt through the optimal provider/combo using OmniRoute's combo engine + scoring |
| Quota Management | `quota-management` | Reports per-provider quota state, helps callers decide when to throttle/switch |
| Provider Discovery | `provider-discovery` | Lists installed providers with capabilities, free-tier flags, OAuth status |
| Cost Analysis | `cost-analysis` | Estimates cost of a request/conversation given the catalog + recent usage |
| Health Report | `health-report` | Aggregates circuit breaker, cooldown, lockout state per provider |
| Skill | ID | Description | Tags | Examples |
| :------------------ | :-------------------- | :------------------------------------------------------------------------------------------------------------ | :---------------------- | :-------------------------------------- |
| Smart Routing | `smart-routing` | Routes a prompt through the optimal provider/combo using OmniRoute's combo engine + scoring | routing, providers | "Route this prompt via the best model" |
| Quota Management | `quota-management` | Reports per-provider quota state, helps callers decide when to throttle/switch | quota, providers | "Check quota for anthropic" |
| Provider Discovery | `provider-discovery` | Lists installed providers with capabilities, free-tier flags, OAuth status | providers, discovery | "What providers are available?" |
| Cost Analysis | `cost-analysis` | Estimates cost of a request/conversation given the catalog + recent usage | cost, usage | "Estimate cost for this conversation" |
| Health Report | `health-report` | Aggregates circuit breaker, cooldown, lockout state per provider | health, resilience | "Show health status of all providers" |
| List Capabilities | `list-capabilities` | Returns the full 42-entry Agent Skills catalog as a markdown table with raw SKILL.md URLs for context injection | catalog, discovery, skills | "List all OmniRoute capabilities" |
> Note: the Agent Card description currently advertises "36+ providers" (`src/app/.well-known/agent.json/route.ts:26` and `:55`). The actual catalog has grown to 180+ providers — the string should be updated in a follow-up change (tracked as a separate doc/code TODO; not modified here).
### `list-capabilities` Skill Detail
The `list-capabilities` skill is particularly useful for external agents that need to discover what OmniRoute exposes before sending API calls. It returns a structured markdown table artifact:
```
| ID | Name | Category | Area | Endpoints/Commands | Raw URL |
| --- | --- | --- | --- | --- | --- |
| omni-auth | Auth & Sessions | api | auth | POST /api/auth/login, ... | https://raw.githubusercontent.com/... |
...
```
Each row includes the `rawUrl` column so agents can immediately fetch the full SKILL.md. The `metadata.totalSkills` field is always `42`. Implementation: `src/lib/a2a/skills/listCapabilities.ts`. See also [AGENT-SKILLS.md](./AGENT-SKILLS.md).
---
## REST API (auxiliary)

View File

@@ -0,0 +1,301 @@
---
title: "OmniRoute Agent Skills Catalog"
version: 3.8.6
lastUpdated: 2026-05-28
---
# OmniRoute Agent Skills Catalog
> **Source of truth:** `src/lib/agentSkills/` (catalog, generator, parsers) + `skills/` directory (SKILL.md files)
> **Last updated:** 2026-05-28 — v3.8.6
Agent Skills are structured SKILL.md files that teach external agents, MCP clients, and A2A orchestrators how to use OmniRoute's REST API and CLI. Unlike [Omni Skills](./SKILLS.md) (which are LLM tool definitions executed inside OmniRoute), Agent Skills are a *documentation catalog* — static markdown that can be fed directly into agent context.
---
## Overview
The catalog contains **42 canonical Agent Skills** (22 REST API + 20 CLI). Each skill has:
- A **canonical ID** (`omni-auth`, `cli-serve`, etc.)
- A **SKILL.md** file in `skills/{id}/SKILL.md` with YAML frontmatter (`name`, `description`) + rich markdown body
- **REST endpoints** (API skills) or **CLI subcommands** (CLI skills) derived from the OpenAPI spec and CLI registry
- A **GitHub raw URL** for live fetch: `https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/{id}/SKILL.md`
---
## Architecture
```
src/shared/constants/agentSkills.ts — 42-entry curated list (name/desc/category/area/icon)
src/lib/agentSkills/
catalog.ts — getCatalog(), getSkillById(), filterCatalog(), computeCoverage()
generator.ts — generateAgentSkills() writes SKILL.md to skills/{id}/
openapiParser.ts — extracts REST endpoints from docs/reference/openapi.yaml
cliRegistryParser.ts — extracts CLI subcommands from bin/cli-registry.ts
schemas.ts — Zod schemas: AgentSkillSchema, SkillCoverageSchema, etc.
types.ts — TypeScript interfaces: AgentSkill, SkillCoverage, etc.
skills/{id}/SKILL.md — Generated + curated markdown files (42 total)
src/app/api/agent-skills/
route.ts — GET /api/agent-skills
[id]/route.ts — GET /api/agent-skills/{id}
[id]/raw/route.ts — GET /api/agent-skills/{id}/raw (text/markdown)
coverage/route.ts — GET /api/agent-skills/coverage
generate/route.ts — POST /api/agent-skills/generate (auth required)
open-sse/mcp-server/tools/agentSkillTools.ts — 3 MCP tools (list, get, coverage)
src/lib/a2a/skills/listCapabilities.ts — A2A skill: list-capabilities
```
---
## SKILL.md Format
```markdown
---
name: omni-providers
description: "Manage provider connections: add, test, rotate, and remove credentials."
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
...
## Authentication
...
## Endpoints
...
<!-- skill:custom-start -->
## Custom Section (preserved across regeneration)
...
<!-- skill:custom-end -->
```
The generator preserves content between `<!-- skill:custom-start -->` and `<!-- skill:custom-end -->` on regeneration. Ten skills have curated custom blocks:
`omni-mcp`, `omni-compression`, `cli-providers`, `cli-eval`, `omni-agents-a2a`, `omni-combos-routing`, `omni-auth`, `omni-resilience`, `omni-inference`, `cli-serve`.
---
## REST API Discovery
| Endpoint | Method | Description | Auth |
| :--- | :--- | :--- | :--- |
| `/api/agent-skills` | GET | List catalog (optional `?category=api\|cli&area=<area>`) | none |
| `/api/agent-skills/{id}` | GET | Get single skill metadata | none |
| `/api/agent-skills/{id}/raw` | GET | Fetch SKILL.md as `text/markdown` | none |
| `/api/agent-skills/coverage` | GET | Coverage stats (how many SKILL.md files exist) | none |
| `/api/agent-skills/generate` | POST | Trigger generator (dryRun/prune/onlyIds) | management |
Example — list all API skills:
```bash
curl "http://localhost:20128/api/agent-skills?category=api"
```
Example — fetch a single SKILL.md:
```bash
curl -H "Accept: text/markdown" "http://localhost:20128/api/agent-skills/omni-providers/raw"
```
---
## MCP Discovery
Three MCP tools are registered under scope `read:catalog`:
| Tool | Description |
| :--- | :--- |
| `omniroute_agent_skills_list` | List skills (optional `category` / `area` filters) |
| `omniroute_agent_skills_get` | Get metadata + SKILL.md for one skill by `id` |
| `omniroute_agent_skills_coverage` | Coverage stats (API/CLI have/total) |
See [MCP-SERVER.md](./MCP-SERVER.md) for scope wiring and authentication.
---
## A2A Discovery
The A2A skill `list-capabilities` returns the full 42-skill catalog as a markdown table artifact. External orchestrators can invoke it via:
```json
{
"jsonrpc": "2.0", "id": "1",
"method": "message/send",
"params": {
"skill": "list-capabilities",
"messages": [{"role": "user", "content": "List all capabilities"}]
}
}
```
See [A2A-SERVER.md](./A2A-SERVER.md) for protocol details.
---
## Catalog — 42 Skill IDs
### API Skills (22)
| ID | Area | Entry Point |
| :--- | :--- | :--- |
| `omni-auth` | auth | Auth + session management |
| `omni-providers` | providers | Provider connection management |
| `omni-models` | models | Model catalog and capabilities |
| `omni-combos-routing` | combos-routing | Combo routing strategies |
| `omni-api-keys` | api-keys | API key management |
| `omni-usage-logs` | usage-logs | Usage and cost logs |
| `omni-budget` | budget | Budget guards |
| `omni-settings` | settings | Global settings |
| `omni-proxies` | proxies | Proxy pool management |
| `omni-cache` | cache | Semantic + prompt cache |
| `omni-compression` | compression | Context compression engines |
| `omni-context-rtk` | context-rtk | RTK compression |
| `omni-resilience` | resilience | Circuit breakers + cooldowns |
| `omni-cli-tools` | cli-tools | CLI tools REST proxy |
| `omni-tunnels` | tunnels | Tunnel management |
| `omni-sync-cloud` | sync-cloud | Cloud sync |
| `omni-db-backups` | db-backups | Database backups |
| `omni-webhooks` | webhooks | Webhook event dispatcher |
| `omni-mcp` | mcp | MCP server (37 tools, 3 transports) |
| `omni-agents-a2a` | agents-a2a | A2A agent protocol |
| `omni-version-manager` | version-manager | Version and update management |
| `omni-inference` | inference | Direct inference / completions |
### CLI Skills (20)
| ID | Area | CLI Command Root |
| :--- | :--- | :--- |
| `cli-serve` | cli-serve | `omniroute serve` |
| `cli-health` | cli-health | `omniroute health` |
| `cli-providers` | cli-providers | `omniroute providers` |
| `cli-keys` | cli-keys | `omniroute keys` |
| `cli-models` | cli-models | `omniroute models` |
| `cli-chat` | cli-chat | `omniroute chat` |
| `cli-routing` | cli-routing | `omniroute routing` |
| `cli-resilience` | cli-resilience | `omniroute resilience` |
| `cli-compression` | cli-compression | `omniroute compression` |
| `cli-contexts` | cli-contexts | `omniroute contexts` |
| `cli-cost-usage` | cli-cost-usage | `omniroute cost` |
| `cli-mcp` | cli-mcp | `omniroute mcp` |
| `cli-a2a` | cli-a2a | `omniroute a2a` |
| `cli-tunnel` | cli-tunnel | `omniroute tunnel` |
| `cli-backup-sync` | cli-backup-sync | `omniroute backup` |
| `cli-policy-audit` | cli-policy-audit | `omniroute policy` |
| `cli-batches` | cli-batches | `omniroute batch` |
| `cli-eval` | cli-eval | `omniroute eval` |
| `cli-plugins-skills` | cli-plugins-skills | `omniroute plugins` |
| `cli-setup` | cli-setup | `omniroute setup` |
---
## How External Agents Consume Skills
### 1. Discovery via REST
```bash
# Get the full catalog
curl "http://your-omniroute/api/agent-skills" | jq '.skills[] | {id, name, category}'
# Get SKILL.md for context injection
curl "http://your-omniroute/api/agent-skills/omni-providers/raw" > omni-providers.md
```
### 2. Discovery via MCP
```typescript
// In a Claude Desktop / Cursor MCP client:
const result = await client.callTool("omniroute_agent_skills_list", { category: "api" });
// result.skills → array of AgentSkill with rawUrl for each
```
### 3. Discovery via A2A
```python
import requests
resp = requests.post("http://your-omniroute/a2a", json={
"jsonrpc": "2.0", "id": "1",
"method": "message/send",
"params": {"skill": "list-capabilities", "messages": [{"role": "user", "content": "list"}]}
})
table = resp.json()["result"]["artifacts"][0]["content"]
# table is a markdown table with all 42 skill IDs + rawUrl columns
```
### 4. Direct GitHub raw fetch (no server required)
```bash
BASE="https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills"
curl "${BASE}/omni-providers/SKILL.md"
```
---
## Generator
The generator reads the curated catalog + OpenAPI spec + CLI registry and writes `skills/{id}/SKILL.md` for each entry:
```bash
# Preview (dry run, no writes)
curl -X POST http://localhost:20128/api/agent-skills/generate \
-H "Authorization: Bearer <admin-key>" \
-H "Content-Type: application/json" \
-d '{"dryRun":true}'
# Full regeneration
curl -X POST http://localhost:20128/api/agent-skills/generate \
-H "Authorization: Bearer <admin-key>" \
-H "Content-Type: application/json" \
-d '{"dryRun":false,"prune":false}'
# Regenerate specific IDs
curl -X POST http://localhost:20128/api/agent-skills/generate \
-H "Authorization: Bearer <admin-key>" \
-H "Content-Type: application/json" \
-d '{"dryRun":false,"onlyIds":["omni-providers","cli-serve"]}'
```
The generator response is a `GeneratorReport`:
```json
{
"generated": ["omni-providers", "cli-serve"],
"unchanged": [],
"pruned": [],
"orphansDetected": [],
"errors": []
}
```
---
## Coverage API
```bash
curl "http://localhost:20128/api/agent-skills/coverage"
```
```json
{
"api": {"have": 22, "total": 22},
"cli": {"have": 20, "total": 20},
"totalSkills": 42,
"generatedAt": "2026-05-28T00:00:00.000Z"
}
```
---
## Related
- [SKILLS.md](./SKILLS.md) — Omni Skills framework (LLM tool injection + marketplace)
- [MCP-SERVER.md](./MCP-SERVER.md) — MCP tool catalog (`omniroute_agent_skills_*` tools)
- [A2A-SERVER.md](./A2A-SERVER.md) — A2A protocol (`list-capabilities` skill)
- `src/lib/agentSkills/` — catalog, generator, parsers
- `skills/` — generated SKILL.md files (42 entries)

View File

@@ -175,6 +175,18 @@ Defined in `open-sse/mcp-server/tools/skillTools.ts`. Backed by `src/lib/skills/
| `omniroute_skills_execute` | Execute a skill with provided input and return the execution record |
| `omniroute_skills_executions` | List recent skill execution history |
## Agent Skill Catalog Tools (3)
Defined in `open-sse/mcp-server/tools/agentSkillTools.ts`. Backed by `src/lib/agentSkills/catalog`. These tools expose the 42-entry Agent Skills documentation catalog to MCP clients and external agents. Scope: `read:catalog`.
| Tool | Scopes | Description |
| :--------------------------------- | :------------- | :--------------------------------------------------------------------------------------------------------------- |
| `omniroute_agent_skills_list` | `read:catalog` | List all 42 agent skills with optional `category` (api\|cli) and `area` filters; returns metadata + coverage |
| `omniroute_agent_skills_get` | `read:catalog` | Get full metadata + SKILL.md content for a single skill by canonical `id` |
| `omniroute_agent_skills_coverage` | `read:catalog` | Coverage stats: how many of the 22 API and 20 CLI skills have SKILL.md files on the filesystem vs catalog totals |
See [AGENT-SKILLS.md](./AGENT-SKILLS.md) for the full catalog and how external agents consume it.
## Related Frameworks (v3.8.0)
The MCP tool inventory above (37 tools = 30 base + 3 memory + 4 skills) is intentionally
@@ -248,9 +260,11 @@ MCP tools are authenticated through API key scopes. Scope enforcement is central
| `write:compression` | `compression_configure`, `set_compression_engine` |
| `read:proxies` | `oneproxy_fetch`, `oneproxy_rotate`, `oneproxy_stats` |
| `read:catalog` | `agent_skills_list`, `agent_skills_get`, `agent_skills_coverage` |
Wildcard scopes are supported: `read:*` grants all read-scopes, `*` grants full access.
Memory and Skill tools currently do not declare static scope requirements in their definitions; access is gated by the caller's API key and audited through the standard MCP audit pipeline.
Memory and Skill tools currently do not declare static scope requirements in their definitions; access is gated by the caller's API key and audited through the standard MCP audit pipeline. Agent Skill Catalog tools require `read:catalog`.
---

View File

@@ -1,13 +1,13 @@
---
title: "Skills Framework"
version: 3.8.2
lastUpdated: 2026-05-13
version: 3.8.6
lastUpdated: 2026-05-28
---
# Skills Framework
> **Source of truth:** `src/lib/skills/` and `src/app/api/skills/`
> **Last updated:** 2026-05-13 — v3.8.0
> **Last updated:** 2026-05-28 — v3.8.6
OmniRoute exposes an extensible Skills framework that lets language models (and operators) compose reusable capabilities — from filesystem reads and HTTP requests to sandboxed code execution and curated marketplace skills.
@@ -15,6 +15,28 @@ A skill is a versioned, schema-defined unit of work. OmniRoute can inject skills
---
## Agent Skills vs Omni Skills
OmniRoute has two distinct but complementary skill systems:
| Dimension | **Omni Skills** (this doc) | **Agent Skills** |
| :--- | :--- | :--- |
| Purpose | LLM tool injection + sandboxed execution | SKILL.md catalog for external agents to discover and consume |
| Source of truth | `src/lib/skills/` + marketplace | `src/lib/agentSkills/` + `skills/` directory |
| Runtime mode | Injected into outbound requests, executed on tool-call events | Static markdown catalog + REST/MCP/A2A discovery endpoints |
| Who uses it | OmniRoute itself (combo routing, inbound LLM calls) | External agents, MCP clients, A2A orchestrators |
| Count | Variable (marketplace-driven) | 42 canonical entries (22 API + 20 CLI) |
| Format | `SkillDefinition` with tool schema + handler | `SKILL.md` frontmatter + markdown body |
| Discovery | `/api/skills/*` REST + `omniroute_skills_*` MCP tools | `/api/agent-skills/*` REST + `omniroute_agent_skills_*` MCP tools + A2A `list-capabilities` |
**Omni Skills** are the execution engine — they define what OmniRoute *can do* when an LLM invokes a tool.
**Agent Skills** are the documentation catalog — they explain to external agents *how to use* OmniRoute's REST API and CLI, with structured SKILL.md files that can be fed directly into agent prompts.
For the Agent Skills catalog, generator, MCP tools, and A2A skill, see [docs/frameworks/AGENT-SKILLS.md](./AGENT-SKILLS.md).
---
## Concepts
### Skill Sources

View File

@@ -75,6 +75,10 @@ tags:
description: Fallback chain management
- name: Telemetry
description: Telemetry and token health monitoring
- name: Agent Skills
description: >-
Agent Skills catalog — 42 SKILL.md files (22 REST API + 20 CLI) for external agents,
MCP clients, and A2A orchestrators to discover OmniRoute capabilities.
- name: AgentBridge
description: >-
MITM proxy manager for 9 IDE agents (Antigravity, Kiro, Copilot, Codex, Cursor, Zed,
@@ -3715,6 +3719,232 @@ paths:
"500":
description: Failed to parse OpenAPI spec
# ─── Agent Skills Catalog ────────────────────────────────────────────────────
/api/agent-skills:
get:
tags: [Agent Skills]
summary: List agent skills catalog
description: |
Returns the full 42-entry Agent Skills catalog with optional filtering.
Skills describe how to use OmniRoute's REST API and CLI — they are structured
SKILL.md documentation files discoverable by external agents, MCP clients, and
A2A orchestrators. No authentication required.
parameters:
- name: category
in: query
required: false
schema:
type: string
enum: [api, cli]
description: Filter by category (api = REST API skills, cli = CLI skills)
- name: area
in: query
required: false
schema:
type: string
description: Filter by area slug (e.g. "providers", "models", "cli-serve")
responses:
"200":
description: Catalog list
content:
application/json:
schema:
type: object
required: [skills, count, coverage]
properties:
skills:
type: array
items:
$ref: "#/components/schemas/AgentSkill"
count:
type: integer
coverage:
$ref: "#/components/schemas/SkillCoverage"
"400":
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/{id}:
get:
tags: [Agent Skills]
summary: Get a single agent skill
description: |
Returns metadata for a single agent skill by its canonical ID
(e.g. `omni-providers`, `cli-serve`). No authentication required.
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID
example: omni-providers
responses:
"200":
description: Agent skill metadata
content:
application/json:
schema:
$ref: "#/components/schemas/AgentSkill"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/{id}/raw:
get:
tags: [Agent Skills]
summary: Get raw SKILL.md content
description: |
Returns the SKILL.md content for a skill as `text/markdown`.
Resolution order: local filesystem `skills/{id}/SKILL.md` → GitHub raw URL (1-hour cache).
No authentication required.
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID
example: omni-providers
responses:
"200":
description: SKILL.md content as Markdown
headers:
X-Skill-Source:
schema:
type: string
enum: [filesystem, github, generated]
description: Where the content was loaded from
X-Skill-Fetched-At:
schema:
type: string
format: date-time
description: ISO timestamp of when the content was fetched
Cache-Control:
schema:
type: string
description: "public, max-age=3600"
content:
text/markdown:
schema:
type: string
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"502":
description: Upstream GitHub fetch failed
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/coverage:
get:
tags: [Agent Skills]
summary: Get SKILL.md coverage stats
description: |
Returns how many of the 22 API skills and 20 CLI skills have SKILL.md
files on the local filesystem vs the catalog totals. No authentication required.
responses:
"200":
description: Coverage stats
content:
application/json:
schema:
$ref: "#/components/schemas/SkillCoverage"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/generate:
post:
tags: [Agent Skills]
summary: Trigger SKILL.md generator
description: |
Runs the Agent Skills generator which writes `skills/{id}/SKILL.md` for
all 42 catalog entries (or a subset via `onlyIds`). Preserves
`<!-- skill:custom-start --> ... <!-- skill:custom-end -->` blocks.
**Requires management authentication.**
security:
- BearerAuth: []
- ManagementSessionAuth: []
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
dryRun:
type: boolean
default: true
description: "If true, reports what would be generated without writing files"
prune:
type: boolean
default: false
description: "If true, deletes skill directories not in the catalog"
onlyIds:
type: array
items:
type: string
description: "If provided, only regenerate these skill IDs"
responses:
"200":
description: Generator report
content:
application/json:
schema:
type: object
required: [generated, unchanged, pruned, orphansDetected, errors]
properties:
generated:
type: array
items:
type: string
description: IDs that got new/updated SKILL.md
unchanged:
type: array
items:
type: string
description: IDs whose content was already up to date
pruned:
type: array
items:
type: string
description: IDs whose directories were deleted (prune mode)
orphansDetected:
type: array
items:
type: string
description: Directories found in skills/ not in the catalog
errors:
type: array
items:
type: object
required: [id, error]
properties:
id:
type: string
error:
type: string
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
"503":
description: Generator module not available
components:
securitySchemes:
BearerAuth:
@@ -3776,6 +4006,130 @@ components:
$ref: "#/components/schemas/ValidationErrorResponse"
schemas:
AgentSkill:
type: object
description: >-
Single entry in the Agent Skills catalog. Describes one OmniRoute REST API surface
(category: api) or CLI subcommand group (category: cli) with a canonical ID and a
link to its SKILL.md documentation file.
required: [id, name, description, category, area, rawUrl, githubUrl]
properties:
id:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID (e.g. "omni-providers", "cli-serve")
example: omni-providers
name:
type: string
minLength: 1
maxLength: 100
description: Human-readable skill name
example: Provider Management
description:
type: string
minLength: 1
maxLength: 2000
description: One-paragraph description of what the skill covers
category:
type: string
enum: [api, cli]
description: "api = REST API skill; cli = CLI subcommand skill"
area:
type: string
minLength: 1
maxLength: 50
description: Functional area slug (e.g. "providers", "combos-routing", "cli-serve")
example: providers
endpoints:
type: array
items:
type: string
description: REST API endpoints (present for api-category skills only)
example: ["POST /api/providers", "GET /api/providers/:id"]
cliCommands:
type: array
items:
type: string
description: CLI subcommand names (present for cli-category skills only)
example: ["providers list", "providers test", "providers rotate"]
icon:
type: string
description: Material symbol icon name for dashboard display
isEntry:
type: boolean
description: Whether this is a recommended starting point
isNew:
type: boolean
description: Whether this skill was added in a recent release
rawUrl:
type: string
format: uri
description: GitHub raw URL of the SKILL.md file
example: "https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/omni-providers/SKILL.md"
githubUrl:
type: string
format: uri
description: GitHub blob URL for viewing the SKILL.md in the browser
example: "https://github.com/diegosouzapw/OmniRoute/blob/main/skills/omni-providers/SKILL.md"
SkillCoverage:
type: object
description: >-
Coverage statistics for the Agent Skills catalog: how many of the 22 REST API
skills and 20 CLI skills have generated SKILL.md files on the local filesystem.
required: [api, cli, totalSkills, generatedAt]
properties:
api:
type: object
required: [have, total]
properties:
have:
type: integer
minimum: 0
maximum: 22
description: Number of API skills with SKILL.md on disk
total:
type: integer
enum: [22]
description: Canonical API skill count (always 22)
cli:
type: object
required: [have, total]
properties:
have:
type: integer
minimum: 0
maximum: 20
description: Number of CLI skills with SKILL.md on disk
total:
type: integer
enum: [20]
description: Canonical CLI skill count (always 20)
totalSkills:
type: integer
minimum: 0
maximum: 42
description: Sum of api.have + cli.have
generatedAt:
type: string
format: date-time
description: ISO datetime when coverage was last computed
ErrorResponse:
type: object
description: Standard error response body
required: [error]
properties:
error:
type: object
required: [message]
properties:
message:
type: string
description: Human-readable error message (never includes stack traces)
code:
type: string
description: Machine-readable error code
# ─── AgentBridge Schemas ────────────────────────────────────────

View File

@@ -259,6 +259,12 @@ const nextConfig = {
async redirects() {
return [
// Dashboard routes
{
source: "/dashboard/skills",
destination: "/dashboard/omni-skills",
permanent: true,
},
// Architecture
{
source: "/docs/architecture",

View File

@@ -1267,6 +1267,122 @@ export const oneproxyStatsTool: McpToolDefinition<
sourceEndpoints: ["/api/settings/oneproxy"],
};
// ============ Agent Skills Tools ============
// --- omniroute_agent_skills_list ---
export const agentSkillsListInput = z.object({
category: z.enum(["api", "cli"]).optional().describe("Filter by category: 'api' or 'cli'"),
area: z.string().optional().describe("Filter by area (e.g. 'providers', 'models', 'cli-serve')"),
});
export const agentSkillsListOutput = z.object({
skills: z.array(
z.object({
id: z.string(),
name: z.string(),
description: z.string(),
category: z.enum(["api", "cli"]),
area: z.string(),
endpoints: z.array(z.string()).optional(),
cliCommands: z.array(z.string()).optional(),
icon: z.string().optional(),
isEntry: z.boolean().optional(),
isNew: z.boolean().optional(),
rawUrl: z.string(),
githubUrl: z.string(),
})
),
count: z.number(),
coverage: z.object({
api: z.object({ have: z.number(), total: z.literal(22) }),
cli: z.object({ have: z.number(), total: z.literal(20) }),
totalSkills: z.number(),
generatedAt: z.string(),
}),
});
export const agentSkillsListTool: McpToolDefinition<
typeof agentSkillsListInput,
typeof agentSkillsListOutput
> = {
name: "omniroute_agent_skills_list",
description:
"List OmniRoute agent skills with optional filtering by category (api/cli) or area. Returns skill metadata including id, name, description, endpoints/commands, and URLs.",
inputSchema: agentSkillsListInput,
outputSchema: agentSkillsListOutput,
scopes: ["read:catalog"],
auditLevel: "none",
phase: 2,
sourceEndpoints: ["/api/agent-skills"],
};
// --- omniroute_agent_skills_get ---
export const agentSkillsGetInput = z.object({
id: z.string().describe("Canonical skill ID (e.g. 'omni-providers', 'cli-serve')"),
});
export const agentSkillsGetOutput = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
category: z.enum(["api", "cli"]),
area: z.string(),
endpoints: z.array(z.string()).optional(),
cliCommands: z.array(z.string()).optional(),
icon: z.string().optional(),
isEntry: z.boolean().optional(),
isNew: z.boolean().optional(),
rawUrl: z.string(),
githubUrl: z.string(),
markdown: z.object({
id: z.string(),
frontmatter: z.object({ name: z.string(), description: z.string() }),
body: z.string(),
source: z.enum(["filesystem", "github", "generated"]),
fetchedAt: z.string(),
}),
});
export const agentSkillsGetTool: McpToolDefinition<
typeof agentSkillsGetInput,
typeof agentSkillsGetOutput
> = {
name: "omniroute_agent_skills_get",
description:
"Get detailed metadata and SKILL.md markdown for a single agent skill by its canonical ID. Returns all skill fields plus the raw markdown content.",
inputSchema: agentSkillsGetInput,
outputSchema: agentSkillsGetOutput,
scopes: ["read:catalog"],
auditLevel: "none",
phase: 2,
sourceEndpoints: ["/api/agent-skills/:id", "/api/agent-skills/:id/raw"],
};
// --- omniroute_agent_skills_coverage ---
export const agentSkillsCoverageInput = z.object({}).describe("No parameters required");
export const agentSkillsCoverageOutput = z.object({
api: z.object({ have: z.number(), total: z.literal(22) }),
cli: z.object({ have: z.number(), total: z.literal(20) }),
totalSkills: z.number(),
generatedAt: z.string(),
});
export const agentSkillsCoverageTool: McpToolDefinition<
typeof agentSkillsCoverageInput,
typeof agentSkillsCoverageOutput
> = {
name: "omniroute_agent_skills_coverage",
description:
"Returns the current SKILL.md coverage stats: how many of the 22 API skills and 20 CLI skills have generated SKILL.md files on the filesystem vs the catalog total.",
inputSchema: agentSkillsCoverageInput,
outputSchema: agentSkillsCoverageOutput,
scopes: ["read:catalog"],
auditLevel: "none",
phase: 2,
sourceEndpoints: ["/api/agent-skills"],
};
// ============ Tool Registry ============
/** All MCP tool definitions, ordered by phase then name */
@@ -1301,6 +1417,9 @@ export const MCP_TOOLS = [
oneproxyFetchTool,
oneproxyRotateTool,
oneproxyStatsTool,
agentSkillsListTool,
agentSkillsGetTool,
agentSkillsCoverageTool,
] as const;
/** Essential tools only (Phase 1) */

View File

@@ -75,6 +75,7 @@ import {
} from "./tools/advancedTools.ts";
import { memoryTools } from "./tools/memoryTools.ts";
import { skillTools } from "./tools/skillTools.ts";
import { agentSkillTools } from "./tools/agentSkillTools.ts";
import { pluginTools } from "./tools/pluginTools.ts";
import { compressionTools } from "./tools/compressionTools.ts";
import { gamificationTools } from "./tools/gamificationTools.ts";
@@ -103,6 +104,7 @@ const TOTAL_MCP_TOOL_COUNT =
MCP_TOOLS.length +
Object.keys(memoryTools).length +
Object.keys(skillTools).length +
Object.keys(agentSkillTools).length +
gamificationTools.length +
pluginTools.length;
@@ -1005,6 +1007,29 @@ export function createMcpServer(): McpServer {
);
});
// ── Agent Skill Tools ─────────────────────────
Object.values(agentSkillTools).forEach((toolDef) => {
server.registerTool(
toolDef.name,
{
description: toolDef.description,
// @ts-ignore: dynamic zod access
inputSchema: toolDef.inputSchema,
},
withScopeEnforcement(toolDef.name, async (args) => {
try {
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
// @ts-expect-error - handler type lost through dynamic Object.values() access
const result = await toolDef.handler(parsedArgs);
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
}
})
);
});
// ── Plugin Tools ──────────────────────────────
pluginTools.forEach((toolDef) => {
server.registerTool(

View File

@@ -0,0 +1,83 @@
import { z } from "zod";
import { getCatalog, getSkillById, filterCatalog, computeCoverage, fetchSkillMarkdown } from "@/lib/agentSkills/catalog";
import type { AgentSkill, SkillCoverage } from "@/lib/agentSkills/types";
// ── Input Schemas ────────────────────────────────────────────────────────────
export const AgentSkillsListSchema = z.object({
category: z.enum(["api", "cli"]).optional().describe("Filter by category: 'api' or 'cli'"),
area: z.string().optional().describe("Filter by area (e.g. 'providers', 'models', 'cli-serve')"),
});
export const AgentSkillsGetSchema = z.object({
id: z.string().describe("Canonical skill ID (e.g. 'omni-providers', 'cli-serve')"),
});
export const AgentSkillsCoverageSchema = z.object({});
// ── Tool Definitions ─────────────────────────────────────────────────────────
export const agentSkillTools = {
omniroute_agent_skills_list: {
name: "omniroute_agent_skills_list",
description:
"List OmniRoute agent skills with optional filtering by category (api/cli) or area. Returns skill metadata including id, name, description, endpoints/commands, and URLs.",
inputSchema: AgentSkillsListSchema,
handler: async (args: z.infer<typeof AgentSkillsListSchema>) => {
const skills: AgentSkill[] =
args.category || args.area
? filterCatalog({ category: args.category, area: args.area })
: getCatalog();
return {
skills: skills.map((s) => ({
id: s.id,
name: s.name,
description: s.description,
category: s.category,
area: s.area,
endpoints: s.endpoints,
cliCommands: s.cliCommands,
icon: s.icon,
isEntry: s.isEntry,
isNew: s.isNew,
rawUrl: s.rawUrl,
githubUrl: s.githubUrl,
})),
count: skills.length,
coverage: computeCoverage(),
};
},
},
omniroute_agent_skills_get: {
name: "omniroute_agent_skills_get",
description:
"Get detailed metadata and SKILL.md markdown for a single agent skill by its canonical ID. Returns all skill fields plus the raw markdown content.",
inputSchema: AgentSkillsGetSchema,
handler: async (args: z.infer<typeof AgentSkillsGetSchema>) => {
const skill: AgentSkill | null = getSkillById(args.id);
if (!skill) {
throw new Error(`Skill not found: ${args.id}`);
}
const markdown = await fetchSkillMarkdown(args.id);
return {
...skill,
markdown,
};
},
},
omniroute_agent_skills_coverage: {
name: "omniroute_agent_skills_coverage",
description:
"Returns the current SKILL.md coverage stats: how many of the 22 API skills and 20 CLI skills have generated SKILL.md files on the filesystem vs the catalog total.",
inputSchema: AgentSkillsCoverageSchema,
handler: async (_args: z.infer<typeof AgentSkillsCoverageSchema>) => {
const coverage: SkillCoverage = computeCoverage();
return coverage;
},
},
};

109
package-lock.json generated
View File

@@ -35,6 +35,7 @@
"fumadocs-core": "^16.9.0",
"fumadocs-mdx": "^15.0.7",
"fumadocs-ui": "^16.9.0",
"gray-matter": "^4.0.3",
"http-proxy-middleware": "^4.0.0",
"https-proxy-agent": "^9.0.0",
"ink": "^7.0.3",
@@ -10631,6 +10632,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esquery": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
@@ -10903,6 +10917,18 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"license": "MIT",
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fast-copy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz",
@@ -11860,6 +11886,43 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"license": "MIT",
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/gray-matter/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/gray-matter/node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/hachure-fill": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
@@ -13022,6 +13085,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -13873,6 +13945,15 @@
"resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/koffi": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz",
@@ -18291,6 +18372,19 @@
"compute-scroll-into-view": "^3.0.2"
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"license": "MIT",
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/secure-json-parse": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
@@ -18791,6 +18885,12 @@
"node": ">= 10.x"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/sql.js": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz",
@@ -19098,6 +19198,15 @@
"node": ">=4"
}
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",

View File

@@ -159,6 +159,7 @@
"fumadocs-core": "^16.9.0",
"fumadocs-mdx": "^15.0.7",
"fumadocs-ui": "^16.9.0",
"gray-matter": "^4.0.3",
"http-proxy-middleware": "^4.0.0",
"https-proxy-agent": "^9.0.0",
"ink": "^7.0.3",
@@ -217,6 +218,7 @@
"@testing-library/react": "^16.3.2",
"@types/bcryptjs": "^3.0.0",
"@types/better-sqlite3": "^7.6.13",
"@types/bun": "latest",
"@types/keytar": "^4.4.2",
"@types/node": "^25.9.1",
"@types/react": "^19.2.15",
@@ -239,8 +241,7 @@
"typescript-eslint": "^8.59.4",
"vitest": "^4.1.7",
"wait-on": "^9.0.10",
"wtfnode": "^0.10.1",
"@types/bun": "latest"
"wtfnode": "^0.10.1"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,mjs}": [

View File

@@ -0,0 +1,183 @@
#!/usr/bin/env node
/**
* generate-agent-skills.mjs — CLI wrapper for src/lib/agentSkills/generator.ts
*
* Usage:
* node scripts/skills/generate-agent-skills.mjs # dry-run (default)
* node scripts/skills/generate-agent-skills.mjs --apply # write SKILL.md files
* node scripts/skills/generate-agent-skills.mjs --prune # detect orphans (dry-run)
* node scripts/skills/generate-agent-skills.mjs --apply --prune # write + delete orphans
* node scripts/skills/generate-agent-skills.mjs --only=omni-providers,cli-serve
* node scripts/skills/generate-agent-skills.mjs --json # JSON output to stdout
*
* Exit codes:
* 0 — success (dry-run with no changes, or apply completed)
* 1 — error (import or generator threw)
* 2 — dry-run detected changes (use for CI fail-on-stale check)
*
* Security: no shell interpolation of user input (Hard Rule #13).
* All runtime values are passed as JS variables, not shell strings.
*/
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import path from "node:path";
import process from "node:process";
// ── Resolve project root from script location ─────────────────────────────────
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// scripts/skills/generate-agent-skills.mjs → up 2 levels → project root
const projectRoot = path.resolve(__dirname, "..", "..");
// ── Argument parsing ──────────────────────────────────────────────────────────
const args = process.argv.slice(2);
function hasFlag(flag) {
return args.some((a) => a === flag || a.startsWith(flag + "="));
}
function getFlagValue(flag) {
for (const arg of args) {
if (arg.startsWith(flag + "=")) return arg.slice(flag.length + 1);
}
return null;
}
const applyMode = hasFlag("--apply");
const pruneMode = hasFlag("--prune");
const jsonOutput = hasFlag("--json");
const onlyRaw = getFlagValue("--only");
const onlyIds = onlyRaw ? onlyRaw.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
// ── Utility: print table ──────────────────────────────────────────────────────
function printTable(report) {
const { generated, unchanged, pruned, orphansDetected, errors } = report;
console.log(`\nGenerated: ${generated.length} · Unchanged: ${unchanged.length} · Pruned: ${pruned.length} · Orphans: ${orphansDetected.length} · Errors: ${errors.length}\n`);
if (generated.length > 0) {
console.log(" GENERATED:");
for (const id of generated) {
console.log(` + ${id}`);
}
}
if (unchanged.length > 0 && unchanged.length <= 10) {
console.log(" UNCHANGED:");
for (const id of unchanged) {
console.log(` = ${id}`);
}
} else if (unchanged.length > 10) {
console.log(` UNCHANGED: ${unchanged.length} skills (all up-to-date)`);
}
if (orphansDetected.length > 0) {
console.log(" ORPHANS DETECTED:");
for (const id of orphansDetected) {
console.log(` ? ${id}`);
}
}
if (pruned.length > 0) {
console.log(" PRUNED:");
for (const id of pruned) {
console.log(` - ${id}`);
}
}
if (errors.length > 0) {
console.log(" ERRORS:");
for (const e of errors) {
console.log(` ! ${e.id}: ${e.error}`);
}
}
}
// ── Main ─────────────────────────────────────────────────────────────────────
async function main() {
// Change CWD to project root so generator resolves files correctly
process.chdir(projectRoot);
if (!jsonOutput) {
const mode = applyMode ? "apply" : "dry-run";
const prune = pruneMode ? " + prune" : "";
const filter = onlyIds ? ` (only: ${onlyIds.join(", ")})` : "";
console.log(`\nAgent Skills Generator [${mode}${prune}]${filter}`);
console.log("─".repeat(60));
}
// Dynamic import via tsx runtime — works because package.json has tsx devDep
// and node is invoked with --import tsx/esm by the caller (or we use tsx directly).
// To support plain `node` invocation, we use a dynamic import with tsx register.
let generateAgentSkills;
try {
// Try direct import first (when running under tsx or compiled)
const mod = await import("../../src/lib/agentSkills/generator.ts");
generateAgentSkills = mod.generateAgentSkills;
} catch (importErr) {
// Fallback: try tsx register approach
try {
const require = createRequire(import.meta.url);
// Register tsx for TypeScript support
const tsxPath = require.resolve("tsx/esm");
const { register } = await import("node:module");
register(tsxPath, import.meta.url);
const mod = await import("../../src/lib/agentSkills/generator.ts");
generateAgentSkills = mod.generateAgentSkills;
} catch (fallbackErr) {
console.error(
"Error: Could not import generator. Run with tsx:\n" +
" node --import tsx/esm scripts/skills/generate-agent-skills.mjs\n" +
`Import error: ${importErr instanceof Error ? importErr.message : String(importErr)}`,
);
process.exit(1);
}
}
let report;
try {
report = await generateAgentSkills({
dryRun: !applyMode,
prune: pruneMode,
outputDir: "skills",
onlyIds,
});
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (jsonOutput) {
console.log(JSON.stringify({ error: msg }, null, 2));
} else {
console.error(`\nGenerator error: ${msg}`);
}
process.exit(1);
}
if (jsonOutput) {
console.log(JSON.stringify(report, null, 2));
} else {
printTable(report);
if (!applyMode) {
console.log(
"\n(dry-run) No files written. Use --apply to write SKILL.md files.\n",
);
} else {
console.log();
}
}
// Exit code 2 if dry-run detected pending changes (useful for CI)
if (!applyMode && report.generated.length > 0) {
process.exit(2);
}
process.exit(0);
}
main().catch((err) => {
console.error("Unexpected error:", err instanceof Error ? err.message : String(err));
process.exit(1);
});

View File

@@ -1,40 +1,96 @@
# OmniRoute AI Agent Skills
# OmniRoute Agent Skills
Drop-in skills that let any AI agent (Claude Desktop, ChatGPT, Cursor, Cline, Continue, etc.)
consume OmniRoute via OpenAI-compatible REST in one fetch.
## How agents use these
## Entry points
| Type | Skill | Manifest |
| ---- | ----- | -------- |
| API | Authentication (start here for REST access) | [omni-auth/SKILL.md](omni-auth/SKILL.md) |
| CLI | Serve (start here for CLI access) | [cli-serve/SKILL.md](cli-serve/SKILL.md) |
## How agents discover capabilities
- **MCP tool**: `omniroute_agent_skills_list` (scope `read:catalog`) — returns the full 42-skill catalog in one call.
- **A2A skill**: `list-capabilities` — JSON-RPC 2.0 endpoint that returns the agent card with all registered skills.
See [`docs/frameworks/AGENT-SKILLS.md`](../docs/frameworks/AGENT-SKILLS.md) for the full framework reference.
---
## API Skills (22)
Each manifest URL follows the pattern:
`https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/<id>/SKILL.md`
| ID | Name | Description |
| -- | ---- | ----------- |
| `omni-auth` | Authentication | Manage API key authentication and session tokens. Start here to authenticate requests via Bearer token, obtain session cookies, and configure login requirements. |
| `omni-providers` | Providers | Manage provider connections, API keys, OAuth flows, and connection tests. List, add, update, remove, and test AI provider integrations (OpenAI, Anthropic, Gemini, and 160+). |
| `omni-models` | Models | Query available AI models across all configured providers. List models, resolve model aliases, and browse the full model catalog including provider-specific variants. |
| `omni-combos-routing` | Combos & Routing | Create and manage routing combos with 14 strategies (priority, weighted, round-robin, Auto-combo, etc.). Configure fallback chains, test routing outcomes, and retrieve combo metrics. |
| `omni-api-keys` | API Keys | Create, list, rotate, and revoke OmniRoute API keys. Control per-key scopes, spending limits, and expiration. |
| `omni-usage-logs` | Usage & Logs | Access detailed call logs and usage analytics. Filter by provider, model, time range, status, and cost. Export logs and aggregate token usage. |
| `omni-budget` | Budget & Rate Limits | Configure spending limits, token quotas, and rate-limit policies per API key or globally. Inspect current consumption and enforce cost controls. |
| `omni-settings` | Settings | Read and update global application settings: system prompts, thinking budget, IP filters, payload rules, combo defaults, and require-login configuration. |
| `omni-proxies` | Proxy Configuration | Configure HTTP/HTTPS/SOCKS proxies for upstream provider requests. Set per-provider or global proxy rules, test connectivity, and manage proxy rotation. |
| `omni-cache` | Cache | Manage the LLM response cache. View cache statistics, clear entries, configure TTL policies, and control semantic-similarity caching thresholds. |
| `omni-compression` | Compression | Configure RTK, Caveman, and stacked compression modes. Manage language packs, custom rules, and test prompt compression reducing tokens by 6090%. |
| `omni-context-rtk` | Context & RTK | Configure RTK filters, context engineering rules, and context relay settings. Test compression with real prompt samples and manage context transformation pipelines. |
| `omni-resilience` | Resilience & Monitoring | Monitor provider health, circuit-breaker states, p50/p95/p99 latency metrics, and budget guard alerts. Inspect connection cooldowns and model lockouts in real time. |
| `omni-cli-tools` | CLI Tools | Manage CLI tool integrations exposed via the API. List, configure, and invoke CLI tool plugins that extend OmniRoute's automation surface. |
| `omni-tunnels` | Tunnels | Create and manage secure tunnels (ngrok, Cloudflare Tunnel, custom) to expose OmniRoute to the internet or share access with remote agents and CI pipelines. |
| `omni-sync-cloud` | Cloud Sync | Synchronise OmniRoute configuration, provider connections, and settings to/from cloud storage. Manage cloud worker authentication and remote backup targets. |
| `omni-db-backups` | Database & Backups | Trigger system backups, restore from backup files, and manage the SQLite database lifecycle. Supports export, import, and incremental snapshot strategies. |
| `omni-webhooks` | Webhooks | Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, provider.error, budget.exceeded, etc.) and manage delivery retries. |
| `omni-mcp` | MCP Server | Connect to the OmniRoute MCP server (37 tools, 3 transports: SSE/stdio/HTTP). Covers routing, cache, compression, memory, skills, providers, and audit tools across 16 permission scopes. |
| `omni-agents-a2a` | Agents & A2A Protocol | Interact with OmniRoute via JSON-RPC 2.0 agent-to-agent protocol. 6 built-in A2A skills: smart-routing, quota-management, provider-discovery, cost-analysis, health-report, list-capabilities. |
| `omni-version-manager` | Version Manager | Install, start, stop, restart, and update embedded services (9Router, CLIProxyAPI). Monitor service status, retrieve logs, and configure auto-start. |
| `omni-inference` | Inference (OpenAI-compatible) | The core OpenAI-compatible inference endpoints: chat completions, embeddings, images, audio (TTS/STT), moderations, rerank, and the Responses API. |
---
## CLI Skills (20)
| ID | Name | Description |
| -- | ---- | ----------- |
| `cli-serve` | CLI: Serve | Start, stop, and restart the OmniRoute server from the CLI. Manage daemon mode, port configuration, auto-recovery, system tray integration, and the dashboard open shortcut. |
| `cli-health` | CLI: Health | Check server health, component status, and live metrics from the CLI. Run `health`, `health components`, and `health watch` for a real-time dashboard of circuit breakers and provider status. |
| `cli-providers` | CLI: Providers | Manage provider connections from the CLI: list available/configured providers, add, test, test-all, validate, rotate API keys, and view per-provider metrics. |
| `cli-keys` | CLI: API Keys | Create, list, rotate, and revoke OmniRoute API keys from the CLI. Manage OAuth flows for provider authentication and inspect key scopes and expiration. |
| `cli-models` | CLI: Models | Query available AI models, list model aliases, and browse the full model catalog from the CLI. Filter by provider, search by capability, and resolve model name variants. |
| `cli-chat` | CLI: Chat | Send chat completions, stream responses, and start an interactive REPL session from the CLI. Supports all OmniRoute providers, combo routing, and system prompt configuration. |
| `cli-routing` | CLI: Routing & Combos | Create, list, update, and delete routing combos from the CLI. Test routing strategies, inspect combo metrics, and configure fallback chains interactively. |
| `cli-resilience` | CLI: Resilience & Quotas | Inspect and manage circuit-breaker states, connection cooldowns, quota limits, and backoff levels from the CLI. Reset stuck providers and configure resilience thresholds. |
| `cli-compression` | CLI: Compression | Configure and test prompt compression from the CLI. Manage RTK filters, Caveman rules, stacked compression modes, and preview compression output with real prompts. |
| `cli-contexts` | CLI: Contexts & Sessions | Manage context engineering configurations, RTK filter sets, and conversation sessions from the CLI. Apply context-relay settings and inspect active context pipelines. |
| `cli-cost-usage` | CLI: Cost & Usage | View cost breakdowns, token usage, and call logs from the CLI. Filter by provider, model, or date range. Export usage reports and inspect per-connection spending. |
| `cli-mcp` | CLI: MCP | Inspect the MCP server status, list registered tools and scopes, run tool invocations, and manage MCP audit logs from the CLI. |
| `cli-a2a` | CLI: A2A Protocol | Interact with the OmniRoute A2A server from the CLI. Send tasks, inspect skill execution history, and test the JSON-RPC 2.0 agent-to-agent protocol interactively. |
| `cli-tunnel` | CLI: Tunnels | Start and stop tunnel connections (ngrok, Cloudflare, custom) from the CLI. Inspect active tunnel URLs, configure authentication, and test external reachability. |
| `cli-backup-sync` | CLI: Backup & Sync | Backup and restore OmniRoute data from the CLI. Trigger incremental snapshots, sync to cloud storage, manage backup schedules, and restore from archive files. |
| `cli-policy-audit` | CLI: Policy & Audit | Inspect audit logs, manage access policies, view telemetry data, and review request history from the CLI. Filter by event type, user, or time range for compliance workflows. |
| `cli-batches` | CLI: Batches & Files | Submit and monitor batch inference jobs from the CLI. Upload and manage files for batch processing, retrieve results, and integrate batch pipelines with CI/CD workflows. |
| `cli-eval` | CLI: Evals | Create and run evaluation suites, watch live benchmark progress, view scorecards, compare model performance, and integrate eval runs with CI workflows from the CLI. |
| `cli-plugins-skills` | CLI: Plugins, Skills & Memory | Manage Omni Skills (list, install, test, remove), plugins (create, configure), and persistent memory (search, add, clear) from the CLI. |
| `cli-setup` | CLI: Setup & Config | Run initial setup, configure global CLI settings, manage environment variables, check for updates, and configure autostart via the CLI setup and config commands. |
---
## Raw manifest URLs
All manifests are publicly accessible:
```
User to agent: "Use OmniRoute for code-gen. Fetch this URL and follow it:
https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md"
https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/<id>/SKILL.md
```
The agent retrieves the manifest, sees the setup + endpoints, and routes calls
through `$OMNIROUTE_URL/v1/...` with `Authorization: Bearer $OMNIROUTE_KEY`.
Examples:
- API entry: `https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omni-auth/SKILL.md`
- CLI entry: `https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/cli-serve/SKILL.md`
## Skills index
| Capability | Manifest |
| ------------------------ | -------------------------------------------------------------------- |
| Entry point + setup | [omniroute/SKILL.md](omniroute/SKILL.md) |
| Chat / code-gen | [omniroute-chat/SKILL.md](omniroute-chat/SKILL.md) |
| Image generation | [omniroute-image/SKILL.md](omniroute-image/SKILL.md) |
| Text-to-speech | [omniroute-tts/SKILL.md](omniroute-tts/SKILL.md) |
| Speech-to-text | [omniroute-stt/SKILL.md](omniroute-stt/SKILL.md) |
| Embeddings | [omniroute-embeddings/SKILL.md](omniroute-embeddings/SKILL.md) |
| Web search | [omniroute-web-search/SKILL.md](omniroute-web-search/SKILL.md) |
| Web fetch (URL→markdown) | [omniroute-web-fetch/SKILL.md](omniroute-web-fetch/SKILL.md) |
| MCP server (37 tools) | [omniroute-mcp/SKILL.md](omniroute-mcp/SKILL.md) |
| A2A protocol | [omniroute-a2a/SKILL.md](omniroute-a2a/SKILL.md) |
| Routing & combos | [omniroute-routing/SKILL.md](omniroute-routing/SKILL.md) |
| Token compression | [omniroute-compression/SKILL.md](omniroute-compression/SKILL.md) |
| Monitoring & health | [omniroute-monitoring/SKILL.md](omniroute-monitoring/SKILL.md) |
| CLI entry point | [omniroute-cli/SKILL.md](omniroute-cli/SKILL.md) |
| CLI admin & lifecycle | [omniroute-cli-admin/SKILL.md](omniroute-cli-admin/SKILL.md) |
| CLI providers & keys | [omniroute-cli-providers/SKILL.md](omniroute-cli-providers/SKILL.md) |
| CLI cloud agents | [omniroute-cli-cloud/SKILL.md](omniroute-cli-cloud/SKILL.md) |
| CLI evals & benchmarks | [omniroute-cli-eval/SKILL.md](omniroute-cli-eval/SKILL.md) |
---
## Format
@@ -42,12 +98,12 @@ Each `SKILL.md` follows the Anthropic skill manifest spec with YAML frontmatter
(`name`, `description`) and a self-contained markdown body: setup, endpoints,
examples, and error codes. Assume the reader is an agent with no prior context.
## Skills exclusive to OmniRoute
---
These 5 skills have no equivalent in other AI routers:
## What makes OmniRoute skills unique
- `omniroute-mcp` — 37 MCP tools (memory, skills, providers, routing, compression) over SSE/stdio/HTTP
- `omniroute-a2a`5 A2A skills (smart-routing, quota, discovery, cost, health) via JSON-RPC 2.0
- `omniroute-routing` — create/configure combos, 14 strategies, Auto-combo scoring, fallback chains
- `omniroute-compression` — RTK + Caveman + stacked mode + MCP accessibility filter (6090% token savings)
- `omniroute-monitoring` — circuit breakers, p50/p95/p99 latency, budget guard, MCP audit log
- `omni-mcp` — 37 MCP tools (memory, skills, providers, routing, compression) over SSE/stdio/HTTP
- `omni-agents-a2a`6 A2A skills (smart-routing, quota, discovery, cost, health, list-capabilities) via JSON-RPC 2.0
- `omni-combos-routing` — create/configure combos, 14 strategies, Auto-combo scoring, fallback chains
- `omni-compression` — RTK + Caveman + stacked mode + MCP accessibility filter (6090% token savings)
- `omni-resilience` — circuit breakers, p50/p95/p99 latency, budget guard, MCP audit log

142
skills/cli-a2a/SKILL.md Normal file
View File

@@ -0,0 +1,142 @@
---
name: cli-a2a
description: Interact with the OmniRoute A2A server from the CLI. Send tasks, inspect skill execution history, and test the JSON-RPC 2.0 agent-to-agent protocol interactively.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Interact with the OmniRoute A2A server from the CLI. Send tasks, inspect skill execution history, and test the JSON-RPC 2.0 agent-to-agent protocol interactively.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `a2a`
Agent-to-Agent (A2A) server
**Example:**
```bash
omniroute a2a
```
### `a2a status`
Show A2A server status
**Flags:**
- `--json`
**Example:**
```bash
omniroute a2a status
```
### `a2a card`
Print the Agent Card JSON
**Example:**
```bash
omniroute a2a card
```
### `a2a skills`
**Example:**
```bash
omniroute a2a skills
```
### `a2a invoke <skill>`
**Flags:**
- `--input <json>`
- `--input-file <path>`
- `--wait`
- `--timeout <ms>`
**Example:**
```bash
omniroute a2a invoke <skill>
```
### `a2a tasks`
**Example:**
```bash
omniroute a2a tasks
```
### `a2a list`
**Flags:**
- `--status <s>`
- `--skill <s>`
- `--limit <n>`
- `--since <ts>`
**Example:**
```bash
omniroute a2a list
```
### `a2a get <id>`
**Example:**
```bash
omniroute a2a get <id>
```
### `a2a cancel <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute a2a cancel <id>
```
### `a2a watch <id>`
**Example:**
```bash
omniroute a2a watch <id>
```
### `a2a stream <id>`
**Example:**
```bash
omniroute a2a stream <id>
```
### `a2a logs <id>`
**Example:**
```bash
omniroute a2a logs <id>
```

View File

@@ -0,0 +1,367 @@
---
name: cli-backup-sync
description: Backup and restore OmniRoute data from the CLI. Trigger incremental snapshots, sync to cloud storage, manage backup schedules, and restore from archive files.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Backup and restore OmniRoute data from the CLI. Trigger incremental snapshots, sync to cloud storage, manage backup schedules, and restore from archive files.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `backup`
**Example:**
```bash
omniroute backup
```
### `backup create`
**Flags:**
- `--name <name>`
- `--cloud`
- `--encrypt`
- `--key-file <path>`
- `--exclude <pattern>`
- `--retention <n>`
**Example:**
```bash
omniroute backup create
```
### `backup auto`
**Example:**
```bash
omniroute backup auto
```
### `backup enable`
**Flags:**
- `--cron <expr>`
- `--cloud`
- `--encrypt`
- `--retention <n>`
**Example:**
```bash
omniroute backup enable
```
### `backup disable`
**Example:**
```bash
omniroute backup disable
```
### `backup status`
**Flags:**
- `--name <name>`
- `--cloud`
- `--encrypt`
- `--key-file <path>`
- `--exclude <pattern>`
- `--retention <n>`
**Example:**
```bash
omniroute backup status
```
### `backup restore [backupId]`
**Flags:**
- `--list`
- `--yes`
**Example:**
```bash
omniroute backup restore [backupId]
```
### `task`
**Example:**
```bash
omniroute task
```
### `cloud create`
**Flags:**
- `--title <t>`
- `--prompt <p>`
- `--prompt-file <path>`
- `--repo <url>`
- `--branch <b>`
- `--metadata <json>`
**Example:**
```bash
omniroute cloud create
```
### `cloud list`
**Flags:**
- `--status <s>`
- `--limit <n>`
**Example:**
```bash
omniroute cloud list
```
### `cloud get <taskId>`
**Example:**
```bash
omniroute cloud get <taskId>
```
### `cloud status <taskId>`
**Example:**
```bash
omniroute cloud status <taskId>
```
### `cloud cancel <taskId>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute cloud cancel <taskId>
```
### `cloud approve <taskId>`
**Example:**
```bash
omniroute cloud approve <taskId>
```
### `cloud message <taskId> <message>`
**Example:**
```bash
omniroute cloud message <taskId> <message>
```
### `cloud sources <taskId>`
**Example:**
```bash
omniroute cloud sources <taskId>
```
### `cloud cloud`
**Example:**
```bash
omniroute cloud cloud
```
### `cloud agents`
**Example:**
```bash
omniroute cloud agents
```
### `cloud auth`
**Flags:**
- `--no-browser`
- `--timeout <ms>`
**Example:**
```bash
omniroute cloud auth
```
### `sync`
**Example:**
```bash
omniroute sync
```
### `sync push`
**Flags:**
- `--target <t>`
- `--bundle <list>`
- `--dry-run`
**Example:**
```bash
omniroute sync push
```
### `sync pull`
**Flags:**
- `--source <s>`
- `--merge`
- `--replace`
- `--dry-run`
**Example:**
```bash
omniroute sync pull
```
### `sync diff`
**Flags:**
- `--source <s>`
- `--target <t>`
**Example:**
```bash
omniroute sync diff
```
### `sync bundle <outPath>`
**Flags:**
- `--include <list>`
**Example:**
```bash
omniroute sync bundle <outPath>
```
### `sync import <bundlePath>`
**Flags:**
- `--dry-run`
**Example:**
```bash
omniroute sync import <bundlePath>
```
### `sync initialize`
**Flags:**
- `--from-cloud`
**Example:**
```bash
omniroute sync initialize
```
### `sync tokens`
**Example:**
```bash
omniroute sync tokens
```
### `sync list`
**Example:**
```bash
omniroute sync list
```
### `sync create`
**Flags:**
- `--name <n>`
- `--scope <s>`
- `--ttl <duration>`
**Example:**
```bash
omniroute sync create
```
### `sync revoke <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute sync revoke <id>
```
### `sync status`
**Example:**
```bash
omniroute sync status
```
### `sync resolve`
**Example:**
```bash
omniroute sync resolve
```

183
skills/cli-batches/SKILL.md Normal file
View File

@@ -0,0 +1,183 @@
---
name: cli-batches
description: Submit and monitor batch inference jobs from the CLI. Upload and manage files for batch processing, retrieve results, and integrate batch pipelines with CI/CD workflows.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Submit and monitor batch inference jobs from the CLI. Upload and manage files for batch processing, retrieve results, and integrate batch pipelines with CI/CD workflows.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `batches`
**Example:**
```bash
omniroute batches
```
### `batches list`
**Flags:**
- `--status <s>`
- `--limit <n>`
**Example:**
```bash
omniroute batches list
```
### `batches get <batchId>`
**Example:**
```bash
omniroute batches get <batchId>
```
### `batches create`
**Flags:**
- `--endpoint <e>`
- `--completion-window <w>`
- `--metadata <kv>`
**Example:**
```bash
omniroute batches create
```
### `batches submit`
**Flags:**
- `--endpoint <e>`
- `--wait`
**Example:**
```bash
omniroute batches submit
```
### `batches cancel <batchId>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute batches cancel <batchId>
```
### `batches wait <batchId>`
**Flags:**
- `--timeout <ms>`
**Example:**
```bash
omniroute batches wait <batchId>
```
### `batches output <batchId>`
**Flags:**
- `--out <path>`
**Example:**
```bash
omniroute batches output <batchId>
```
### `batches errors <batchId>`
**Flags:**
- `--out <path>`
**Example:**
```bash
omniroute batches errors <batchId>
```
### `files`
**Example:**
```bash
omniroute files
```
### `files list`
**Flags:**
- `--purpose <p>`
- `--limit <n>`
**Example:**
```bash
omniroute files list
```
### `files get <fileId>`
**Example:**
```bash
omniroute files get <fileId>
```
### `files upload <path>`
**Example:**
```bash
omniroute files upload <path>
```
### `files content <fileId>`
**Flags:**
- `--out <path>`
**Example:**
```bash
omniroute files content <fileId>
```
### `files delete <fileId>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute files delete <fileId>
```

78
skills/cli-chat/SKILL.md Normal file
View File

@@ -0,0 +1,78 @@
---
name: cli-chat
description: Send chat completions, stream responses, and start an interactive REPL session from the CLI. Supports all OmniRoute providers, combo routing, and system prompt configuration.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Send chat completions, stream responses, and start an interactive REPL session from the CLI. Supports all OmniRoute providers, combo routing, and system prompt configuration.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `chat [prompt]`
**Flags:**
- `--file <path>`
- `--stdin`
- `-s, --system <prompt>`
- `-m, --model <id>`
- `--max-tokens <n>`
- `--temperature <t>`
- `--top-p <p>`
- `--reasoning-effort <level>`
- `--thinking-budget <tokens>`
- `--combo <name>`
- `--responses-api`
- `--stream`
- `--no-history`
**Example:**
```bash
omniroute chat [prompt]
```
### `repl`
**Flags:**
- `-m, --model <id>`
- `--combo <name>`
- `-s, --system <prompt>`
- `--resume <session>`
**Example:**
```bash
omniroute repl
```
### `stream [prompt]`
**Flags:**
- `--file <path>`
- `--stdin`
- `-m, --model <id>`
- `-s, --system <prompt>`
- `--combo <name>`
- `--max-tokens <n>`
- `--responses-api`
- `--raw`
- `--debug`
- `--save <path>`
**Example:**
```bash
omniroute stream [prompt]
```

View File

@@ -0,0 +1,157 @@
---
name: cli-compression
description: Configure and test prompt compression from the CLI. Manage RTK filters, Caveman rules, stacked compression modes, and preview compression output with real prompts.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Configure and test prompt compression from the CLI. Manage RTK filters, Caveman rules, stacked compression modes, and preview compression output with real prompts.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `compression`
**Example:**
```bash
omniroute compression
```
### `compression status`
**Example:**
```bash
omniroute compression status
```
### `compression configure`
**Flags:**
- `--engine <e>`
- `--caveman-aggressiveness <n>`
- `--rtk-budget <n>`
- `--language-pack <p>`
**Example:**
```bash
omniroute compression configure
```
### `compression engine`
**Example:**
```bash
omniroute compression engine
```
### `compression set <name>`
**Example:**
```bash
omniroute compression set <name>
```
### `compression get`
**Example:**
```bash
omniroute compression get
```
### `compression combos`
**Example:**
```bash
omniroute compression combos
```
### `compression list`
**Example:**
```bash
omniroute compression list
```
### `compression stats`
**Flags:**
- `--period <p>`
**Example:**
```bash
omniroute compression stats
```
### `compression rules`
**Example:**
```bash
omniroute compression rules
```
### `compression list`
**Example:**
```bash
omniroute compression list
```
### `compression add`
**Flags:**
- `--replacement <r>`
**Example:**
```bash
omniroute compression add
```
### `compression remove <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute compression remove <id>
```
### `compression language-packs`
**Example:**
```bash
omniroute compression language-packs
```
### `compression preview`
**Example:**
```bash
omniroute compression preview
```

View File

@@ -0,0 +1,382 @@
---
name: cli-contexts
description: Manage context engineering configurations, RTK filter sets, and conversation sessions from the CLI. Apply context-relay settings and inspect active context pipelines.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Manage context engineering configurations, RTK filter sets, and conversation sessions from the CLI. Apply context-relay settings and inspect active context pipelines.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `context-eng`
**Example:**
```bash
omniroute context-eng
```
### `context-eng analytics`
**Flags:**
- `--period <p>`
**Example:**
```bash
omniroute context-eng analytics
```
### `context-eng caveman`
**Example:**
```bash
omniroute context-eng caveman
```
### `context-eng config`
**Example:**
```bash
omniroute context-eng config
```
### `context-eng show`
**Example:**
```bash
omniroute context-eng show
```
### `context-eng set`
**Flags:**
- `--aggressiveness <n>`
- `--max-shrink-pct <n>`
- `--preserve-tags <list>`
**Example:**
```bash
omniroute context-eng set
```
### `context-eng rtk`
**Example:**
```bash
omniroute context-eng rtk
```
### `context-eng config`
**Example:**
```bash
omniroute context-eng config
```
### `context-eng show`
**Example:**
```bash
omniroute context-eng show
```
### `context-eng set`
**Flags:**
- `--token-budget <n>`
- `--reserve-pct <n>`
**Example:**
```bash
omniroute context-eng set
```
### `context-eng filters`
**Example:**
```bash
omniroute context-eng filters
```
### `context-eng list`
**Example:**
```bash
omniroute context-eng list
```
### `context-eng add`
**Flags:**
- `--priority <n>`
- `--action <a>`
**Example:**
```bash
omniroute context-eng add
```
### `context-eng remove <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute context-eng remove <id>
```
### `context-eng test`
**Example:**
```bash
omniroute context-eng test
```
### `context-eng raw-output <id>`
**Example:**
```bash
omniroute context-eng raw-output <id>
```
### `context-eng combos`
**Example:**
```bash
omniroute context-eng combos
```
### `context-eng list`
**Example:**
```bash
omniroute context-eng list
```
### `context-eng get <id>`
**Example:**
```bash
omniroute context-eng get <id>
```
### `context-eng assignments <id>`
**Example:**
```bash
omniroute context-eng assignments <id>
```
### `contexts`
**Example:**
```bash
omniroute contexts
```
### `contexts list`
List all contexts
**Example:**
```bash
omniroute contexts list
```
### `contexts add <name>`
Add a new context
**Flags:**
- `--api-key <k>`
- `--api-key-stdin`
- `--description <d>`
**Example:**
```bash
omniroute contexts add <name>
```
### `contexts use <name>`
Switch active context
**Example:**
```bash
omniroute contexts use <name>
```
### `contexts current`
Show current active context name
**Example:**
```bash
omniroute contexts current
```
### `contexts show <name>`
Show context details
**Example:**
```bash
omniroute contexts show <name>
```
### `contexts remove <name>`
Remove a context
**Flags:**
- `--yes`
**Example:**
```bash
omniroute contexts remove <name>
```
### `contexts rename <old> <new>`
Rename a context
**Example:**
```bash
omniroute contexts rename <old> <new>
```
### `contexts export`
Export contexts to JSON
**Flags:**
- `--out <path>`
- `--no-secrets`
**Example:**
```bash
omniroute contexts export
```
### `contexts import <file>`
Import contexts from a JSON file
**Flags:**
- `--merge`
**Example:**
```bash
omniroute contexts import <file>
```
### `sessions`
**Example:**
```bash
omniroute sessions
```
### `sessions list`
**Flags:**
- `--user <u>`
- `--kind <k>`
- `--active`
- `--limit <n>`
**Example:**
```bash
omniroute sessions list
```
### `sessions show <sessionId>`
**Example:**
```bash
omniroute sessions show <sessionId>
```
### `sessions expire <sessionId>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute sessions expire <sessionId>
```
### `sessions expire-all`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute sessions expire-all
```
### `sessions current`
**Example:**
```bash
omniroute sessions current
```

View File

@@ -0,0 +1,252 @@
---
name: cli-cost-usage
description: View cost breakdowns, token usage, and call logs from the CLI. Filter by provider, model, or date range. Export usage reports and inspect per-connection spending.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
View cost breakdowns, token usage, and call logs from the CLI. Filter by provider, model, or date range. Export usage reports and inspect per-connection spending.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `cost`
**Flags:**
- `--period <range>`
- `--since <date>`
- `--until <date>`
- `--group-by <field>`
- `--api-key <key>`
- `--limit <n>`
**Example:**
```bash
omniroute cost
```
### `pricing`
**Example:**
```bash
omniroute pricing
```
### `pricing sync`
**Flags:**
- `--provider <p>`
- `--force`
**Example:**
```bash
omniroute pricing sync
```
### `pricing list`
**Flags:**
- `--provider <p>`
- `--model <m>`
- `--limit <n>`
**Example:**
```bash
omniroute pricing list
```
### `pricing get <model>`
**Example:**
```bash
omniroute pricing get <model>
```
### `pricing defaults`
**Example:**
```bash
omniroute pricing defaults
```
### `pricing show`
**Example:**
```bash
omniroute pricing show
```
### `pricing set`
**Flags:**
- `--input <p>`
- `--output <p>`
- `--cache-read <p>`
- `--cache-write <p>`
**Example:**
```bash
omniroute pricing set
```
### `pricing diff`
**Flags:**
- `--model <m>`
**Example:**
```bash
omniroute pricing diff
```
### `usage`
**Example:**
```bash
omniroute usage
```
### `usage analytics`
**Flags:**
- `--period <range>`
- `--provider <id>`
**Example:**
```bash
omniroute usage analytics
```
### `usage budget`
**Example:**
```bash
omniroute usage budget
```
### `usage list`
**Example:**
```bash
omniroute usage list
```
### `usage get [scope]`
**Example:**
```bash
omniroute usage get [scope]
```
### `usage set <amount>`
**Flags:**
- `--scope <s>`
- `--period <p>`
**Example:**
```bash
omniroute usage set <amount>
```
### `usage reset [scope]`
**Example:**
```bash
omniroute usage reset [scope]
```
### `usage quota`
**Flags:**
- `--provider <id>`
- `--check`
**Example:**
```bash
omniroute usage quota
```
### `usage logs`
**Flags:**
- `--limit <n>`
- `--search <q>`
- `--since <ts>`
- `--follow`
- `--api-key <k>`
**Example:**
```bash
omniroute usage logs
```
### `usage utilization`
**Flags:**
- `--api-key <k>`
**Example:**
```bash
omniroute usage utilization
```
### `usage history`
**Flags:**
- `--limit <n>`
**Example:**
```bash
omniroute usage history
```
### `usage proxy-logs`
**Flags:**
- `--limit <n>`
**Example:**
```bash
omniroute usage proxy-logs
```

263
skills/cli-eval/SKILL.md Normal file
View File

@@ -0,0 +1,263 @@
---
name: cli-eval
description: Create and run evaluation suites, watch live benchmark progress, view scorecards, compare model performance, and integrate eval runs with CI workflows from the CLI.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Create and run evaluation suites, watch live benchmark progress, view scorecards, compare model performance, and integrate eval runs with CI workflows from the CLI.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `eval`
**Example:**
```bash
omniroute eval
```
### `eval suites`
**Example:**
```bash
omniroute eval suites
```
### `eval list`
**Example:**
```bash
omniroute eval list
```
### `eval get <suiteId>`
**Example:**
```bash
omniroute eval get <suiteId>
```
### `eval create`
**Flags:**
- `--file <path>`
**Example:**
```bash
omniroute eval create
```
### `eval run <suiteId>`
**Flags:**
- `-m, --model <id>`
- `--combo <name>`
- `--concurrency <n>`
- `--tag <tag>`
- `--watch`
**Example:**
```bash
omniroute eval run <suiteId>
```
### `eval list`
**Flags:**
- `--suite <id>`
- `--status <s>`
- `--since <ts>`
- `--limit <n>`
**Example:**
```bash
omniroute eval list
```
### `eval get <runId>`
**Example:**
```bash
omniroute eval get <runId>
```
### `eval results <runId>`
**Flags:**
- `--failed`
**Example:**
```bash
omniroute eval results <runId>
```
### `eval cancel <runId>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute eval cancel <runId>
```
### `eval scorecard <runId>`
**Example:**
```bash
omniroute eval scorecard <runId>
```
### `simulate [prompt]`
**Flags:**
- `--file <path>`
- `-m, --model <id>`
- `--combo <name>`
- `--reasoning-effort <level>`
- `--thinking-budget <n>`
- `--explain`
**Example:**
```bash
omniroute simulate [prompt]
```
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute-cli-eval/SKILL.md (preserved curated content) -->
# OmniRoute — CLI Evals
Requires the `omniroute` CLI. See [CLI entry-point skill](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli/SKILL.md) for install + global flags.
## What are evals?
Evals are automated test suites that score LLM outputs against expected answers or rubrics. OmniRoute stores suites and run results in its local database.
## Eval suites
```bash
omniroute eval suites list # List all eval suites
omniroute eval suites list --json # JSON output
omniroute eval suites get <suiteId> # Full suite definition
```
### Create a suite
```bash
omniroute eval suites create \
--name "code-quality" \
--rubric "exact-match" \
--samples-file ./samples.jsonl # JSONL: {input, expected_output}
```
Rubric options: `exact-match`, `contains`, `llm-judge`, `regex`.
`--samples-file` format (one JSON object per line):
```jsonl
{"input": "What is 2+2?", "expected_output": "4"}
{"input": "Translate 'hello' to Spanish", "expected_output": "hola"}
```
## Run an eval
```bash
omniroute eval suites run <suiteId> \
--model claude-sonnet-4-6 # Run suite against a specific model
omniroute eval suites run <suiteId> \
--model gpt-4o \
--watch # Live TUI progress (EvalWatch)
```
The run is asynchronous. Use `--watch` for a live terminal dashboard or poll manually:
```bash
RUN_ID=$(omniroute eval suites run <suiteId> --model claude-sonnet-4-6 --output json | jq -r '.id')
omniroute eval get $RUN_ID
```
## Manage runs
```bash
omniroute eval list # List all eval runs
omniroute eval list --json
omniroute eval get <runId> # Run details (status, model, score)
omniroute eval results <runId> # Per-sample results
omniroute eval scorecard <runId> # Full scorecard with pass/fail per sample
omniroute eval cancel <runId> # Cancel a running eval
```
## Scorecard output
```bash
omniroute eval scorecard <runId> --output json
```
Response fields per sample:
```json
{
"id": "sample-1",
"score": 0.95,
"passed": true,
"input": "What is 2+2?",
"output": "4",
"expected": "4"
}
```
## Comparing models
Run the same suite against multiple models and compare:
```bash
for MODEL in claude-sonnet-4-6 gpt-4o gemini-2.0-flash; do
omniroute eval suites run $SUITE_ID --model $MODEL --output json | jq '{model: .model, score: .score}'
done
```
## CI integration
```bash
# Run and fail CI if score drops below threshold
SCORE=$(omniroute eval suites run $SUITE_ID --model claude-sonnet-4-6 --output json | jq -r '.score')
python3 -c "import sys; score=float('$SCORE'); sys.exit(0 if score >= 0.90 else 1)"
```
## Errors
- `suites create` fails with `invalid rubric` → use one of: `exact-match`, `contains`, `llm-judge`, `regex`
- `suites run` returns `model not found` → verify model ID with `omniroute models --search <name>`
- `eval get` shows `status: failed` → check `omniroute logs --search eval` for error details
- `scorecard` returns empty results → the run may still be `running`; poll `omniroute eval get <runId>` until `status` is `completed`
<!-- skill:custom-end -->

View File

@@ -0,0 +1,88 @@
---
name: cli-health
description: Check server health, component status, and live metrics from the CLI. Run `health`, `health components`, and `health watch` for a real-time dashboard of circuit breakers and provider status.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Check server health, component status, and live metrics from the CLI. Run `health`, `health components`, and `health watch` for a real-time dashboard of circuit breakers and provider status.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `doctor`
**Flags:**
- `--no-liveness`
- `--host <host>`
- `--liveness-url <url>`
**Example:**
```bash
omniroute doctor
```
### `health`
**Flags:**
- `-v, --verbose`
- `--json`
- `--alerts-only`
**Example:**
```bash
omniroute health
```
### `health components`
List health components and their status
**Flags:**
- `--alerts-only`
**Example:**
```bash
omniroute health components
```
### `health watch`
Live dashboard — refresh every N seconds
**Flags:**
- `--interval <s>`
**Example:**
```bash
omniroute health watch
```
### `status`
Show OmniRoute status dashboard
**Flags:**
- `-v, --verbose`
**Example:**
```bash
omniroute status
```

226
skills/cli-keys/SKILL.md Normal file
View File

@@ -0,0 +1,226 @@
---
name: cli-keys
description: Create, list, rotate, and revoke OmniRoute API keys from the CLI. Manage OAuth flows for provider authentication and inspect key scopes and expiration.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Create, list, rotate, and revoke OmniRoute API keys from the CLI. Manage OAuth flows for provider authentication and inspect key scopes and expiration.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `keys`
**Example:**
```bash
omniroute keys
```
### `keys add <provider> [apiKey]`
**Flags:**
- `--stdin`
**Example:**
```bash
omniroute keys add <provider> [apiKey]
```
### `keys list`
**Flags:**
- `--json`
**Example:**
```bash
omniroute keys list
```
### `keys remove <provider>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute keys remove <provider>
```
### `keys regenerate <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute keys regenerate <id>
```
### `keys revoke <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute keys revoke <id>
```
### `keys reveal <id>`
**Example:**
```bash
omniroute keys reveal <id>
```
### `keys usage <id>`
**Flags:**
- `--limit <n>`
**Example:**
```bash
omniroute keys usage <id>
```
### `keys policy`
**Example:**
```bash
omniroute keys policy
```
### `keys show <id>`
**Example:**
```bash
omniroute keys show <id>
```
### `keys set <id>`
**Flags:**
- `--rate-limit <n>`
- `--max-cost <n>`
- `--allowed-models <list>`
**Example:**
```bash
omniroute keys set <id>
```
### `keys expiration`
**Example:**
```bash
omniroute keys expiration
```
### `keys list`
**Flags:**
- `--days <n>`
- `--json`
**Example:**
```bash
omniroute keys list
```
### `keys rotate <id>`
**Flags:**
- `--grace-period <ms>`
- `--yes`
**Example:**
```bash
omniroute keys rotate <id>
```
### `oauth`
**Example:**
```bash
omniroute oauth
```
### `oauth providers`
**Example:**
```bash
omniroute oauth providers
```
### `oauth start`
**Flags:**
- `--no-browser`
- `--import-from-system`
- `--social <s>`
- `--timeout <ms>`
**Example:**
```bash
omniroute oauth start
```
### `oauth status`
**Flags:**
- `--provider <id>`
**Example:**
```bash
omniroute oauth status
```
### `oauth revoke`
**Flags:**
- `--connection-id <id>`
- `--yes`
**Example:**
```bash
omniroute oauth revoke
```

150
skills/cli-mcp/SKILL.md Normal file
View File

@@ -0,0 +1,150 @@
---
name: cli-mcp
description: Inspect the MCP server status, list registered tools and scopes, run tool invocations, and manage MCP audit logs from the CLI.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Inspect the MCP server status, list registered tools and scopes, run tool invocations, and manage MCP audit logs from the CLI.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `mcp`
**Example:**
```bash
omniroute mcp
```
### `mcp status`
Show MCP server status
**Flags:**
- `--json`
**Example:**
```bash
omniroute mcp status
```
### `mcp restart`
Restart the MCP server
**Example:**
```bash
omniroute mcp restart
```
### `mcp call <tool> [argsJson]`
**Flags:**
- `--args <json>`
- `--args-file <path>`
- `--stream`
- `--scope <s>`
**Example:**
```bash
omniroute mcp call <tool> [argsJson]
```
### `mcp scopes`
**Flags:**
- `--tool <name>`
**Example:**
```bash
omniroute mcp scopes
```
### `mcp tools`
**Example:**
```bash
omniroute mcp tools
```
### `mcp list`
**Flags:**
- `--scope <s>`
**Example:**
```bash
omniroute mcp list
```
### `mcp info <name>`
**Example:**
```bash
omniroute mcp info <name>
```
### `mcp schema <name>`
**Flags:**
- `--io <kind>`
**Example:**
```bash
omniroute mcp schema <name>
```
### `mcp audit`
**Example:**
```bash
omniroute mcp audit
```
### `mcp tail`
**Flags:**
- `--follow`
- `--limit <n>`
**Example:**
```bash
omniroute mcp tail
```
### `mcp stats`
**Flags:**
- `--period <p>`
**Example:**
```bash
omniroute mcp stats
```

View File

@@ -0,0 +1,31 @@
---
name: cli-models
description: Query available AI models, list model aliases, and browse the full model catalog from the CLI. Filter by provider, search by capability, and resolve model name variants.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Query available AI models, list model aliases, and browse the full model catalog from the CLI. Filter by provider, search by capability, and resolve model name variants.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `models [provider]`
**Flags:**
- `--search <query>`
- `--json`
**Example:**
```bash
omniroute models [provider]
```

View File

@@ -0,0 +1,385 @@
---
name: cli-plugins-skills
description: Manage Omni Skills (list, install, test, remove), plugins (create, configure), and persistent memory (search, add, clear) from the CLI.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Manage Omni Skills (list, install, test, remove), plugins (create, configure), and persistent memory (search, add, clear) from the CLI.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `memory`
**Example:**
```bash
omniroute memory
```
### `memory search <query>`
**Flags:**
- `--type <type>`
- `--limit <n>`
- `--api-key <key>`
- `--token-budget <n>`
**Example:**
```bash
omniroute memory search <query>
```
### `memory add`
**Flags:**
- `--content <text>`
- `--file <path>`
- `--type <type>`
- `--metadata <json>`
- `--api-key <key>`
**Example:**
```bash
omniroute memory add
```
### `memory clear`
**Flags:**
- `--type <type>`
- `--older-than <duration>`
- `--api-key <key>`
- `--yes`
**Example:**
```bash
omniroute memory clear
```
### `memory list`
**Flags:**
- `--type <type>`
- `--limit <n>`
- `--api-key <key>`
**Example:**
```bash
omniroute memory list
```
### `memory get <id>`
**Example:**
```bash
omniroute memory get <id>
```
### `memory delete <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute memory delete <id>
```
### `memory health`
**Example:**
```bash
omniroute memory health
```
### `PLUGIN_NAME`
**Example:**
```bash
omniroute PLUGIN_NAME
```
### `plugin plugin`
**Example:**
```bash
omniroute plugin plugin
```
### `plugin list`
**Example:**
```bash
omniroute plugin list
```
### `plugin install <name>`
**Flags:**
- `-y, --yes`
**Example:**
```bash
omniroute plugin install <name>
```
### `plugin remove <name>`
**Flags:**
- `-y, --yes`
**Example:**
```bash
omniroute plugin remove <name>
```
### `plugin info <name>`
**Example:**
```bash
omniroute plugin info <name>
```
### `plugin search [query]`
**Example:**
```bash
omniroute plugin search [query]
```
### `plugin update [name]`
**Example:**
```bash
omniroute plugin update [name]
```
### `plugin scaffold <name>`
**Example:**
```bash
omniroute plugin scaffold <name>
```
### `marketplace`
**Example:**
```bash
omniroute marketplace
```
### `skills search [query]`
**Flags:**
- `--category <c>`
- `--tag <t>`
- `--limit <n>`
- `--sort <s>`
**Example:**
```bash
omniroute skills search [query]
```
### `skills info <packageId>`
**Example:**
```bash
omniroute skills info <packageId>
```
### `skills install <packageId>`
**Flags:**
- `--version <v>`
- `--enable`
- `--yes`
**Example:**
```bash
omniroute skills install <packageId>
```
### `skills categories`
**Example:**
```bash
omniroute skills categories
```
### `skills featured`
**Example:**
```bash
omniroute skills featured
```
### `skills skills`
**Example:**
```bash
omniroute skills skills
```
### `skills list`
**Flags:**
- `--type <type>`
- `--enabled`
- `--disabled`
- `--api-key <key>`
**Example:**
```bash
omniroute skills list
```
### `skills get <id>`
**Example:**
```bash
omniroute skills get <id>
```
### `skills install`
**Flags:**
- `--from-file <path>`
- `--from-url <url>`
- `--type <type>`
- `--enable`
**Example:**
```bash
omniroute skills install
```
### `skills enable <id>`
**Example:**
```bash
omniroute skills enable <id>
```
### `skills disable <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute skills disable <id>
```
### `skills delete <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute skills delete <id>
```
### `skills execute <id>`
**Flags:**
- `--input <json>`
- `--input-file <path>`
- `--timeout <ms>`
**Example:**
```bash
omniroute skills execute <id>
```
### `skills executions`
**Flags:**
- `--skill <id>`
- `--limit <n>`
- `--status <s>`
**Example:**
```bash
omniroute skills executions
```
### `skills skillssh`
**Example:**
```bash
omniroute skills skillssh
```
### `skills list`
**Example:**
```bash
omniroute skills list
```
### `skills install <url>`
**Example:**
```bash
omniroute skills install <url>
```

View File

@@ -0,0 +1,244 @@
---
name: cli-policy-audit
description: Inspect audit logs, manage access policies, view telemetry data, and review request history from the CLI. Filter by event type, user, or time range for compliance workflows.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Inspect audit logs, manage access policies, view telemetry data, and review request history from the CLI. Filter by event type, user, or time range for compliance workflows.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `audit`
**Example:**
```bash
omniroute audit
```
### `audit tail`
**Flags:**
- `--source <s>`
- `--follow`
- `--limit <n>`
**Example:**
```bash
omniroute audit tail
```
### `audit search <query>`
**Flags:**
- `--source <s>`
- `--since <ts>`
- `--until <ts>`
- `--limit <n>`
- `--actor <id>`
- `--action <a>`
**Example:**
```bash
omniroute audit search <query>
```
### `audit export <file>`
**Flags:**
- `--source <s>`
- `--format <f>`
- `--since <ts>`
- `--until <ts>`
**Example:**
```bash
omniroute audit export <file>
```
### `audit stats`
**Flags:**
- `--source <s>`
- `--period <p>`
**Example:**
```bash
omniroute audit stats
```
### `audit get <id>`
**Flags:**
- `--source <s>`
**Example:**
```bash
omniroute audit get <id>
```
### `logs`
**Flags:**
- `--follow`
- `--filter <level>`
- `--lines <n>`
- `--timeout <ms>`
- `--base-url <url>`
- `--request-id <id>`
- `--api-key <key>`
- `--combo <name>`
- `--status <code>`
- `--duration-min <ms>`
- `--duration-max <ms>`
- `--export <path>`
**Example:**
```bash
omniroute logs
```
### `policy`
**Example:**
```bash
omniroute policy
```
### `policy list`
**Flags:**
- `--kind <k>`
- `--scope <s>`
**Example:**
```bash
omniroute policy list
```
### `policy get <id>`
**Example:**
```bash
omniroute policy get <id>
```
### `policy create`
**Example:**
```bash
omniroute policy create
```
### `policy update <id>`
**Example:**
```bash
omniroute policy update <id>
```
### `policy delete <id>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute policy delete <id>
```
### `policy evaluate`
**Flags:**
- `--resource <r>`
- `--context <json>`
**Example:**
```bash
omniroute policy evaluate
```
### `policy export <file>`
**Example:**
```bash
omniroute policy export <file>
```
### `policy import <file>`
**Flags:**
- `--overwrite`
**Example:**
```bash
omniroute policy import <file>
```
### `telemetry`
**Example:**
```bash
omniroute telemetry
```
### `telemetry summary`
**Flags:**
- `--period <p>`
- `--compare-to <p>`
**Example:**
```bash
omniroute telemetry summary
```
### `telemetry export`
**Flags:**
- `--out <path>`
- `--period <p>`
**Example:**
```bash
omniroute telemetry export
```

View File

@@ -0,0 +1,332 @@
---
name: cli-providers
description: "Manage provider connections from the CLI: list available/configured providers, add, test, test-all, validate, rotate API keys, and view per-provider metrics."
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Manage provider connections from the CLI: list available/configured providers, add, test, test-all, validate, rotate API keys, and view per-provider metrics.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `provider-cmd provider [subcommand]`
Manage provider connections (use
**Example:**
```bash
omniroute provider-cmd provider [subcommand]
```
### `providers`
**Example:**
```bash
omniroute providers
```
### `providers available`
Show available providers in the OmniRoute catalog
**Flags:**
- `--json`
- `--search <query>`
- `-q, --q <query>`
- `--category <category>`
**Example:**
```bash
omniroute providers available
```
### `providers list`
List configured provider connections
**Flags:**
- `--json`
**Example:**
```bash
omniroute providers list
```
### `providers test <idOrName>`
Test a configured provider connection
**Flags:**
- `--json`
**Example:**
```bash
omniroute providers test <idOrName>
```
### `providers test-all`
Test all active provider connections
**Flags:**
- `--json`
**Example:**
```bash
omniroute providers test-all
```
### `providers validate`
Validate local provider configuration without calling upstream
**Flags:**
- `--json`
**Example:**
```bash
omniroute providers validate
```
### `providers rotate <idOrName>`
**Flags:**
- `--new-key <key>`
- `--from-env <VAR>`
- `--oauth`
- `--yes`
- `--skip-test`
- `--dry-run`
**Example:**
```bash
omniroute providers rotate <idOrName>
```
### `providers status`
**Flags:**
- `--provider <name>`
- `--json`
**Example:**
```bash
omniroute providers status
```
### `providers metrics`
**Flags:**
- `--provider <id>`
- `--connection-id <id>`
- `--period <p>`
- `--metric <m>`
- `--sort-by <field>`
- `--limit <n>`
- `--watch`
- `--compare <list>`
**Example:**
```bash
omniroute providers metrics
```
### `providers metric <connectionId> <metric>`
**Flags:**
- `--period <p>`
**Example:**
```bash
omniroute providers metric <connectionId> <metric>
```
### `test-provider test [provider] [model]`
**Flags:**
- `--all-providers`
- `--json`
- `--latency`
- `--repeat <n>`
- `--compare <models>`
- `--save <path>`
**Example:**
```bash
omniroute test-provider test [provider] [model]
```
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute-cli-providers/SKILL.md (preserved curated content) -->
# OmniRoute — CLI Providers & Keys
Requires the `omniroute` CLI. See [CLI entry-point skill](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli/SKILL.md) for install + global flags.
## Provider catalog (available providers)
```bash
omniroute providers available # Full OmniRoute provider catalog
omniroute providers available --search openai # Filter by id, name, alias
omniroute providers available --category api-key # Filter by category
omniroute providers available --json # Machine-readable JSON
```
Categories: `api-key`, `oauth`, `free`, `local`, `combo`.
## Configured provider connections
```bash
omniroute providers list # Connections in your DB
omniroute providers list --json
```
## Testing connections
```bash
omniroute providers test <id|name> # Test one configured connection
omniroute providers test-all # Test every active connection (TUI progress)
omniroute providers validate # Local-only structural validation (no HTTP)
```
`test-all` opens an interactive TUI that shows live pass/fail per connection. Use `--json` to get a machine-readable result:
```bash
omniroute providers test-all --json
```
## API key management (OmniRoute keys)
These manage the OmniRoute API keys issued under **API Manager** — not provider credentials.
```bash
omniroute keys list # List all OmniRoute API keys
omniroute keys add <provider> [apiKey] # Add an API key for a provider
omniroute keys remove <provider> # Remove an API key
omniroute keys regenerate <id> # Regenerate (rotate) a key
omniroute keys revoke <id> # Revoke a key (disables it)
omniroute keys reveal <id> # Show the full key value
omniroute keys usage <id> # Show usage stats for a key
omniroute keys rotate <id> # Rotate + revoke old key atomically
omniroute keys expiration list # List key expiration times
```
### Key policies
```bash
omniroute keys policy show <id> # Show rate-limit / permission policy
omniroute keys policy set <id> \
--rate-limit 100 \
--rate-window minute \
--permissions chat,models # Set policy on a key
```
## Models
```bash
omniroute models # List all models (all providers)
omniroute models openai # Filter by provider
omniroute models --search gpt # Search by name
omniroute models --json # JSON output
```
## OAuth providers
```bash
omniroute oauth list # List OAuth-configured providers
omniroute oauth login <provider> # Start browser-based OAuth flow
omniroute oauth logout <provider> # Revoke OAuth token
omniroute oauth status <provider> # Show token state + expiry
omniroute oauth refresh <provider> # Force token refresh
```
For OAuth providers (Gemini, Windsurf, Antigravity, etc.) the `login` command opens the OmniRoute dashboard OAuth flow in your browser.
## Provider nodes (multi-account routing)
Provider nodes let you attach multiple API keys / accounts to one logical provider for round-robin or failover.
```bash
omniroute nodes list <provider> # List nodes for a provider
omniroute nodes add <provider> --api-key <key> # Add a node
omniroute nodes remove <provider> <nodeId> # Remove a node
omniroute nodes test <provider> <nodeId> # Test one node
```
## Routing combos (CLI)
Create and manage routing combos from the terminal:
```bash
omniroute combo list # List all combos
omniroute combo create <name> \
--strategy priority \
--targets anthropic/claude-opus-4-7,openai/gpt-4o # Create combo
omniroute combo switch <name> # Activate a combo as default
omniroute combo delete <name> # Delete a combo
omniroute combo suggest --task "code review" # Ask OmniRoute to recommend a combo
```
For the full REST API for combos see [omniroute-routing skill](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-routing/SKILL.md).
## Quota & usage
```bash
omniroute quota # Provider quota usage + reset times
omniroute usage # Request + token usage summary
omniroute cost # Cost breakdown (by provider/model)
```
## Compression (CLI)
```bash
omniroute compression status # Current compression mode + savings stats
omniroute compression set --mode rtk # Enable RTK compression
omniroute compression set --mode stacked # Enable stacked (RTK + Caveman)
omniroute compression set --mode off # Disable compression
omniroute compression preview --mode rtk --text "..." # Preview savings for sample text
```
## Health
```bash
omniroute health # Detailed health: circuit breakers, cache, memory
```
## Errors
- `providers test <id>` fails with 401 → API key stored for that provider is wrong; use `keys remove` + `keys add` to reset it
- `oauth login` opens but doesn't complete → the OAuth token endpoint may be firewalled; use API key auth instead
- `combo create` fails with `strategy unknown` → use one of: `priority`, `weighted`, `round-robin`, `fill-first`, `least-used`, `cost-optimized`, `auto`, `random`, `strict-random`, `p2c`, `reset-aware`, `lkgp`, `context-optimized`, `context-relay`
<!-- skill:custom-end -->

View File

@@ -0,0 +1,158 @@
---
name: cli-resilience
description: Inspect and manage circuit-breaker states, connection cooldowns, quota limits, and backoff levels from the CLI. Reset stuck providers and configure resilience thresholds.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Inspect and manage circuit-breaker states, connection cooldowns, quota limits, and backoff levels from the CLI. Reset stuck providers and configure resilience thresholds.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `quota`
**Flags:**
- `--provider <id>`
- `--json`
**Example:**
```bash
omniroute quota
```
### `resilience`
**Example:**
```bash
omniroute resilience
```
### `resilience status`
**Flags:**
- `--provider <p>`
**Example:**
```bash
omniroute resilience status
```
### `resilience breakers`
**Flags:**
- `--provider <p>`
**Example:**
```bash
omniroute resilience breakers
```
### `resilience cooldowns`
**Flags:**
- `--provider <p>`
- `--connection-id <id>`
**Example:**
```bash
omniroute resilience cooldowns
```
### `resilience lockouts`
**Flags:**
- `--provider <p>`
- `--model <m>`
**Example:**
```bash
omniroute resilience lockouts
```
### `resilience reset`
**Flags:**
- `--connection-id <id>`
- `--model <m>`
- `--all-cooldowns`
- `--yes`
**Example:**
```bash
omniroute resilience reset
```
### `resilience profile`
**Example:**
```bash
omniroute resilience profile
```
### `resilience show`
**Example:**
```bash
omniroute resilience show
```
### `resilience set`
**Example:**
```bash
omniroute resilience set
```
### `resilience config`
**Example:**
```bash
omniroute resilience config
```
### `resilience show`
**Example:**
```bash
omniroute resilience show
```
### `resilience set`
**Flags:**
- `--threshold <n>`
- `--reset-timeout <ms>`
- `--base-cooldown <ms>`
**Example:**
```bash
omniroute resilience set
```

View File

@@ -0,0 +1,91 @@
---
name: cli-routing
description: Create, list, update, and delete routing combos from the CLI. Test routing strategies, inspect combo metrics, and configure fallback chains interactively.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Create, list, update, and delete routing combos from the CLI. Test routing strategies, inspect combo metrics, and configure fallback chains interactively.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `suggest`
**Flags:**
- `--max-cost <usd>`
- `--max-latency-ms <ms>`
- `--weights <json>`
- `--top <n>`
- `--explain`
- `--switch`
**Example:**
```bash
omniroute suggest
```
### `combo combo`
**Example:**
```bash
omniroute combo combo
```
### `combo list`
List configured routing combos
**Flags:**
- `--json`
**Example:**
```bash
omniroute combo list
```
### `combo switch <name>`
Activate a routing combo
**Example:**
```bash
omniroute combo switch <name>
```
### `combo create <name>`
Create a new routing combo
**Example:**
```bash
omniroute combo create <name>
```
### `combo delete <name>`
Delete a routing combo
**Flags:**
- `--yes`
**Example:**
```bash
omniroute combo delete <name>
```

312
skills/cli-serve/SKILL.md Normal file
View File

@@ -0,0 +1,312 @@
---
name: cli-serve
description: Start, stop, and restart the OmniRoute server from the CLI. Manage daemon mode, port configuration, auto-recovery, system tray integration, and the dashboard open shortcut.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Start, stop, and restart the OmniRoute server from the CLI. Manage daemon mode, port configuration, auto-recovery, system tray integration, and the dashboard open shortcut.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `dashboard`
**Flags:**
- `--url`
- `--port <port>`
- `--tui`
**Example:**
```bash
omniroute dashboard
```
### `restart`
**Flags:**
- `--port <port>`
**Example:**
```bash
omniroute restart
```
### `serve`
**Flags:**
- `--port <port>`
- `--no-open`
- `--daemon`
- `--log`
- `--no-recovery`
- `--max-restarts <n>`
- `--tray`
- `--no-tray`
**Example:**
```bash
omniroute serve
```
### `stop`
**Example:**
```bash
omniroute stop
```
<!-- skill:custom-start -->
<!-- Aggregated from: omniroute-cli (setup + index), omniroute-cli-admin (lifecycle/setup/backup/tunnel) -->
## Setup
The `omniroute` binary ships with the OmniRoute server. It is both the server launcher and a full management CLI with 250+ commands across 39 groups.
### Install
```bash
npm install -g omniroute # npm registry
# or: use the binary bundled with the desktop app
```
Requires Node.js ≥20.20.2, ≥22.22.2, or ≥24.
Verify:
```bash
omniroute --version # prints installed version
omniroute --help # full command tree
```
### Connection
Every CLI command that talks to the server reads two values:
| Source | Variable / Flag |
| -------- | ------------------------------------ |
| Base URL | `OMNIROUTE_BASE_URL` or `--base-url` |
| API key | `OMNIROUTE_API_KEY` or `--api-key` |
Default base URL: `http://localhost:20128`
```bash
export OMNIROUTE_BASE_URL="http://localhost:20128"
export OMNIROUTE_API_KEY="sk-..." # from Dashboard → API Manager
```
For a remote server:
```bash
export OMNIROUTE_BASE_URL="https://your-server.com"
```
### Global flags
| Flag | Description |
| ------------------- | -------------------------------------------------------- |
| `--base-url <url>` | Override server URL for this invocation |
| `--api-key <key>` | Override API key for this invocation |
| `--output <format>` | Output format: `table` (default), `json`, `jsonl`, `csv` |
| `--json` | Shorthand for `--output json` |
| `--non-interactive` | Disable prompts — for CI / shell scripts |
| `--no-open` | Don't auto-open the browser on start |
| `--port <n>` | Override default port 20128 |
| `--help`, `-h` | Show help for the current command |
| `--version`, `-v` | Print the installed version |
### Output formats
All listing commands support `--output`:
```bash
omniroute combo list # human-readable table
omniroute combo list --output json # JSON array
omniroute combo list --output jsonl # one JSON object per line
omniroute combo list --output csv # CSV with header row
```
### Quick start: one-shot server + provider setup
```bash
# 1. Start server
omniroute
# 2. (First run) interactive setup wizard
omniroute setup
# 3. Verify everything is healthy
omniroute doctor
```
### CLI capability skills
| Capability | Skill |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| Server admin + backup | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-admin/SKILL.md |
| Provider & key management | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-providers/SKILL.md |
| Cloud agents (Codex / Devin / Jules) | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-cloud/SKILL.md |
| Evals & benchmarking | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-eval/SKILL.md |
### Errors
- `Connection refused` → server not running; run `omniroute` or `omniroute serve`
- `401 Unauthorized` → wrong or missing API key
- `command not found: omniroute` → not in PATH; check `npm root -g` or re-install
- `doctor` reports SQLite incompatible → `npm rebuild better-sqlite3` in the app directory
## Admin lifecycle
Requires the `omniroute` CLI. See [CLI entry-point skill](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli/SKILL.md) for install + global flags.
### Server lifecycle
```bash
omniroute # Start server (default port 20128)
omniroute serve # Explicit alias
omniroute --port 3000 # Override port
omniroute --no-open # Don't auto-open browser
omniroute --mcp # Start as MCP server (stdio transport)
omniroute stop # Stop the running server
omniroute restart # Restart the server
omniroute dashboard # Open dashboard in browser
omniroute open # Alias for dashboard
omniroute status # Runtime status (uptime, requests, providers)
```
### Setup & provisioning
#### Interactive wizard
```bash
omniroute setup # Step-by-step interactive setup
```
#### Non-interactive (CI / Docker)
```bash
omniroute setup --non-interactive \
--password 'admin-password' \
--add-provider \
--provider openai \
--api-key 'sk-...' \
--test-provider
```
Environment variables for non-interactive setup:
| Variable | Purpose |
| ----------------------------- | -------------------------------------------- |
| `OMNIROUTE_SETUP_PASSWORD` | Admin password (≥8 chars) |
| `OMNIROUTE_PROVIDER` | Provider id (e.g. `openai`, `anthropic`) |
| `OMNIROUTE_PROVIDER_NAME` | Display name for the connection |
| `OMNIROUTE_PROVIDER_BASE_URL` | Optional OpenAI-compatible base URL override |
| `OMNIROUTE_API_KEY` | Provider API key |
| `OMNIROUTE_DEFAULT_MODEL` | Optional default model |
| `DATA_DIR` | Override OmniRoute data directory |
### Diagnostics
```bash
omniroute doctor # Full health check
omniroute doctor --json # Machine-readable JSON
omniroute doctor --no-liveness # Skip HTTP health probe
omniroute doctor --host 0.0.0.0 # Override liveness host
omniroute doctor --liveness-url <url> # Full URL override
```
Checks performed: Config, Database, Storage/encryption, Port, Node runtime, Native binary (better-sqlite3), Memory, Server liveness.
Exit code is non-zero if any check fails — useful in CI:
```bash
omniroute doctor --json | jq '.checks[] | select(.status=="fail")'
```
### Backup & restore
```bash
omniroute backup # Snapshot config + SQLite DB to ~/.omniroute/backups/
omniroute restore # Restore from a previous snapshot (interactive picker)
```
### Autostart (system tray / startup)
```bash
omniroute autostart enable # Register OmniRoute as a system startup item
omniroute autostart disable # Remove startup registration
omniroute autostart status # Show current autostart state
```
On Linux: creates a **systemd user service** (`~/.config/systemd/user/omniroute.service`) and enables **linger** so the service can start after reboot without a graphical login; on desktop sessions it also adds an XDG autostart entry with `--tray`. On macOS: LaunchAgent plist. On Windows: registry startup entry.
### Tunnels (public URL)
Expose a local OmniRoute instance via a secure tunnel:
```bash
omniroute tunnel list # List active tunnels
omniroute tunnel create cloudflare # Start a Cloudflare Tunnel (free)
omniroute tunnel create tailscale # Start a Tailscale funnel
omniroute tunnel create ngrok # Start an ngrok tunnel
omniroute tunnel stop <id> # Stop a running tunnel
```
The tunnel URL is printed and can be used as `OMNIROUTE_BASE_URL` from remote machines.
### Config & environment
```bash
omniroute config show # Display current effective configuration
omniroute env show # List all OmniRoute environment variables
omniroute env get <KEY> # Get a single env var value
omniroute env set <KEY> <value> # Set an env var (temporary — until restart)
```
### Recovery
```bash
omniroute reset-password # Reset the admin password interactively
omniroute reset-encrypted-columns # Dry-run: show encrypted credential columns
omniroute reset-encrypted-columns --force # Null out encrypted credentials in SQLite
```
Use `reset-encrypted-columns --force` only if `STORAGE_ENCRYPTION_KEY` was lost and you need to re-enter all provider API keys.
### Logs
```bash
omniroute logs # Stream live request logs
omniroute logs --json # JSON log entries
omniroute logs --search <term> # Filter by term
omniroute logs --follow # Tail mode (keep streaming)
```
### Update
```bash
omniroute update # Check for a newer version and prompt to update
```
### Errors
- `doctor` shows `STORAGE_ENCRYPTION_KEY missing` → set the key in `.env` or run `reset-encrypted-columns --force` to wipe and re-enter credentials
- `doctor` reports native binary fail → `npm rebuild better-sqlite3` in the OmniRoute app directory
- `tunnel create cloudflare` hangs → ensure `cloudflared` is installed: `brew install cloudflare/cloudflare/cloudflared`
<!-- skill:custom-end -->

261
skills/cli-setup/SKILL.md Normal file
View File

@@ -0,0 +1,261 @@
---
name: cli-setup
description: Run initial setup, configure global CLI settings, manage environment variables, check for updates, and configure autostart via the CLI setup and config commands.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Run initial setup, configure global CLI settings, manage environment variables, check for updates, and configure autostart via the CLI setup and config commands.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `autostart`
**Example:**
```bash
omniroute autostart
```
### `autostart enable`
**Example:**
```bash
omniroute autostart enable
```
### `autostart disable`
**Example:**
```bash
omniroute autostart disable
```
### `autostart status`
**Example:**
```bash
omniroute autostart status
```
### `config`
Show or update CLI tool configuration
**Example:**
```bash
omniroute config
```
### `config list`
List all CLI tools and config status
**Flags:**
- `--json`
**Example:**
```bash
omniroute config list
```
### `config get <tool>`
Show current config for a tool
**Flags:**
- `--json`
**Example:**
```bash
omniroute config get <tool>
```
### `config set <tool>`
Write config for a tool
**Flags:**
- `--model <model>`
- `--non-interactive`
- `--yes`
**Example:**
```bash
omniroute config set <tool>
```
### `config validate <tool>`
Validate config format without writing
**Flags:**
- `--model <model>`
- `--json`
**Example:**
```bash
omniroute config validate <tool>
```
### `config opencode`
Generate OpenCode config (alias for
**Flags:**
- `--model <model>`
- `--non-interactive`
- `--yes`
**Example:**
```bash
omniroute config opencode
```
### `config lang`
**Example:**
```bash
omniroute config lang
```
### `config get`
**Flags:**
- `--json`
**Example:**
```bash
omniroute config get
```
### `config set <code>`
**Flags:**
- `--force`
**Example:**
```bash
omniroute config set <code>
```
### `config list`
**Flags:**
- `--json`
**Example:**
```bash
omniroute config list
```
### `env`
Show and manage environment variables
**Example:**
```bash
omniroute env
```
### `env show`
Show current environment variables
**Flags:**
- `--json`
**Example:**
```bash
omniroute env show
```
### `env get <key>`
Get a single environment variable
**Example:**
```bash
omniroute env get <key>
```
### `env set <key> <value>`
Set an environment variable (current session only)
**Example:**
```bash
omniroute env set <key> <value>
```
### `setup`
**Flags:**
- `--password <value>`
- `--add-provider`
- `--provider <id>`
- `--provider-name <name>`
- `--api-key <value>`
- `--default-model <model>`
- `--provider-base-url <url>`
- `--test-provider`
- `--non-interactive`
- `--list`
**Example:**
```bash
omniroute setup
```
### `update`
**Flags:**
- `--check`
- `--apply`
- `--changelog`
- `--dry-run`
- `--no-backup`
- `--yes`
**Example:**
```bash
omniroute update
```

106
skills/cli-tunnel/SKILL.md Normal file
View File

@@ -0,0 +1,106 @@
---
name: cli-tunnel
description: Start and stop tunnel connections (ngrok, Cloudflare, custom) from the CLI. Inspect active tunnel URLs, configure authentication, and test external reachability.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Start and stop tunnel connections (ngrok, Cloudflare, custom) from the CLI. Inspect active tunnel URLs, configure authentication, and test external reachability.
## Quick install
```bash
npm install -g omniroute # or: npx omniroute
omniroute --version
```
## Subcommands
### `tunnel`
**Example:**
```bash
omniroute tunnel
```
### `tunnel list`
**Flags:**
- `--json`
**Example:**
```bash
omniroute tunnel list
```
### `tunnel create [type]`
**Example:**
```bash
omniroute tunnel create [type]
```
### `tunnel stop <type>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute tunnel stop <type>
```
### `tunnel status <type>`
**Flags:**
- `--json`
**Example:**
```bash
omniroute tunnel status <type>
```
### `tunnel logs <type>`
**Flags:**
- `--tail <n>`
**Example:**
```bash
omniroute tunnel logs <type>
```
### `tunnel info <type>`
**Flags:**
- `--json`
**Example:**
```bash
omniroute tunnel info <type>
```
### `tunnel rotate <type>`
**Flags:**
- `--yes`
**Example:**
```bash
omniroute tunnel rotate <type>
```

View File

@@ -0,0 +1,91 @@
---
name: omni-agents-a2a
description: "Interact with OmniRoute via JSON-RPC 2.0 agent-to-agent protocol. 6 built-in A2A skills: smart-routing, quota-management, provider-discovery, cost-analysis, health-report, list-capabilities."
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Interact with OmniRoute via JSON-RPC 2.0 agent-to-agent protocol. 6 built-in A2A skills: smart-routing, quota-management, provider-discovery, cost-analysis, health-report, list-capabilities.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
_No endpoints mapped for this area yet._
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute-a2a/SKILL.md (preserved curated content) -->
# OmniRoute — A2A Protocol
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
OmniRoute publishes an Agent Card at `/.well-known/agent.json` and accepts
JSON-RPC 2.0 calls at `/a2a`.
## Discovery
```bash
curl $OMNIROUTE_URL/.well-known/agent.json
```
Returns Agent Card with skills, endpoints, auth scheme.
## Available skills
| Skill | Purpose |
| -------------------- | ----------------------------------------------------------------------------------- |
| `smart-routing` | Given a prompt, recommends best provider/model combo |
| `quota-management` | Reports quota balance for given provider/account |
| `provider-discovery` | Lists providers matching capability filters (vision, JSON mode, tools, max-context) |
| `cost-analysis` | Estimates cost for a given request shape |
| `health-report` | Returns system health (circuit states, latencies, errors) |
## Call example (JSON-RPC 2.0)
```bash
curl -X POST $OMNIROUTE_URL/a2a \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"skillId": "smart-routing",
"input": { "prompt_length": 4000, "tools": true, "vision": false }
},
"id": 1
}'
```
## Response shape
```json
{
"jsonrpc": "2.0",
"result": {
"taskId": "...",
"status": "completed",
"output": { "recommended_combo": "...", "reasoning": "..." }
},
"id": 1
}
```
## Errors
- `-32600` → invalid request (bad JSON-RPC envelope)
- `-32601` → method not found (check `method` field)
- `-32602` → invalid params (check `skillId` against Agent Card)
- `401` → missing/invalid `OMNIROUTE_KEY`
## Reference
Full docs: https://github.com/diegosouzapw/OmniRoute/blob/main/docs/frameworks/A2A-SERVER.md
<!-- skill:custom-end -->

View File

@@ -0,0 +1,48 @@
---
name: omni-api-keys
description: Create, list, rotate, and revoke OmniRoute API keys. Control per-key scopes, spending limits, and expiration. Keys gate access to all proxy and management endpoints.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Create, list, rotate, and revoke OmniRoute API keys. Control per-key scopes, spending limits, and expiration. Keys gate access to all proxy and management endpoints.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/keys
List API keys
```bash
curl https://localhost:20128/api/keys \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/keys
Create API key
```bash
curl -X POST https://localhost:20128/api/keys \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/keys/{id}
Delete API key
```bash
curl -X DELETE https://localhost:20128/api/keys/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

117
skills/omni-auth/SKILL.md Normal file
View File

@@ -0,0 +1,117 @@
---
name: omni-auth
description: Manage API key authentication and session tokens. Start here to authenticate requests via Bearer token, obtain session cookies, and configure login requirements for the OmniRoute API.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Manage API key authentication and session tokens. Start here to authenticate requests via Bearer token, obtain session cookies, and configure login requirements for the OmniRoute API.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### POST /api/auth/login
Authenticate user
```bash
curl -X POST https://localhost:20128/api/auth/login \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/auth/logout
Log out
```bash
curl -X POST https://localhost:20128/api/auth/logout \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute/SKILL.md (preserved curated content) -->
# OmniRoute
Local/remote AI gateway exposing OpenAI-compatible REST. One key, 207+ providers,
auto-fallback, RTK token saver, MCP server, A2A agents.
## Setup
```bash
export OMNIROUTE_URL="http://localhost:20128" # or VPS / tunnel URL
export OMNIROUTE_KEY="sk-..." # from Dashboard → API Keys
```
All requests: `${OMNIROUTE_URL}/v1/...` with `Authorization: Bearer ${OMNIROUTE_KEY}`.
Verify: `curl $OMNIROUTE_URL/api/health``{"ok":true}`
## Discover models
```bash
curl $OMNIROUTE_URL/v1/models # chat/LLM (default)
curl $OMNIROUTE_URL/v1/models/image # image-gen
curl $OMNIROUTE_URL/v1/models/tts # text-to-speech
curl $OMNIROUTE_URL/v1/models/embedding # embeddings
curl $OMNIROUTE_URL/v1/models/web # web search + fetch
curl $OMNIROUTE_URL/v1/models/stt # speech-to-text
```
Use `data[].id` as `model` field in requests. Combos appear with `owned_by:"combo"`.
## Capability skills
| Capability | Raw URL |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| Chat / code-gen | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-chat/SKILL.md |
| Image generation | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-image/SKILL.md |
| Text-to-speech | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-tts/SKILL.md |
| Speech-to-text | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-stt/SKILL.md |
| Embeddings | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-embeddings/SKILL.md |
| Web search | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-web-search/SKILL.md |
| Web fetch | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-web-fetch/SKILL.md |
| MCP server (37 tools) | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-mcp/SKILL.md |
| A2A protocol | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-a2a/SKILL.md |
| Routing & combos | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-routing/SKILL.md |
| Token compression | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-compression/SKILL.md |
| Monitoring & health | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-monitoring/SKILL.md |
## CLI skills (omniroute binary)
| Capability | Raw URL |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| CLI entry point | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli/SKILL.md |
| CLI admin & lifecycle | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-admin/SKILL.md |
| CLI providers & keys | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-providers/SKILL.md |
| CLI cloud agents | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-cloud/SKILL.md |
| CLI evals & benchmarks | https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute-cli-eval/SKILL.md |
## Errors
- `401` → set/refresh `OMNIROUTE_KEY` (Dashboard → API Keys)
- `400 Invalid model format` → check `model` exists in `/v1/models/<kind>`
- `503 Provider circuit open` → upstream provider down; retry after `Retry-After` seconds
- `429` → rate limited; honor `Retry-After`
## Differentiators vs OpenAI direct
- **Auto-fallback** combos (14 strategies): never stop coding even if a provider rate-limits
- **RTK token saver**: tool_result compressed via 47 specialized filters (git-diff, test-jest, terraform-plan, docker-logs…) — 20-40% token reduction
- **Caveman mode**: optional terse system prompt injection (LITE/FULL/ULTRA) — 15-25% completion reduction
- **MCP + A2A** servers built-in (this is the only AI router that exposes both protocols)
- **Memory** with FTS5 + Qdrant for persistent agent context
- **Guardrails** for PII masking, prompt injection detection, vision policies
<!-- skill:custom-end -->

View File

@@ -0,0 +1,39 @@
---
name: omni-budget
description: Configure spending limits, token quotas, and rate-limit policies per API key or globally. Inspect current consumption and enforce cost controls across providers.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Configure spending limits, token quotas, and rate-limit policies per API key or globally. Inspect current consumption and enforce cost controls across providers.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/rate-limit
Get rate limit configuration
```bash
curl https://localhost:20128/api/rate-limit \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/rate-limit
Update rate limit configuration
```bash
curl -X POST https://localhost:20128/api/rate-limit \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,57 @@
---
name: omni-cache
description: Manage the LLM response cache. View cache statistics, clear entries, configure TTL policies, and control semantic-similarity caching thresholds.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Manage the LLM response cache. View cache statistics, clear entries, configure TTL policies, and control semantic-similarity caching thresholds.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/cache
Get cache statistics
```bash
curl https://localhost:20128/api/cache \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### DELETE /api/cache
Clear all caches
```bash
curl -X DELETE https://localhost:20128/api/cache \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cache/stats
Get detailed cache statistics
Returns detailed statistics for all cache layers.
```bash
curl https://localhost:20128/api/cache/stats \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### DELETE /api/cache/stats
Clear cache statistics
```bash
curl -X DELETE https://localhost:20128/api/cache/stats \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,320 @@
---
name: omni-cli-tools
description: "Manage CLI tool integrations exposed via the API. List, configure, and invoke CLI tool plugins that extend OmniRoute's automation surface."
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Manage CLI tool integrations exposed via the API. List, configure, and invoke CLI tool plugins that extend OmniRoute's automation surface.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/cli-tools/backups
List CLI tool backups
```bash
curl https://localhost:20128/api/cli-tools/backups \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/backups
Create CLI tool backup
```bash
curl -X POST https://localhost:20128/api/cli-tools/backups \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/cli-tools/runtime/{toolId}
Get runtime status for a CLI tool
```bash
curl https://localhost:20128/api/cli-tools/runtime/{toolId} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/guide-settings/{toolId}
Get guide settings for a tool
```bash
curl https://localhost:20128/api/cli-tools/guide-settings/{toolId} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/antigravity-mitm
Get Antigravity MITM proxy settings
```bash
curl https://localhost:20128/api/cli-tools/antigravity-mitm \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/antigravity-mitm
Update Antigravity MITM proxy settings
```bash
curl -X POST https://localhost:20128/api/cli-tools/antigravity-mitm \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/antigravity-mitm
Reset Antigravity MITM proxy settings
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/antigravity-mitm \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/antigravity-mitm/alias
Get Antigravity MITM alias configuration
```bash
curl https://localhost:20128/api/cli-tools/antigravity-mitm/alias \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/cli-tools/antigravity-mitm/alias
Update Antigravity MITM alias configuration
```bash
curl -X PUT https://localhost:20128/api/cli-tools/antigravity-mitm/alias \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/cli-tools/claude-settings
Get Claude CLI settings
```bash
curl https://localhost:20128/api/cli-tools/claude-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/claude-settings
Apply Claude CLI settings
```bash
curl -X POST https://localhost:20128/api/cli-tools/claude-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/claude-settings
Reset Claude CLI settings
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/claude-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/cline-settings
Get Cline CLI settings
```bash
curl https://localhost:20128/api/cli-tools/cline-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/cline-settings
Apply Cline CLI settings
```bash
curl -X POST https://localhost:20128/api/cli-tools/cline-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/cline-settings
Reset Cline CLI settings
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/cline-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/codex-profiles
Get Codex profiles
```bash
curl https://localhost:20128/api/cli-tools/codex-profiles \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/codex-profiles
Create Codex profile
```bash
curl -X POST https://localhost:20128/api/cli-tools/codex-profiles \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### PUT /api/cli-tools/codex-profiles
Update Codex profile
```bash
curl -X PUT https://localhost:20128/api/cli-tools/codex-profiles \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/codex-profiles
Delete Codex profile
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/codex-profiles \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/codex-settings
Get Codex CLI settings
```bash
curl https://localhost:20128/api/cli-tools/codex-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/codex-settings
Apply Codex CLI settings
```bash
curl -X POST https://localhost:20128/api/cli-tools/codex-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/codex-settings
Reset Codex CLI settings
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/codex-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/droid-settings
Get Droid CLI settings
```bash
curl https://localhost:20128/api/cli-tools/droid-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/droid-settings
Apply Droid CLI settings
```bash
curl -X POST https://localhost:20128/api/cli-tools/droid-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/droid-settings
Reset Droid CLI settings
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/droid-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/kilo-settings
Get Kilo CLI settings
```bash
curl https://localhost:20128/api/cli-tools/kilo-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/kilo-settings
Apply Kilo CLI settings
```bash
curl -X POST https://localhost:20128/api/cli-tools/kilo-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/kilo-settings
Reset Kilo CLI settings
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/kilo-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/cli-tools/openclaw-settings
Get OpenClaw CLI settings
```bash
curl https://localhost:20128/api/cli-tools/openclaw-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/cli-tools/openclaw-settings
Apply OpenClaw CLI settings
```bash
curl -X POST https://localhost:20128/api/cli-tools/openclaw-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/cli-tools/openclaw-settings
Reset OpenClaw CLI settings
```bash
curl -X DELETE https://localhost:20128/api/cli-tools/openclaw-settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,241 @@
---
name: omni-combos-routing
description: Create and manage routing combos with 14 strategies (priority, weighted, round-robin, Auto-combo, etc.). Configure fallback chains, test routing outcomes, and retrieve combo metrics.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Create and manage routing combos with 14 strategies (priority, weighted, round-robin, Auto-combo, etc.). Configure fallback chains, test routing outcomes, and retrieve combo metrics.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/combos
List routing combos
```bash
curl https://localhost:20128/api/combos \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/combos
Create routing combo
```bash
curl -X POST https://localhost:20128/api/combos \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### PATCH /api/combos/{id}
Update combo
```bash
curl -X PATCH https://localhost:20128/api/combos/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/combos/{id}
Delete combo
```bash
curl -X DELETE https://localhost:20128/api/combos/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/combos/metrics
Get combo metrics
```bash
curl https://localhost:20128/api/combos/metrics \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/combos/test
Test a combo configuration
```bash
curl -X POST https://localhost:20128/api/combos/test \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/fallback/chains
List fallback chains
Returns all registered fallback chains for model routing.
```bash
curl https://localhost:20128/api/fallback/chains \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/fallback/chains
Create fallback chain
Registers a fallback routing chain for a model.
```bash
curl -X POST https://localhost:20128/api/fallback/chains \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/fallback/chains
Delete fallback chain
```bash
curl -X DELETE https://localhost:20128/api/fallback/chains \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute-routing/SKILL.md (preserved curated content) -->
# OmniRoute — Routing & Combos
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
## What is a combo?
A combo is a named group of providers/models with a routing strategy. All requests through a combo are automatically distributed, failed-over, and load-balanced — the caller uses a single model ID like `my-combo`.
## List existing combos
```bash
curl $OMNIROUTE_URL/api/combos \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
Response includes `id`, `name`, `strategy`, `enabled`, and per-target stats.
## Create a combo
```bash
curl -X POST $OMNIROUTE_URL/api/combos \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-combo",
"strategy": "priority",
"targets": [
{ "provider": "anthropic", "model": "claude-opus-4-7", "weight": 1 },
{ "provider": "openai", "model": "gpt-4o", "weight": 1 }
]
}'
```
## 14 routing strategies
| Strategy | Description |
| ------------------- | -------------------------------------------------- |
| `priority` | Always use target[0]; fall back on error |
| `weighted` | Distribute by weight percentage |
| `round-robin` | Rotate targets in order |
| `fill-first` | Fill quota of target[0] before spilling |
| `least-used` | Route to target with fewest active requests |
| `cost-optimized` | Pick cheapest target for the token estimate |
| `auto` | 9-factor scoring: cost + latency + quota + circuit |
| `random` | Uniform random selection |
| `strict-random` | Random without repeating until all used |
| `p2c` | Power-of-2-choices: sample 2, pick better |
| `reset-aware` | Prefer targets near quota reset time |
| `lkgp` | Last-known-good-provider sticky routing |
| `context-optimized` | Pick best model for context length |
| `context-relay` | Chain models for very long contexts |
## Auto-combo (recommended for production)
Auto-combo scores each candidate on 9 factors every request:
```bash
curl -X POST $OMNIROUTE_URL/api/combos \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-auto",
"strategy": "auto",
"targets": [
{ "provider": "anthropic", "model": "claude-sonnet-4-6" },
{ "provider": "openai", "model": "gpt-4o-mini" },
{ "provider": "google", "model": "gemini-2.0-flash" }
]
}'
```
Then call it with:
```bash
curl -X POST $OMNIROUTE_URL/v1/chat/completions \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{ "model": "prod-auto", "messages": [{ "role": "user", "content": "Hello" }] }'
```
## Activate / deactivate a combo
```bash
# Activate
curl -X PUT $OMNIROUTE_URL/api/combos/{id}/toggle \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-d '{ "enabled": true }'
```
## Get combo metrics
```bash
curl $OMNIROUTE_URL/api/combos/{id}/metrics \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
Returns p50/p95/p99 latency, success rate, cost, and per-target breakdown.
## Simulate routing (dry run)
```bash
curl -X POST $OMNIROUTE_URL/api/routing/simulate \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{ "comboId": "{id}", "messages": [{ "role": "user", "content": "test" }] }'
```
Returns which provider would be selected and why — no actual API call is made.
## Via MCP (if OmniRoute is your MCP server)
```
omniroute_list_combos → list all combos
omniroute_switch_combo → enable/disable a combo
omniroute_set_routing_strategy → change strategy at runtime
omniroute_simulate_route → dry-run routing decision
omniroute_best_combo_for_task → get recommendation by task type
```
## Errors
- `404 combo not found` → check `id` from `/api/combos`
- `400 invalid strategy` → use one of the 14 strategies above
- `409 name conflict` → combo name already exists
<!-- skill:custom-end -->

View File

@@ -0,0 +1,179 @@
---
name: omni-compression
description: Configure RTK (command output), Caveman (prose), and stacked compression modes. Manage language packs, custom rules, and test prompt compression reducing tokens by 6090%.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Configure RTK (command output), Caveman (prose), and stacked compression modes. Manage language packs, custom rules, and test prompt compression reducing tokens by 6090%.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### POST /api/compression/preview
Preview compression for a message payload
```bash
curl -X POST https://localhost:20128/api/compression/preview \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/compression/language-packs
List Caveman compression language packs
```bash
curl https://localhost:20128/api/compression/language-packs \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/compression/rules
List Caveman compression rule metadata
```bash
curl https://localhost:20128/api/compression/rules \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute-compression/SKILL.md (preserved curated content) -->
# OmniRoute — Compression
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
## Overview
OmniRoute compresses token payloads before forwarding to providers. No code changes required — set it once, it applies to all requests transparently.
| Engine | Best for | Typical savings |
| ------------------------- | ------------------------------------ | --------------- |
| RTK | Terminal / build / test / git output | 6090% |
| Caveman | Human prose, chat history | 46% input |
| Stacked (`rtk → caveman`) | Mixed coding sessions | 7895% |
| MCP accessibility filter | Browser/accessibility tool results | 6080% |
## Get current settings
```bash
curl $OMNIROUTE_URL/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
## Enable RTK (best for coding agents)
```bash
curl -X PUT $OMNIROUTE_URL/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{ "mode": "rtk", "enabled": true }'
```
## Enable stacked mode (maximum savings)
```bash
curl -X PUT $OMNIROUTE_URL/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "stacked",
"enabled": true,
"stackedPipeline": ["rtk", "caveman"]
}'
```
## Enable Caveman (prose / chat)
```bash
curl -X PUT $OMNIROUTE_URL/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{ "mode": "standard", "enabled": true }'
```
Caveman intensities: `lite` (safe), `standard` (balanced), `aggressive` (long sessions), `ultra` (context recovery).
## Preview compression before enabling
```bash
curl -X POST $OMNIROUTE_URL/api/compression/preview \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "rtk",
"text": "$ npm test\n> jest\n\nPASS src/a.test.ts (2.1s)\nPASS src/b.test.ts (1.8s)\n..."
}'
```
Response includes `compressed`, `original_length`, `compressed_length`, `savings_pct`.
## MCP accessibility-tree filter (browser agent use)
When OmniRoute is used with browser/Playwright MCP tools, it automatically compresses verbose accessibility-tree tool results. Enabled by default; configure thresholds:
```bash
curl -X PUT $OMNIROUTE_URL/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"mcpAccessibility": {
"enabled": true,
"collapseThreshold": 30,
"maxTextChars": 50000
}
}'
```
`collapseThreshold`: collapse sibling lines when ≥ N repeats (default 30).
`maxTextChars`: hard truncate after N chars with navigation hint (default 50000).
## Language packs (Caveman)
Caveman supports language-aware rules for pt-BR, es, de, fr, ja:
```bash
curl -X PUT $OMNIROUTE_URL/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "standard",
"cavemanConfig": {
"language": "pt-BR",
"autoDetectLanguage": true
}
}'
```
## Via MCP
```
omniroute_compression_status → current settings + savings analytics
omniroute_compression_configure → update mode/threshold/language
omniroute_set_compression_engine → switch engine at runtime
```
## Disable compression
```bash
curl -X PUT $OMNIROUTE_URL/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-d '{ "enabled": false }'
```
## Errors
- `400 invalid mode` → use `off`, `lite`, `standard`, `aggressive`, `ultra`, `rtk`, or `stacked`
- `400 invalid stackedPipeline` → array must contain valid engine ids (`rtk`, `caveman`)
<!-- skill:custom-end -->

View File

@@ -0,0 +1,68 @@
---
name: omni-context-rtk
description: Configure RTK filters, context engineering rules, and context relay settings. Test compression with real prompt samples and manage context transformation pipelines.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Configure RTK filters, context engineering rules, and context relay settings. Test compression with real prompt samples and manage context transformation pipelines.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/context/rtk/config
Get RTK compression settings
```bash
curl https://localhost:20128/api/context/rtk/config \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/context/rtk/config
Update RTK compression settings
```bash
curl -X PUT https://localhost:20128/api/context/rtk/config \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/context/rtk/filters
List RTK filters and load diagnostics
```bash
curl https://localhost:20128/api/context/rtk/filters \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/context/rtk/test
Run RTK compression preview for text
```bash
curl -X POST https://localhost:20128/api/context/rtk/test \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/context/rtk/raw-output/{id}
Read retained redacted RTK raw output
```bash
curl https://localhost:20128/api/context/rtk/raw-output/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,20 @@
---
name: omni-db-backups
description: Trigger system backups, restore from backup files, and manage the SQLite database lifecycle. Supports export, import, and incremental snapshot strategies.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Trigger system backups, restore from backup files, and manage the SQLite database lifecycle. Supports export, import, and incremental snapshot strategies.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
_No endpoints mapped for this area yet._
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,530 @@
---
name: omni-inference
description: "The core OpenAI-compatible inference endpoints: chat completions, embeddings, images, audio (TTS/STT), moderations, rerank, and the Responses API. The primary integration surface for AI agents."
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
The core OpenAI-compatible inference endpoints: chat completions, embeddings, images, audio (TTS/STT), moderations, rerank, and the Responses API. The primary integration surface for AI agents.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### POST /api/v1/chat/completions
Create chat completion
OpenAI-compatible chat completions endpoint. Routes to configured providers.
```bash
curl -X POST https://localhost:20128/api/v1/chat/completions \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/providers/{provider}/chat/completions
Create chat completion (provider-specific)
Routes to a specific provider by name.
```bash
curl -X POST https://localhost:20128/api/v1/providers/{provider}/chat/completions \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/api/chat
Ollama-compatible chat endpoint
Provides compatibility with Ollama's /api/chat format.
```bash
curl -X POST https://localhost:20128/api/v1/api/chat \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/messages
Create message (Anthropic-compatible)
Anthropic Messages API endpoint. Routes to Claude providers.
```bash
curl -X POST https://localhost:20128/api/v1/messages \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/messages/count_tokens
Count tokens for a message
```bash
curl -X POST https://localhost:20128/api/v1/messages/count_tokens \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/responses
Create response (OpenAI Responses API)
OpenAI Responses API endpoint.
```bash
curl -X POST https://localhost:20128/api/v1/responses \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/embeddings
Create embeddings
```bash
curl -X POST https://localhost:20128/api/v1/embeddings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/providers/{provider}/embeddings
Create embeddings (provider-specific)
```bash
curl -X POST https://localhost:20128/api/v1/providers/{provider}/embeddings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/images/generations
Generate images
```bash
curl -X POST https://localhost:20128/api/v1/images/generations \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/providers/{provider}/images/generations
Generate images (provider-specific)
```bash
curl -X POST https://localhost:20128/api/v1/providers/{provider}/images/generations \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/audio/speech
Generate speech audio
Text-to-speech endpoint. Routes to configured TTS providers.
```bash
curl -X POST https://localhost:20128/api/v1/audio/speech \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/audio/transcriptions
Transcribe audio
Audio-to-text transcription endpoint.
```bash
curl -X POST https://localhost:20128/api/v1/audio/transcriptions \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/moderations
Create moderation
Content moderation endpoint. Routes to configured moderation providers.
```bash
curl -X POST https://localhost:20128/api/v1/moderations \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/v1/rerank
Rerank documents
Document reranking endpoint.
```bash
curl -X POST https://localhost:20128/api/v1/rerank \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/v1
API v1 root endpoint
Returns basic API info and status.
```bash
curl https://localhost:20128/api/v1 \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/v1/providers/{provider}/models
List models for a specific provider
Returns only models for the selected provider with provider prefix removed from each model id.
```bash
curl https://localhost:20128/api/v1/providers/{provider}/models \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
<!-- Aggregated from: omniroute-chat, omniroute-image, omniroute-tts, omniroute-stt, omniroute-embeddings, omniroute-web-search, omniroute-web-fetch -->
## Chat completions
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
### Endpoints
- `POST $OMNIROUTE_URL/v1/chat/completions` — OpenAI format
- `POST $OMNIROUTE_URL/v1/messages` — Anthropic Messages format
- `POST $OMNIROUTE_URL/v1/responses` — OpenAI Responses API
### Discover
```bash
curl $OMNIROUTE_URL/v1/models | jq '.data[].id'
```
Combos (e.g. `auto`, `cost-optimized`, `subscription`) auto-fallback through multiple providers.
### OpenAI format example
```bash
curl -X POST $OMNIROUTE_URL/v1/chat/completions \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-4-7",
"messages": [{"role": "user", "content": "Refactor this function"}],
"stream": true
}'
```
### Anthropic format example
```bash
curl -X POST $OMNIROUTE_URL/v1/messages \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-4-7",
"max_tokens": 4096,
"messages": [{"role": "user", "content": "Hi"}]
}'
```
### Tool use
Supports OpenAI `tools` array and Anthropic `tools` block. Tool results
auto-compressed via RTK (47 filters: git-diff, grep, test-jest, terraform-plan,
docker-logs, etc.) — 20-40% token savings. Disable per-request with
`X-Omniroute-Rtk: off` header.
### Reasoning / thinking
Anthropic extended thinking and OpenAI Responses reasoning blocks are forwarded
verbatim. Cached automatically via reasoning cache.
### Errors
- `401` → invalid API key
- `400 invalid_model` → model not in registry; check `/v1/models`
- `503 circuit_open` → provider circuit breaker tripped; retry later or use combo
- `429 rate_limited` → honor `Retry-After`; consider using a combo for auto-fallback
## Image generation
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
### Endpoints
- `POST $OMNIROUTE_URL/v1/images/generations` — Text-to-image
- `POST $OMNIROUTE_URL/v1/images/edits` — Image edit (mask)
- `POST $OMNIROUTE_URL/v1/images/variations` — Variations
### Discover
```bash
curl $OMNIROUTE_URL/v1/models/image | jq '.data[]'
```
Returns `{ id, owned_by, sizes:[...], capabilities:[...] }` per model.
### Generate example
```bash
curl -X POST $OMNIROUTE_URL/v1/images/generations \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "dall-e-3",
"prompt": "a red bicycle on a wet street, photoreal",
"n": 1,
"size": "1024x1024",
"response_format": "b64_json"
}'
```
Response: `{ created, data: [{ url? or b64_json, revised_prompt }] }`
### Errors
- `400 invalid_size` → not supported by this model; check `/v1/models/image`
- `400 content_policy_violation` → blocked by provider safety
- `503` → provider unavailable; try another model in `/v1/models/image`
## Text-to-speech
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
### Endpoint
- `POST $OMNIROUTE_URL/v1/audio/speech` — returns binary audio (mp3/opus/wav/flac)
### Discover
```bash
curl $OMNIROUTE_URL/v1/models/tts | jq '.data[]'
```
Each entry includes `voices:[...]` for the available voice names per provider.
### Example
```bash
curl -X POST $OMNIROUTE_URL/v1/audio/speech \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "tts-1",
"input": "Hello from OmniRoute.",
"voice": "alloy",
"response_format": "mp3"
}' --output speech.mp3
```
### Voices
Voice names vary by provider. Check `/v1/models/tts` — each entry has `voices:[...]`.
Common OpenAI voices: `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`.
### Errors
- `400 invalid_voice` → voice not supported by this model
- `400 input_too_long` → input exceeds model character limit
- `503` → provider unavailable; try another model in `/v1/models/tts`
## Speech-to-text
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
### Endpoints
- `POST $OMNIROUTE_URL/v1/audio/transcriptions` — multipart upload, returns text
- `POST $OMNIROUTE_URL/v1/audio/translations` — transcribe + translate to English
### Discover
```bash
curl $OMNIROUTE_URL/v1/models/stt | jq '.data[]'
```
### Example
```bash
curl -X POST $OMNIROUTE_URL/v1/audio/transcriptions \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-F "file=@audio.mp3" \
-F "model=whisper-1" \
-F "response_format=verbose_json"
```
Response: `{ text, language, duration, segments?:[{ start, end, text }] }`
### Supported formats
Audio: `mp3`, `mp4`, `mpeg`, `mpga`, `m4a`, `wav`, `webm`.
Response formats: `json`, `text`, `srt`, `verbose_json`, `vtt`.
### Errors
- `400 invalid_file_format` → unsupported audio format
- `400 file_too_large` → exceeds provider limit (usually 25MB)
- `503` → provider unavailable; try another model in `/v1/models/stt`
## Embeddings
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
### Endpoint
- `POST $OMNIROUTE_URL/v1/embeddings`
### Discover
```bash
curl $OMNIROUTE_URL/v1/models/embedding | jq '.data[]'
```
Each entry: `{ id, owned_by, dimensions, max_input_tokens }`.
### Example
```bash
curl -X POST $OMNIROUTE_URL/v1/embeddings \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "text-embedding-3-large",
"input": ["first text", "second text"],
"encoding_format": "float"
}'
```
Response: `{ data:[{ embedding:[...], index }], usage:{ prompt_tokens, total_tokens } }`
### Batch input
`input` accepts a string or array of strings (up to provider batch limit, typically 2048 items).
### Errors
- `400 input_too_long` → input exceeds `max_input_tokens` for this model
- `400 invalid_encoding_format` → use `float` or `base64`
- `503` → provider unavailable; try another model in `/v1/models/embedding`
## Web search
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
### Endpoint
- `POST $OMNIROUTE_URL/v1/web/search` — unified search format
### Discover
```bash
curl $OMNIROUTE_URL/v1/models/web | jq '.data[] | select(.kind == "webSearch")'
```
### Example
```bash
curl -X POST $OMNIROUTE_URL/v1/web/search \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "tavily/search",
"query": "OmniRoute github latest release",
"max_results": 5,
"include_answer": true
}'
```
Response: `{ answer?, results:[{ url, title, content, score }] }`
### Parameters
| Field | Type | Description |
| ---------------- | ------- | ------------------------------------ |
| `model` | string | Provider model from `/v1/models/web` |
| `query` | string | Search query |
| `max_results` | number | Max results (default: 5) |
| `include_answer` | boolean | Include AI-synthesized answer |
| `search_depth` | string | `basic` or `advanced` (Tavily) |
### Errors
- `400 query_too_long` → shorten the search query
- `503` → provider unavailable; try another model in `/v1/models/web`
## Web fetch
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
### Endpoint
- `POST $OMNIROUTE_URL/v1/web/fetch`
### Discover
```bash
curl $OMNIROUTE_URL/v1/models/web | jq '.data[] | select(.kind == "webFetch")'
```
### Example
```bash
curl -X POST $OMNIROUTE_URL/v1/web/fetch \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jina/reader",
"url": "https://anthropic.com",
"format": "markdown"
}'
```
Response: `{ url, title, markdown, links?:[...], images?:[...] }`
### Parameters
| Field | Type | Description |
| -------- | ------ | ----------------------------------------------------------------------- |
| `model` | string | Provider from `/v1/models/web` (e.g. `jina/reader`, `firecrawl/scrape`) |
| `url` | string | URL to fetch |
| `format` | string | `markdown` (default), `html`, `text` |
### Errors
- `400 invalid_url` → URL must be http/https
- `403 blocked` → provider blocked by target site; try a different model
- `503` → provider unavailable; try another model in `/v1/models/web`
<!-- skill:custom-end -->

91
skills/omni-mcp/SKILL.md Normal file
View File

@@ -0,0 +1,91 @@
---
name: omni-mcp
description: "Connect to the OmniRoute MCP server (37 tools, 3 transports: SSE/stdio/HTTP). Covers routing, cache, compression, memory, skills, providers, and audit tools across 16 permission scopes."
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Connect to the OmniRoute MCP server (37 tools, 3 transports: SSE/stdio/HTTP). Covers routing, cache, compression, memory, skills, providers, and audit tools across 16 permission scopes.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
_No endpoints mapped for this area yet._
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute-mcp/SKILL.md (preserved curated content) -->
# OmniRoute — MCP Server
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
## Transports
- **stdio** — local IPC, for Claude Desktop / VS Code extensions
- **SSE** — `GET $OMNIROUTE_URL/api/mcp/sse`
- **Streamable HTTP** — `POST $OMNIROUTE_URL/api/mcp/stream`
## Claude Desktop config
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"omniroute": {
"command": "npx",
"args": ["-y", "omniroute", "--mcp"],
"env": { "OMNIROUTE_KEY": "sk-..." }
}
}
}
```
## Cursor / VS Code config
```json
{
"mcp": {
"servers": {
"omniroute": {
"url": "http://localhost:20128/api/mcp/sse",
"headers": { "Authorization": "Bearer sk-..." }
}
}
}
}
```
## Available tools (37 total)
| Scope | Tools |
| --------- | -------------------------------------------------------------------------------------------------- |
| health | `omniroute_get_health` |
| combos | `omniroute_list_combos`, `omniroute_get_combo_metrics`, `omniroute_switch_combo` |
| routing | `omniroute_simulate_route`, `omniroute_best_combo_for_task`, `omniroute_explain_route` |
| providers | `omniroute_get_provider_metrics`, `omniroute_check_quota`, `omniroute_route_request` |
| budget | `omniroute_set_budget_guard`, `omniroute_set_routing_strategy`, `omniroute_set_resilience_profile` |
| testing | `omniroute_test_combo` |
| memory | `memory_add`, `memory_search`, `memory_delete` |
| skills | `skill_invoke`, `skill_list`, `skill_describe`, `skill_register` |
| cache | `omniroute_cache_stats`, `omniroute_cache_flush` |
| admin | `omniroute_db_health_check`, `omniroute_sync_pricing`, `omniroute_get_session_snapshot` |
Full list: `GET $OMNIROUTE_URL/api/mcp/tools`
## Scopes
Tools are grouped into 13 scopes (chat-only, memory-readonly, full-admin, etc.).
Pass scope name as `--scope` arg or via `X-Omniroute-Scope` header.
## Reference
Full docs: https://github.com/diegosouzapw/OmniRoute/blob/main/docs/frameworks/MCP-SERVER.md
<!-- skill:custom-end -->

View File

@@ -0,0 +1,59 @@
---
name: omni-models
description: Query available AI models across all configured providers. List models, resolve model aliases, and browse the full model catalog including provider-specific variants.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Query available AI models across all configured providers. List models, resolve model aliases, and browse the full model catalog including provider-specific variants.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/v1/models
List available models
Returns all models available across configured providers.
```bash
curl https://localhost:20128/api/v1/models \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/models
List models (management)
```bash
curl https://localhost:20128/api/models \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/models/alias
Create or update a model alias
```bash
curl -X POST https://localhost:20128/api/models/alias \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/models/catalog
Get full model catalog
```bash
curl https://localhost:20128/api/models/catalog \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,229 @@
---
name: omni-providers
description: Manage provider connections, API keys, OAuth flows, and connection tests via the REST API. List, add, update, remove, and test AI provider integrations (OpenAI, Anthropic, Gemini, and 160+).
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Manage provider connections, API keys, OAuth flows, and connection tests via the REST API. List, add, update, remove, and test AI provider integrations (OpenAI, Anthropic, Gemini, and 160+).
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/providers
List provider connections
```bash
curl https://localhost:20128/api/providers \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/providers
Create provider connection
```bash
curl -X POST https://localhost:20128/api/providers \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/providers/{id}
Get provider connection
```bash
curl https://localhost:20128/api/providers/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PATCH /api/providers/{id}
Update provider connection
```bash
curl -X PATCH https://localhost:20128/api/providers/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/providers/{id}
Delete provider connection
```bash
curl -X DELETE https://localhost:20128/api/providers/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/providers/{id}/test
Test provider connection
```bash
curl -X POST https://localhost:20128/api/providers/{id}/test \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/providers/{id}/models
List models for a provider
```bash
curl https://localhost:20128/api/providers/{id}/models \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/providers/test-batch
Test multiple providers at once
```bash
curl -X POST https://localhost:20128/api/providers/test-batch \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/providers/validate
Validate provider credentials
```bash
curl -X POST https://localhost:20128/api/providers/validate \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/providers/client
Get client-side provider info
```bash
curl https://localhost:20128/api/providers/client \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/provider-nodes
List provider nodes
```bash
curl https://localhost:20128/api/provider-nodes \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/provider-nodes
Create provider node
```bash
curl -X POST https://localhost:20128/api/provider-nodes \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### PATCH /api/provider-nodes/{id}
Update provider node
```bash
curl -X PATCH https://localhost:20128/api/provider-nodes/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### DELETE /api/provider-nodes/{id}
Delete provider node
```bash
curl -X DELETE https://localhost:20128/api/provider-nodes/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/provider-nodes/validate
Validate a provider node
```bash
curl -X POST https://localhost:20128/api/provider-nodes/validate \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/provider-models
List provider models
```bash
curl https://localhost:20128/api/provider-models \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
## Additional endpoints
### POST /api/providers/{id}/refresh
Rotate / refresh OAuth tokens or re-validate credentials for a provider connection. Useful after token expiration or when credentials need to be renewed without deleting the connection.
```bash
curl -X POST https://localhost:20128/api/providers/{id}/refresh \
-H "Authorization: Bearer $OMNIROUTE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/provider-metrics
Retrieve aggregated usage and performance metrics for all provider connections (request counts, error rates, latency percentiles, token usage). Used by dashboards and Auto-Combo scoring.
```bash
curl https://localhost:20128/api/provider-metrics \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
**Response example:**
```json
{
"providers": [
{
"id": "my-openai",
"requestCount": 1240,
"errorRate": 0.02,
"p50LatencyMs": 340,
"p99LatencyMs": 1200,
"tokensIn": 500000,
"tokensOut": 250000
}
]
}
```
### GET /api/providers/health-matrix
Returns a health-matrix view of all providers with per-connection status, circuit-breaker state, and cooldown expiry. Useful for debugging routing decisions.
```bash
curl https://localhost:20128/api/providers/health-matrix \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
<!-- skill:custom-end -->

View File

@@ -0,0 +1,20 @@
---
name: omni-proxies
description: Configure HTTP/HTTPS/SOCKS proxies for upstream provider requests. Set per-provider or global proxy rules, test connectivity, and manage proxy rotation.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Configure HTTP/HTTPS/SOCKS proxies for upstream provider requests. Set per-provider or global proxy rules, test connectivity, and manage proxy rotation.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
_No endpoints mapped for this area yet._
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,144 @@
---
name: omni-resilience
description: Monitor provider health, circuit-breaker states, p50/p95/p99 latency metrics, and budget guard alerts. Inspect connection cooldowns and model lockouts in real time.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Monitor provider health, circuit-breaker states, p50/p95/p99 latency metrics, and budget guard alerts. Inspect connection cooldowns and model lockouts in real time.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/monitoring/health
System health check
Returns system health including uptime, memory, circuit breakers, rate limits
```bash
curl https://localhost:20128/api/monitoring/health \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.
<!-- skill:custom-start -->
<!-- Migrated from skills/omniroute-monitoring/SKILL.md (preserved curated content) -->
# OmniRoute — Monitoring & Health
Requires `OMNIROUTE_URL` and `OMNIROUTE_KEY`. See [entry-point SKILL](https://raw.githubusercontent.com/diegosouzapw/OmniRoute/main/skills/omniroute/SKILL.md) for setup.
## System health
```bash
curl $OMNIROUTE_URL/api/health \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
Returns: uptime, memory, active connections, circuit breaker states, rate limit status, cache stats.
Unauthenticated quick check:
```bash
curl $OMNIROUTE_URL/api/health
# → {"ok":true}
```
## Provider circuit breakers
Circuit breakers prevent traffic from hitting failing providers.
States: `CLOSED` (normal), `OPEN` (blocked), `HALF_OPEN` (probe mode — auto-recovers).
```bash
curl $OMNIROUTE_URL/api/monitoring/health \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
Response includes `circuitBreakers` array with per-provider state and `resetAt` timestamp.
## Per-provider metrics (p50/p95/p99)
```bash
curl $OMNIROUTE_URL/api/providers/metrics \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
Response shape per provider:
```json
{
"provider": "anthropic",
"requests": 1247,
"successRate": 0.994,
"latency": { "p50": 820, "p95": 2100, "p99": 3800 },
"circuitState": "CLOSED",
"tokensUsed": 2847000
}
```
## Via MCP (if OmniRoute is your MCP server)
```
omniroute_get_health → full system health snapshot
omniroute_get_provider_metrics → p50/p95/p99 + circuit state per provider
omniroute_get_session_snapshot → cost, tokens, errors for current session
omniroute_check_quota → quota balance + percent remaining + reset time
omniroute_db_health_check → diagnose + auto-repair database drift
```
## Quota check
```bash
curl $OMNIROUTE_URL/api/quota \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
Returns used/total tokens and requests per provider/account, with `resetAt` timestamps.
## Budget guard (spend limit)
Set a session spending limit that degrades or blocks requests when hit:
```bash
curl -X POST $OMNIROUTE_URL/api/budget/guard \
-H "Authorization: Bearer $OMNIROUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"limitUsd": 5.00,
"action": "degrade",
"degradeTo": "openai/gpt-4o-mini"
}'
```
`action` options:
- `degrade` — switch to a cheaper model when limit is hit
- `block` — return 429 when limit is hit
- `alert` — continue but add `X-Budget-Warning` header
## MCP audit log
OmniRoute logs every MCP tool call to `mcp_audit` table. Query via API:
```bash
curl "$OMNIROUTE_URL/api/mcp/status" \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
Returns: server status, heartbeat, recent audit activity summary.
## Errors
- `503` on health endpoint → OmniRoute is starting up; retry in 5s
- Circuit breaker `OPEN` → provider is temporarily blocked; check `resetAt` to know when it auto-recovers
- `429 budget_exceeded` → budget guard limit reached; raise limit or wait for reset
<!-- skill:custom-end -->

View File

@@ -0,0 +1,222 @@
---
name: omni-settings
description: "Read and update global application settings: system prompts, thinking budget, IP filters, payload rules, combo defaults, and require-login configuration."
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Read and update global application settings: system prompts, thinking budget, IP filters, payload rules, combo defaults, and require-login configuration.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/settings
Get application settings
```bash
curl https://localhost:20128/api/settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PATCH /api/settings
Update settings
```bash
curl -X PATCH https://localhost:20128/api/settings \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/settings/compression
Get global compression settings
```bash
curl https://localhost:20128/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/settings/compression
Update global compression settings
```bash
curl -X PUT https://localhost:20128/api/settings/compression \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/settings/payload-rules
Get payload rules configuration
Returns the current payload rules used to mutate outgoing request payloads before they
are sent upstream.
Requires a dashboard management session cookie when management auth is enabled.
```bash
curl https://localhost:20128/api/settings/payload-rules \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/settings/payload-rules
Update payload rules configuration
Persists and hot reloads payload rules. The legacy input field `default-raw` is accepted
on writes and normalized to `defaultRaw` in responses/runtime state.
Requires a dashboard management session cookie when management auth is enabled.
```bash
curl -X PUT https://localhost:20128/api/settings/payload-rules \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/settings/combo-defaults
Get combo default settings
```bash
curl https://localhost:20128/api/settings/combo-defaults \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/settings/proxy
Get proxy settings
```bash
curl https://localhost:20128/api/settings/proxy \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PATCH /api/settings/proxy
Update proxy settings
```bash
curl -X PATCH https://localhost:20128/api/settings/proxy \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/settings/proxy/test
Test proxy connection
```bash
curl -X POST https://localhost:20128/api/settings/proxy/test \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/settings/require-login
Toggle login requirement
```bash
curl -X POST https://localhost:20128/api/settings/require-login \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/settings/ip-filter
Get IP filter configuration
Returns the current IP filter settings including blacklist, whitelist, and temp bans.
```bash
curl https://localhost:20128/api/settings/ip-filter \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/settings/ip-filter
Update IP filter configuration
Configure IP filtering with blacklist/whitelist modes, add/remove individual IPs, and manage temp bans.
```bash
curl -X PUT https://localhost:20128/api/settings/ip-filter \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/settings/system-prompt
Get system prompt configuration
Returns the current system prompt injection settings.
```bash
curl https://localhost:20128/api/settings/system-prompt \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/settings/system-prompt
Update system prompt configuration
```bash
curl -X PUT https://localhost:20128/api/settings/system-prompt \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/settings/thinking-budget
Get thinking budget configuration
Returns the current thinking/reasoning budget settings for AI models.
```bash
curl https://localhost:20128/api/settings/thinking-budget \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/settings/thinking-budget
Update thinking budget configuration
```bash
curl -X PUT https://localhost:20128/api/settings/thinking-budget \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/tags
List Ollama-compatible model tags
Returns models in Ollama /api/tags format for Ollama client compatibility
```bash
curl https://localhost:20128/api/tags \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,98 @@
---
name: omni-sync-cloud
description: Synchronise OmniRoute configuration, provider connections, and settings to/from cloud storage. Manage cloud worker authentication and remote backup targets.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Synchronise OmniRoute configuration, provider connections, and settings to/from cloud storage. Manage cloud worker authentication and remote backup targets.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### POST /api/cloud/auth
Authenticate with cloud worker
Authenticates with the OmniRoute cloud worker for remote access.
```bash
curl -X POST https://localhost:20128/api/cloud/auth \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### PUT /api/cloud/credentials/update
Update cloud worker credentials
```bash
curl -X PUT https://localhost:20128/api/cloud/credentials/update \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/cloud/model/resolve
Resolve model via cloud
Resolves a model request through the cloud worker.
```bash
curl -X POST https://localhost:20128/api/cloud/model/resolve \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/cloud/models/alias
Get cloud model aliases
```bash
curl https://localhost:20128/api/cloud/models/alias \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### PUT /api/cloud/models/alias
Update cloud model alias
```bash
curl -X PUT https://localhost:20128/api/cloud/models/alias \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/sync/cloud
Sync with cloud
```bash
curl -X POST https://localhost:20128/api/sync/cloud \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/sync/initialize
Initialize cloud sync
```bash
curl -X POST https://localhost:20128/api/sync/initialize \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,20 @@
---
name: omni-tunnels
description: Create and manage secure tunnels (ngrok, Cloudflare Tunnel, custom) to expose OmniRoute to the internet or share access with remote agents and CI pipelines.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Create and manage secure tunnels (ngrok, Cloudflare Tunnel, custom) to expose OmniRoute to the internet or share access with remote agents and CI pipelines.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
_No endpoints mapped for this area yet._
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,115 @@
---
name: omni-usage-logs
description: Access detailed call logs and usage analytics. Filter by provider, model, time range, status, and cost. Export logs and aggregate token usage across all connections.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Access detailed call logs and usage analytics. Filter by provider, model, time range, status, and cost. Export logs and aggregate token usage across all connections.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### GET /api/usage/analytics
Get usage analytics
```bash
curl https://localhost:20128/api/usage/analytics \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/call-logs
Get call logs
```bash
curl https://localhost:20128/api/usage/call-logs \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/call-logs/{id}
Get a specific call log
```bash
curl https://localhost:20128/api/usage/call-logs/{id} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/{connectionId}
Get usage for a specific connection
```bash
curl https://localhost:20128/api/usage/{connectionId} \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/history
Get usage history
```bash
curl https://localhost:20128/api/usage/history \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/logs
Get usage logs
```bash
curl https://localhost:20128/api/usage/logs \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/proxy-logs
Get proxy logs
```bash
curl https://localhost:20128/api/usage/proxy-logs \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/request-logs
Get request logs
```bash
curl https://localhost:20128/api/usage/request-logs \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/usage/budget
Get usage budget status
Returns current budget limits and consumption.
```bash
curl https://localhost:20128/api/usage/budget \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/usage/budget
Configure usage budget
Set or update budget limits for usage tracking.
```bash
curl -X POST https://localhost:20128/api/usage/budget \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,221 @@
---
name: omni-version-manager
description: Install, start, stop, restart, and update embedded services (9Router, CLIProxyAPI). Monitor service status, retrieve logs, and configure auto-start for local-only service endpoints.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Install, start, stop, restart, and update embedded services (9Router, CLIProxyAPI). Monitor service status, retrieve logs, and configure auto-start for local-only service endpoints.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
### POST /api/services/9router/install
Install 9Router from npm
Installs the `9router` npm package under DATA_DIR/services/9router/. Uses execFile (no shell interpolation — hard rule #13). **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/install \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/9router/start
Start 9Router
Spawns the 9Router process. Idempotent if already running. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/start \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/9router/stop
Stop 9Router
Gracefully stops 9Router (SIGTERM → 15 s → SIGKILL). Idempotent. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/stop \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/9router/restart
Restart 9Router
Equivalent to stop() then start() under the operation lock. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/restart \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/9router/update
Update 9Router to a newer npm version
Stops the service (if running), installs the newer npm version, then restarts. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/update \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/9router/rotate-key
Rotate the 9Router API key
Generates a new API key, encrypts it at-rest, and restarts the service to apply it. The plaintext key is never returned. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/rotate-key \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/services/9router/status
Get 9Router status
Returns combined live supervisor state and DB metadata. **LOCAL_ONLY** — loopback only.
```bash
curl https://localhost:20128/api/services/9router/status \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/services/9router/auto-start
Toggle 9Router auto-start
When enabled, 9Router starts automatically on the next OmniRoute boot. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/auto-start \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/cliproxy/install
Install CLIProxyAPI from npm
Installs the CLIProxyAPI package under DATA_DIR/services/cliproxy/. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/cliproxy/install \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/cliproxy/start
Start CLIProxyAPI
Spawns the CLIProxyAPI process. Idempotent if already running. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/cliproxy/start \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/cliproxy/stop
Stop CLIProxyAPI
Gracefully stops CLIProxyAPI. Idempotent. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/cliproxy/stop \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/cliproxy/restart
Restart CLIProxyAPI
stop() then start() under the operation lock. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/cliproxy/restart \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/cliproxy/update
Update CLIProxyAPI to a newer npm version
Stops, installs newer version, restarts. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/cliproxy/update \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/services/cliproxy/status
Get CLIProxyAPI status
Returns live supervisor state and DB metadata (no apiKeyMasked — CLIProxyAPI does not use an injected API key). **LOCAL_ONLY** — loopback only.
```bash
curl https://localhost:20128/api/services/cliproxy/status \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/services/cliproxy/auto-start
Toggle CLIProxyAPI auto-start
When enabled, CLIProxyAPI starts automatically on the next OmniRoute boot. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/cliproxy/auto-start \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/services/{name}/logs
Stream service logs via SSE
Returns a Server-Sent Events stream from the service's in-memory ring buffer (5 MB, circular). Sends a `snapshot` event with historical lines first, then live `log` events, plus a `heartbeat` every 15 s. **LOCAL_ONLY** — loopback only.
```bash
curl https://localhost:20128/api/services/{name}/logs \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,20 @@
---
name: omni-webhooks
description: Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, provider.error, budget.exceeded, etc.) and manage delivery retries.
---
<!-- generated by src/lib/agentSkills/generator.ts; manual edits will be overwritten -->
## Overview
Register, list, test, and remove webhook endpoints. Configure event subscriptions (request.completed, provider.error, budget.exceeded, etc.) and manage delivery retries.
## Authentication
All requests require a valid Bearer token or session cookie. Obtain a token via `POST /api/auth/login` or configure `REQUIRE_API_KEY=false` for local development.
## Endpoints
_No endpoints mapped for this area yet._
## Payloads
See the full OpenAPI specification at `GET /api/openapi/spec` or `docs/reference/openapi.yaml` for detailed request/response schemas.

View File

@@ -0,0 +1,304 @@
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { useTranslations } from "next-intl";
import { SkillsConceptCard } from "@/shared/components/SkillsConceptCard";
import { CoverageBar } from "./components/CoverageBar";
import { McpA2aLinksBar } from "./components/McpA2aLinksBar";
import { SkillCard } from "./components/SkillCard";
import { SkillPreviewPane } from "./components/SkillPreviewPane";
import type { AgentSkill, SkillCoverage } from "@/lib/agentSkills/types";
type FilterCategory = "all" | "api" | "cli";
// ── Skeleton helpers ─────────────────────────────────────────────────────────
function SkillCardSkeleton(): JSX.Element {
return (
<div className="flex items-start gap-3 rounded-lg border border-border p-3 animate-pulse">
<div className="h-9 w-9 rounded-lg bg-bg-subtle shrink-0" />
<div className="flex-1 space-y-2">
<div className="h-3 w-1/3 rounded bg-bg-subtle" />
<div className="h-2 w-full rounded bg-bg-subtle" />
<div className="h-2 w-4/5 rounded bg-bg-subtle" />
</div>
</div>
);
}
function CoverageBarSkeleton(): JSX.Element {
return (
<div className="space-y-2 animate-pulse">
<div className="flex gap-2">
<div className="h-2 w-16 rounded bg-bg-subtle" />
<div className="flex-1 h-2 rounded bg-bg-subtle" />
</div>
<div className="flex gap-2">
<div className="h-2 w-16 rounded bg-bg-subtle" />
<div className="flex-1 h-2 rounded bg-bg-subtle" />
</div>
</div>
);
}
// ── Main component ───────────────────────────────────────────────────────────
export function AgentSkillsPageClient(): JSX.Element {
const t = useTranslations("agentSkills");
// State
const [catalog, setCatalog] = useState<AgentSkill[]>([]);
const [coverage, setCoverage] = useState<SkillCoverage | null>(null);
const [loadingCatalog, setLoadingCatalog] = useState(true);
const [filter, setFilter] = useState<FilterCategory>("all");
const [searchTerm, setSearchTerm] = useState("");
const [selectedId, setSelectedId] = useState<string | null>(null);
const [markdownCache, setMarkdownCache] = useState<Map<string, string>>(new Map());
const [loadingPreview, setLoadingPreview] = useState(false);
const [generatingSkills, setGeneratingSkills] = useState(false);
const abortRef = useRef<AbortController | null>(null);
// ── Fetch catalog + coverage on mount ────────────────────────────────────
useEffect(() => {
const controller = new AbortController();
abortRef.current = controller;
const load = async () => {
try {
setLoadingCatalog(true);
const res = await fetch("/api/agent-skills", { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = (await res.json()) as { skills: AgentSkill[]; coverage: SkillCoverage };
setCatalog(json.skills ?? []);
setCoverage(json.coverage ?? null);
} catch (err) {
if ((err as Error).name !== "AbortError") {
setCatalog([]);
}
} finally {
setLoadingCatalog(false);
}
};
void load();
return () => {
controller.abort();
};
}, []);
// ── Fetch raw markdown when a card is selected ────────────────────────────
const loadPreview = useCallback(
async (id: string) => {
if (markdownCache.has(id)) return;
setLoadingPreview(true);
try {
const res = await fetch(`/api/agent-skills/${id}/raw`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = await res.text();
setMarkdownCache((prev) => new Map(prev).set(id, body));
} catch {
setMarkdownCache((prev) => {
const next = new Map(prev);
// Set empty string to signal "failed" so we show error state
next.set(id, "");
return next;
});
} finally {
setLoadingPreview(false);
}
},
[markdownCache],
);
// ── Debounced preview load (200ms) ───────────────────────────────────────
useEffect(() => {
if (!selectedId) return;
const timer = setTimeout(() => {
void loadPreview(selectedId);
}, 200);
return () => clearTimeout(timer);
}, [selectedId, loadPreview]);
const handleSelectCard = useCallback(
(id: string) => {
setSelectedId(id);
},
[],
);
const handleRefreshPreview = useCallback(() => {
if (!selectedId) return;
setMarkdownCache((prev) => {
const next = new Map(prev);
next.delete(selectedId);
return next;
});
void loadPreview(selectedId);
}, [selectedId, loadPreview]);
// ── Generate missing skills ───────────────────────────────────────────────
const handleGenerate = useCallback(async () => {
const confirmed = window.confirm(t("regenerateConfirm"));
if (!confirmed) return;
setGeneratingSkills(true);
try {
const res = await fetch("/api/agent-skills/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dryRun: false, prune: false }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
// Refresh catalog + coverage after generation
const res2 = await fetch("/api/agent-skills");
if (res2.ok) {
const json = (await res2.json()) as { skills: AgentSkill[]; coverage: SkillCoverage };
setCatalog(json.skills ?? []);
setCoverage(json.coverage ?? null);
}
} catch {
// Error handled silently — could extend with toast notification
} finally {
setGeneratingSkills(false);
}
}, [t]);
// ── Filtering + search ────────────────────────────────────────────────────
const filteredSkills = catalog.filter((s) => {
if (filter !== "all" && s.category !== filter) return false;
if (searchTerm.trim()) {
const q = searchTerm.toLowerCase();
return (
s.name.toLowerCase().includes(q) ||
s.description.toLowerCase().includes(q) ||
s.id.toLowerCase().includes(q)
);
}
return true;
});
const selectedMarkdown = selectedId ? markdownCache.get(selectedId) ?? null : null;
const coverageTotal =
coverage !== null ? coverage.api.have + coverage.cli.have : null;
const showGenerateButton = coverageTotal !== null && coverageTotal < 42;
return (
<div className="flex flex-col gap-4">
{/* Concept card — full width */}
<SkillsConceptCard variant="agent" />
{/* Header: coverage + MCP/A2A bar + generate button */}
<div className="flex flex-col gap-3 rounded-xl border border-border bg-bg p-4">
{/* Coverage */}
<div>
<div className="mb-2 flex items-center justify-between gap-2">
<span className="text-xs font-semibold text-text-muted uppercase tracking-wide">
{t("coverageLabel")}
</span>
{showGenerateButton && (
<button
onClick={() => void handleGenerate()}
disabled={generatingSkills}
className="flex items-center gap-1.5 rounded-lg border border-primary/30 bg-primary/5 px-3 py-1.5 text-xs font-medium text-primary hover:bg-primary/10 transition-colors disabled:opacity-60"
data-testid="generate-button"
>
<span
className={`material-symbols-outlined text-[14px] ${generatingSkills ? "animate-spin" : ""}`}
>
{generatingSkills ? "refresh" : "auto_fix_high"}
</span>
{generatingSkills ? t("regenerateRunning") : t("generateButton")}
</button>
)}
</div>
{coverage ? (
<CoverageBar coverage={coverage} />
) : (
<CoverageBarSkeleton />
)}
</div>
{/* MCP + A2A links */}
<McpA2aLinksBar />
</div>
{/* Search + filters */}
<div className="flex flex-col sm:flex-row gap-2">
<div className="relative flex-1">
<span className="absolute left-3 top-1/2 -translate-y-1/2 material-symbols-outlined text-[16px] text-text-muted pointer-events-none">
search
</span>
<input
type="search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t("filters.searchPlaceholder")}
className="w-full rounded-lg border border-border bg-bg py-2 pl-9 pr-3 text-sm text-text-main placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-primary/40"
data-testid="search-input"
/>
</div>
<div className="flex gap-1" role="group" aria-label={t("filters.category")}>
{(["all", "api", "cli"] as FilterCategory[]).map((cat) => (
<button
key={cat}
onClick={() => setFilter(cat)}
data-testid={`filter-${cat}`}
className={`rounded-lg px-3 py-2 text-xs font-medium transition-colors border ${
filter === cat
? "bg-primary/10 text-primary border-primary/30"
: "bg-bg text-text-muted border-border hover:bg-bg-subtle hover:text-text-main"
}`}
>
{cat === "all"
? t("filterAll")
: cat === "api"
? t("categoryApi")
: t("categoryCli")}
</button>
))}
</div>
</div>
{/* Two-column grid: left = skill cards, right = preview */}
<div className="grid grid-cols-12 gap-4" data-testid="skills-grid">
{/* Left: skill cards list (col-span 7) */}
<div
className="col-span-12 lg:col-span-7 flex flex-col gap-2"
data-testid="skills-list"
>
{loadingCatalog ? (
Array.from({ length: 6 }).map((_, i) => <SkillCardSkeleton key={i} />)
) : filteredSkills.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border p-8 text-center">
<span className="material-symbols-outlined text-[32px] text-text-muted mb-3">
search_off
</span>
<p className="text-sm text-text-muted">{t("noSkillsFound")}</p>
</div>
) : (
filteredSkills.map((skill) => (
<SkillCard
key={skill.id}
skill={skill}
selected={selectedId === skill.id}
onClick={() => handleSelectCard(skill.id)}
/>
))
)}
</div>
{/* Right: preview pane (col-span 5) */}
<div className="col-span-12 lg:col-span-5" data-testid="preview-column">
<SkillPreviewPane
skillId={selectedId}
markdown={selectedMarkdown}
loading={loadingPreview}
onRefresh={selectedId ? handleRefreshPreview : undefined}
/>
</div>
</div>
</div>
);
}
export default AgentSkillsPageClient;

View File

@@ -0,0 +1,77 @@
"use client";
import { useTranslations } from "next-intl";
import type { SkillCoverage } from "@/lib/agentSkills/types";
interface CoverageBarProps {
coverage: SkillCoverage;
}
function barColor(have: number, total: number): string {
const pct = total > 0 ? have / total : 0;
if (pct >= 1) return "bg-emerald-500";
if (pct >= 0.75) return "bg-amber-400";
return "bg-red-500";
}
function trackColor(have: number, total: number): string {
const pct = total > 0 ? have / total : 0;
if (pct >= 1) return "bg-emerald-500/20";
if (pct >= 0.75) return "bg-amber-400/20";
return "bg-red-500/20";
}
export function CoverageBar({ coverage }: CoverageBarProps): JSX.Element {
const t = useTranslations("agentSkills");
const { api, cli } = coverage;
return (
<div className="flex flex-col gap-2 text-xs" data-testid="coverage-bar">
<div className="flex items-center justify-between gap-2">
<span className="font-medium text-text-muted shrink-0">
{t("categoryApi")} {api.have}/{api.total}
</span>
<div
className={`flex-1 h-2 rounded-full overflow-hidden ${trackColor(api.have, api.total)}`}
>
<div
role="progressbar"
aria-valuenow={api.have}
aria-valuemin={0}
aria-valuemax={api.total}
aria-label={`${t("categoryApi")} ${api.have}/${api.total}`}
className={`h-full rounded-full transition-all duration-500 ${barColor(api.have, api.total)}`}
style={{ width: `${api.total > 0 ? (api.have / api.total) * 100 : 0}%` }}
/>
</div>
<span className="shrink-0 text-text-muted w-12 text-right">
{api.total > 0 ? Math.round((api.have / api.total) * 100) : 0}%
</span>
</div>
<div className="flex items-center justify-between gap-2">
<span className="font-medium text-text-muted shrink-0">
{t("categoryCli")} {cli.have}/{cli.total}
</span>
<div
className={`flex-1 h-2 rounded-full overflow-hidden ${trackColor(cli.have, cli.total)}`}
>
<div
role="progressbar"
aria-valuenow={cli.have}
aria-valuemin={0}
aria-valuemax={cli.total}
aria-label={`${t("categoryCli")} ${cli.have}/${cli.total}`}
className={`h-full rounded-full transition-all duration-500 ${barColor(cli.have, cli.total)}`}
style={{ width: `${cli.total > 0 ? (cli.have / cli.total) * 100 : 0}%` }}
/>
</div>
<span className="shrink-0 text-text-muted w-12 text-right">
{cli.total > 0 ? Math.round((cli.have / cli.total) * 100) : 0}%
</span>
</div>
</div>
);
}
export default CoverageBar;

View File

@@ -0,0 +1,93 @@
"use client";
import { useCallback, useState, useSyncExternalStore } from "react";
import { useTranslations } from "next-intl";
// SSR-safe origin via useSyncExternalStore.
// Server snapshot returns "" (avoids hydration mismatch).
function useOrigin(): string {
return useSyncExternalStore(
() => () => {}, // no external subscription needed
() => (typeof window !== "undefined" ? window.location.origin : ""),
() => "", // server snapshot
);
}
interface LinkCardProps {
label: string;
url: string;
icon: string;
prompt: string;
}
function LinkCard({ label, url, icon, prompt }: LinkCardProps): JSX.Element {
const t = useTranslations("agentSkills");
const [copied, setCopied] = useState(false);
const handleCopy = useCallback(async () => {
try {
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// clipboard not available — silently ignore
}
}, [url]);
return (
<div className="flex-1 flex items-start gap-3 rounded-lg border border-border bg-bg-subtle p-3 min-w-0">
<div className="flex items-center justify-center size-8 rounded-lg bg-primary/10 shrink-0">
<span className="material-symbols-outlined text-primary text-[16px]">{icon}</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-1 mb-1">
<span className="text-xs font-semibold text-text-main">{label}</span>
<button
onClick={() => void handleCopy()}
className={`flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors shrink-0 ${
copied
? "bg-emerald-500/10 text-emerald-700 dark:text-emerald-400"
: "bg-bg text-text-muted hover:text-text-main"
}`}
title={t("copyUrl")}
aria-label={`${t("copyUrl")} ${label}`}
>
<span className="material-symbols-outlined text-[11px]">
{copied ? "check" : "content_copy"}
</span>
{copied ? "✓" : t("copyUrl")}
</button>
</div>
<code className="block truncate text-[10px] font-mono text-text-muted">{url}</code>
<p className="mt-1 text-[10px] text-text-muted leading-relaxed italic">{prompt}</p>
</div>
</div>
);
}
export function McpA2aLinksBar(): JSX.Element {
const t = useTranslations("agentSkills");
const origin = useOrigin();
const mcpUrl = origin ? `${origin}/api/mcp/sse` : "/api/mcp/sse";
const a2aUrl = origin ? `${origin}/.well-known/agent.json` : "/.well-known/agent.json";
return (
<div className="flex flex-col sm:flex-row gap-2" data-testid="mcp-a2a-links-bar">
<LinkCard
label={t("mcpUrl")}
url={mcpUrl}
icon="electrical_services"
prompt="Add this MCP endpoint to your agent to give it 37 OmniRoute tools."
/>
<LinkCard
label={t("a2aLink")}
url={a2aUrl}
icon="hub"
prompt="Register this Agent Card with your orchestrator to enable A2A task delegation."
/>
</div>
);
}
export default McpA2aLinksBar;

View File

@@ -0,0 +1,105 @@
"use client";
import { useCallback } from "react";
import { useTranslations } from "next-intl";
import type { AgentSkill } from "@/lib/agentSkills/types";
interface SkillCardProps {
skill: AgentSkill;
selected: boolean;
onClick: () => void;
}
export function SkillCard({ skill, selected, onClick }: SkillCardProps): JSX.Element {
const t = useTranslations("agentSkills");
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onClick();
}
},
[onClick],
);
const previewItems: string[] =
skill.category === "api"
? (skill.endpoints ?? []).slice(0, 2)
: (skill.cliCommands ?? []).slice(0, 2);
return (
<div
role="button"
tabIndex={0}
aria-pressed={selected}
onClick={onClick}
onKeyDown={handleKeyDown}
data-testid={`skill-card-${skill.id}`}
className={`flex items-start gap-3 rounded-lg border p-3 cursor-pointer transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary/50 ${
selected
? "border-primary/40 bg-primary/5"
: "border-border bg-bg hover:bg-bg-subtle hover:border-border"
}`}
>
<div
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${
selected ? "bg-primary/15" : "bg-bg-subtle"
}`}
>
<span
className={`material-symbols-outlined text-[18px] ${
selected ? "text-primary" : "text-text-muted"
}`}
>
{skill.icon ?? "article"}
</span>
</div>
<div className="min-w-0 flex-1">
<div className="mb-0.5 flex flex-wrap items-center gap-1.5">
<span className="text-sm font-semibold text-text-main">{skill.name}</span>
<span
className={`rounded-full px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide ${
skill.category === "api"
? "bg-blue-500/10 text-blue-700 dark:text-blue-400"
: "bg-violet-500/10 text-violet-700 dark:text-violet-400"
}`}
>
{skill.category === "api" ? t("categoryApi") : t("categoryCli")}
</span>
{skill.isEntry && (
<span className="rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary">
start
</span>
)}
{skill.isNew && (
<span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-700 dark:text-amber-400">
new
</span>
)}
</div>
<p className="text-xs leading-relaxed text-text-muted line-clamp-2">{skill.description}</p>
{previewItems.length > 0 && (
<div className="mt-1.5 flex flex-wrap gap-1">
{previewItems.map((item) => (
<code
key={item}
className="rounded bg-bg-subtle px-1.5 py-0.5 font-mono text-[10px] text-text-muted border border-border/50"
>
{item}
</code>
))}
</div>
)}
</div>
</div>
);
}
export default SkillCard;

View File

@@ -0,0 +1,147 @@
"use client";
import { useCallback } from "react";
import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
// Lazy-load react-markdown to reduce initial bundle size.
const ReactMarkdown = dynamic(() => import("react-markdown"), {
loading: () => <SkeletonLines lines={6} />,
});
// remark-gfm loaded lazily alongside ReactMarkdown via remarkPlugins prop.
// We avoid rehype-raw to prevent XSS (Hard Rule #7 context).
interface SkillPreviewPaneProps {
skillId: string | null;
markdown: string | null;
loading: boolean;
onRefresh?: () => void;
}
function SkeletonLines({ lines }: { lines: number }): JSX.Element {
return (
<div className="space-y-2 animate-pulse" aria-hidden="true">
{Array.from({ length: lines }).map((_, i) => (
<div
key={i}
className="h-3 rounded bg-bg-subtle"
style={{ width: `${60 + ((i * 17) % 40)}%` }}
/>
))}
</div>
);
}
export function SkillPreviewPane({
skillId,
markdown,
loading,
onRefresh,
}: SkillPreviewPaneProps): JSX.Element {
const t = useTranslations("agentSkills");
const handleCopyRawUrl = useCallback(async () => {
if (!skillId) return;
const rawUrl = `https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/${skillId}/SKILL.md`;
try {
await navigator.clipboard.writeText(rawUrl);
} catch {
// clipboard not available — silently ignore
}
}, [skillId]);
const githubUrl = skillId
? `https://github.com/diegosouzapw/OmniRoute/blob/main/skills/${skillId}/SKILL.md`
: null;
// Empty state
if (!skillId) {
return (
<div
className="flex flex-col items-center justify-center h-full min-h-[300px] rounded-xl border border-dashed border-border bg-bg-subtle/30 p-8 text-center"
data-testid="skill-preview-empty"
>
<span className="material-symbols-outlined text-[32px] text-text-muted mb-3">
article
</span>
<p className="text-sm text-text-muted">{t("previewEmpty")}</p>
</div>
);
}
return (
<div
className="flex flex-col rounded-xl border border-border bg-bg h-full"
data-testid="skill-preview-pane"
>
{/* Header */}
<div className="flex items-center justify-between gap-2 border-b border-border px-4 py-2.5 shrink-0">
<span className="text-xs font-mono font-semibold text-text-muted truncate">
{skillId}/SKILL.md
</span>
<div className="flex items-center gap-1 shrink-0">
{onRefresh && (
<button
onClick={onRefresh}
disabled={loading}
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors disabled:opacity-50"
aria-label="Refresh"
>
<span
className={`material-symbols-outlined text-[14px] ${loading ? "animate-spin" : ""}`}
>
refresh
</span>
</button>
)}
<button
onClick={() => void handleCopyRawUrl()}
disabled={!skillId}
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors"
title={t("copyUrl")}
aria-label={t("copyUrl")}
>
<span className="material-symbols-outlined text-[14px]">content_copy</span>
</button>
{githubUrl && (
<a
href={githubUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 rounded px-2 py-1 text-xs text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors"
title={t("viewOnGithub")}
aria-label={t("viewOnGithub")}
>
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
</a>
)}
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-4 min-h-0">
{loading ? (
<SkeletonLines lines={12} />
) : markdown ? (
<div
className="prose prose-sm dark:prose-invert max-w-none text-text-main"
data-testid="skill-preview-markdown"
>
<ReactMarkdown>{markdown}</ReactMarkdown>
</div>
) : (
<div
className="flex items-center gap-2 rounded-lg border border-red-300 bg-red-50 dark:bg-red-950/30 p-3 text-xs text-red-700 dark:text-red-400"
data-testid="skill-preview-error"
>
<span className="material-symbols-outlined text-[16px]">error</span>
{t("previewError")}
</div>
)}
</div>
</div>
);
}
export default SkillPreviewPane;

View File

@@ -1,172 +1,5 @@
"use client";
import { AgentSkillsPageClient } from "./AgentSkillsPageClient";
import { useTranslations } from "next-intl";
import {
AGENT_SKILLS,
AGENT_SKILLS_REPO_URL,
getAgentSkillRawUrl,
getAgentSkillBlobUrl,
type AgentSkill,
} from "@/shared/constants/agentSkills";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
function CopyButton({ url }: { url: string }) {
const t = useTranslations("agents");
const { copied, copy } = useCopyToClipboard();
const isCopied = copied === url;
return (
<button
onClick={() => void copy(url, url)}
className={`flex items-center gap-1 rounded px-2 py-1 text-xs font-medium transition-colors ${
isCopied
? "bg-emerald-500/10 text-emerald-700 dark:text-emerald-400"
: "bg-bg-subtle text-text-muted hover:text-text-main"
}`}
title={t("copyRawUrlTitle")}
>
<span className="material-symbols-outlined text-[14px]">
{isCopied ? "check" : "content_copy"}
</span>
{isCopied ? t("copied") : t("copyUrl")}
</button>
);
}
function SkillRow({ skill }: { skill: AgentSkill }) {
const t = useTranslations("agents");
const rawUrl = getAgentSkillRawUrl(skill.id);
const blobUrl = getAgentSkillBlobUrl(skill.id);
return (
<div className="flex items-start gap-3 rounded-lg border border-border p-3 transition-colors hover:bg-bg-subtle">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-bg-subtle">
<span className="material-symbols-outlined text-[18px] text-text-muted">{skill.icon}</span>
</div>
<div className="min-w-0 flex-1">
<div className="mb-0.5 flex flex-wrap items-center gap-1.5">
<span className="text-sm font-semibold text-text-main">{skill.name}</span>
{skill.isEntry && (
<span className="rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary">
{t("startHere")}
</span>
)}
{skill.isNew && (
<span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-700 dark:text-amber-400">
{t("badgeNew")}
</span>
)}
{skill.endpoint && (
<code className="rounded bg-bg-subtle px-1.5 py-0.5 font-mono text-[10px] text-text-muted">
{skill.endpoint}
</code>
)}
</div>
<p className="text-xs leading-relaxed text-text-muted">{skill.description}</p>
</div>
<div className="flex shrink-0 items-center gap-1">
<a
href={blobUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 rounded px-2 py-1 text-xs font-medium text-text-muted transition-colors hover:bg-bg-subtle hover:text-text-main"
title={t("viewOnGithub")}
>
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
</a>
<CopyButton url={rawUrl} />
</div>
</div>
);
}
function SkillSection({
title,
subtitle,
icon,
skills,
}: {
title: string;
subtitle: string;
icon: string;
skills: AgentSkill[];
}) {
return (
<section className="space-y-3 rounded-xl border border-border bg-bg p-4">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[20px] text-text-muted">{icon}</span>
<div>
<h2 className="text-sm font-semibold text-text-main">{title}</h2>
<p className="text-xs text-text-muted">{subtitle}</p>
</div>
</div>
<div className="flex flex-col gap-2">
{skills.map((skill) => (
<SkillRow key={skill.id} skill={skill} />
))}
</div>
</section>
);
}
export default function AgentSkillsPage() {
const t = useTranslations("agents");
const apiSkills = AGENT_SKILLS.filter((s) => s.category === "api");
const cliSkills = AGENT_SKILLS.filter((s) => s.category === "cli");
return (
<div className="space-y-6">
{/* How to use — full width */}
<div className="rounded-xl border border-border bg-bg-subtle/50 p-4">
<div className="mb-2 flex items-center gap-2">
<span className="material-symbols-outlined text-[18px] text-primary">info</span>
<span className="text-sm font-semibold text-text-main">{t("howToUse")}</span>
</div>
<ol className="space-y-1 text-xs text-text-muted">
<li>
1.{" "}
{t.rich("howToUseStep1", {
copyUrl: t("copyUrl"),
bold: (chunks) => <strong className="text-text-main">{chunks}</strong>,
})}
</li>
<li>
2. {t("howToUseStep2")}
<br />
<code className="mt-1 block rounded border border-border bg-bg px-2 py-1 font-mono text-[11px]">
{t("howToUseStep2Code")}
</code>
</li>
<li>3. {t("howToUseStep3")}</li>
</ol>
<a
href={AGENT_SKILLS_REPO_URL}
target="_blank"
rel="noopener noreferrer"
className="mt-3 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
>
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
{t("browseAllSkillsOnGithub")}
</a>
</div>
{/* Two-column grid: API Skills | CLI Skills */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<SkillSection
title={t("apiSkills")}
subtitle={t("apiSkillsSubtitle", { count: apiSkills.length })}
icon="api"
skills={apiSkills}
/>
<SkillSection
title={t("cliSkills")}
subtitle={t("cliSkillsSubtitle", { count: cliSkills.length })}
icon="terminal"
skills={cliSkills}
/>
</div>
</div>
);
export default function Page() {
return <AgentSkillsPageClient />;
}

View File

@@ -0,0 +1,384 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
import { SkillsConceptCard } from "@/shared/components";
import type { SkillsProvider } from "@/lib/skills/providerSettings";
import { OmniSkillsList } from "./components/OmniSkillsList";
import { OmniExecutionsTab } from "./components/OmniExecutionsTab";
import { OmniSandboxTab } from "./components/OmniSandboxTab";
import { OmniMarketplaceTab } from "./components/OmniMarketplaceTab";
import type { OmniSkill } from "./components/OmniSkillCard";
interface Execution {
id: string;
skillId: string;
skillName: string;
status: string;
duration: number;
createdAt: string;
}
export function OmniSkillsPageClient(): JSX.Element {
const [skills, setSkills] = useState<OmniSkill[]>([]);
const [executions, setExecutions] = useState<Execution[]>([]);
const [loading, setLoading] = useState(true);
const [skillsPage, setSkillsPage] = useState(1);
const [skillsTotal, setSkillsTotal] = useState(0);
const [skillsTotalPages, setSkillsTotalPages] = useState(1);
const [popularDefaults, setPopularDefaults] = useState<string[]>([]);
const [searchTerm, setSearchTerm] = useState("");
const [modeFilter, setModeFilter] = useState<"all" | "on" | "off" | "auto">("all");
const [selectedSkillId, setSelectedSkillId] = useState<string | null>(null);
const [execPage, setExecPage] = useState(1);
const [execTotal, setExecTotal] = useState(0);
const [execTotalPages, setExecTotalPages] = useState(1);
const [activeTab, setActiveTab] = useState<"skills" | "executions" | "sandbox" | "marketplace">(
"skills"
);
const [showInstallModal, setShowInstallModal] = useState(false);
const [installJson, setInstallJson] = useState("");
const [installStatus, setInstallStatus] = useState<{
type: "success" | "error";
message: string;
} | null>(null);
const [installing, setInstalling] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const [skillsProvider, setSkillsProvider] = useState<SkillsProvider>("skillsmp");
const t = useTranslations("skills");
// NOTE: commonT is intentionally unused here but retained for potential future use
useTranslations("common");
const fetchSkills = async (page: number) => {
const params = new URLSearchParams({ page: String(page), limit: "20" });
if (searchTerm.trim()) params.set("q", searchTerm.trim());
if (modeFilter !== "all") params.set("mode", modeFilter);
const res = await fetch(`/api/skills?${params.toString()}`).then((r) => r.json());
setSkills((res as { data?: OmniSkill[] }).data || []);
setSkillsTotal((res as { total?: number }).total || 0);
setSkillsTotalPages((res as { totalPages?: number }).totalPages || 1);
setPopularDefaults(
Array.isArray((res as { popularDefaults?: string[] }).popularDefaults)
? (res as { popularDefaults: string[] }).popularDefaults
: []
);
};
const fetchExecutions = async (page: number) => {
const res = await fetch(`/api/skills/executions?page=${page}&limit=20`).then((r) => r.json());
setExecutions((res as { data?: Execution[] }).data || []);
setExecTotal((res as { total?: number }).total || 0);
setExecTotalPages((res as { totalPages?: number }).totalPages || 1);
};
useEffect(() => {
Promise.all([
fetch("/api/skills?page=1&limit=20").then((r) => r.json()),
fetch("/api/skills/executions?page=1&limit=20").then((r) => r.json()),
fetch("/api/settings").then((r) => (r.ok ? r.json() : null)),
])
.then(([skillsData, executionsData, settingsData]) => {
setSkills((skillsData as { data?: OmniSkill[] }).data || []);
setSkillsTotal((skillsData as { total?: number }).total || 0);
setSkillsTotalPages((skillsData as { totalPages?: number }).totalPages || 1);
setPopularDefaults(
Array.isArray((skillsData as { popularDefaults?: string[] }).popularDefaults)
? (skillsData as { popularDefaults: string[] }).popularDefaults
: []
);
setExecutions((executionsData as { data?: Execution[] }).data || []);
setExecTotal((executionsData as { total?: number }).total || 0);
setExecTotalPages((executionsData as { totalPages?: number }).totalPages || 1);
const sd = settingsData as { skillsProvider?: SkillsProvider } | null;
if (sd?.skillsProvider === "skillsmp" || sd?.skillsProvider === "skillssh") {
setSkillsProvider(sd.skillsProvider);
}
setLoading(false);
})
.catch(() => setLoading(false));
}, []);
const refreshSkills = async () => {
setSkillsPage(1);
await fetchSkills(1);
};
const setSkillMode = async (skillId: string, mode: "on" | "off" | "auto") => {
await fetch(`/api/skills/${skillId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mode }),
});
setSkills(skills.map((s) => (s.id === skillId ? { ...s, mode, enabled: mode !== "off" } : s)));
};
const deleteSkill = async (skillId: string) => {
const res = await fetch(`/api/skills/${skillId}`, { method: "DELETE" });
if (res.ok) {
setSkills(skills.filter((s) => s.id !== skillId));
if (selectedSkillId === skillId) setSelectedSkillId(null);
}
};
const handleInstall = async () => {
setInstalling(true);
setInstallStatus(null);
try {
const manifest = JSON.parse(installJson) as Record<string, unknown>;
const res = await fetch("/api/skills/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(manifest),
});
const data = await res.json();
if (res.ok && (data as { success?: boolean }).success) {
setInstallStatus({
type: "success",
message: t("installSuccess", { id: (data as { id?: string }).id || "" }),
});
setInstallJson("");
await refreshSkills();
} else {
setInstallStatus({
type: "error",
message:
(data as { error?: string; message?: string }).error ||
(data as { error?: string; message?: string }).message ||
t("installError"),
});
}
} catch (err) {
setInstallStatus({
type: "error",
message: err instanceof Error ? err.message : t("invalidJson"),
});
} finally {
setInstalling(false);
}
};
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
setInstallJson((ev.target?.result as string) || "");
};
reader.readAsText(file);
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-text-muted">{t("loading")}...</div>
</div>
);
}
const enabledCount = skills.filter((s) => s.enabled).length;
const execSuccessCount = executions.filter((e) => e.status === "success").length;
const successRate =
executions.length > 0 ? Math.round((execSuccessCount / executions.length) * 100) : 0;
const tabs: { id: "skills" | "executions" | "sandbox" | "marketplace"; labelKey: string }[] = [
{ id: "skills", labelKey: "skillsTab" },
{ id: "executions", labelKey: "executionsTab" },
{ id: "sandbox", labelKey: "sandboxTab" },
{ id: "marketplace", labelKey: "marketplaceTab" },
];
return (
<div className="flex flex-col gap-6">
{/* Concept card */}
<SkillsConceptCard variant="omni" />
{/* Stats Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">{t("totalSkills")}</p>
<p className="text-2xl font-bold text-text-main mt-1">{skillsTotal}</p>
</Card>
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">{t("enabledSkills")}</p>
<p className="text-2xl font-bold text-emerald-400 mt-1">{enabledCount}</p>
</Card>
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">
{t("totalExecutions")}
</p>
<p className="text-2xl font-bold text-violet-400 mt-1">{execTotal}</p>
</Card>
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">{t("successRate")}</p>
<p className="text-2xl font-bold text-amber-400 mt-1">{successRate}%</p>
</Card>
</div>
<div className="flex justify-end">
<button
onClick={() => setShowInstallModal(true)}
className="px-4 py-2 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 transition-colors"
>
{t("installSkillButton")}
</button>
</div>
{/* Tab bar */}
<div className="flex gap-2 border-b border-border">
{tabs.map(({ id, labelKey }) => (
<button
key={id}
onClick={() => setActiveTab(id)}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === id
? "border-violet-500 text-violet-400"
: "border-transparent text-text-muted hover:text-text-main"
}`}
>
{t(labelKey)}
</button>
))}
</div>
{/* Tab content */}
{activeTab === "skills" && (
<OmniSkillsList
skills={skills}
skillsTotal={skillsTotal}
skillsPage={skillsPage}
skillsTotalPages={skillsTotalPages}
popularDefaults={popularDefaults}
searchTerm={searchTerm}
modeFilter={modeFilter}
selectedSkillId={selectedSkillId}
onSearchTermChange={setSearchTerm}
onModeFilterChange={setModeFilter}
onApplyFilters={() => {
setSkillsPage(1);
void fetchSkills(1);
}}
onPagePrev={() => {
const p = Math.max(1, skillsPage - 1);
setSkillsPage(p);
void fetchSkills(p);
}}
onPageNext={() => {
const p = Math.min(skillsTotalPages, skillsPage + 1);
setSkillsPage(p);
void fetchSkills(p);
}}
onSelectSkill={setSelectedSkillId}
onSetMode={setSkillMode}
onUninstall={deleteSkill}
/>
)}
{activeTab === "executions" && (
<OmniExecutionsTab
executions={executions}
execPage={execPage}
execTotalPages={execTotalPages}
execTotal={execTotal}
onPagePrev={() => {
const p = Math.max(1, execPage - 1);
setExecPage(p);
void fetchExecutions(p);
}}
onPageNext={() => {
const p = Math.min(execTotalPages, execPage + 1);
setExecPage(p);
void fetchExecutions(p);
}}
/>
)}
{activeTab === "sandbox" && <OmniSandboxTab />}
{activeTab === "marketplace" && (
<OmniMarketplaceTab skillsProvider={skillsProvider} onRefreshSkills={refreshSkills} />
)}
{/* Install modal */}
{showInstallModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-surface border border-border rounded-xl p-6 w-full max-w-lg mx-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">{t("installSkillModalTitle")}</h2>
<button
onClick={() => {
setShowInstallModal(false);
setInstallStatus(null);
setInstallJson("");
}}
className="text-text-muted hover:text-text-main"
>
X
</button>
</div>
<p className="text-sm text-text-muted mb-4">{t("installSkillModalDesc")}</p>
<textarea
value={installJson}
onChange={(e) => setInstallJson(e.target.value)}
placeholder={t("installJsonPlaceholder")}
className="w-full h-48 p-3 rounded-lg bg-background border border-border text-sm font-mono resize-none focus:outline-none focus:ring-1 focus:ring-violet-500"
/>
<div className="flex items-center gap-3 mt-3">
<input
ref={fileInputRef}
type="file"
accept=".json"
onChange={handleFileUpload}
className="hidden"
/>
<button
onClick={() => fileInputRef.current?.click()}
className="px-3 py-1.5 text-sm rounded-lg border border-border text-text-muted hover:text-text-main transition-colors"
>
{t("uploadJson")}
</button>
<div className="flex-1" />
<button
onClick={() => {
setShowInstallModal(false);
setInstallStatus(null);
setInstallJson("");
}}
className="px-3 py-1.5 text-sm rounded-lg border border-border text-text-muted hover:text-text-main transition-colors"
>
{t("cancel")}
</button>
<button
onClick={handleInstall}
disabled={installing || !installJson.trim()}
className="px-4 py-1.5 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{installing ? t("installing") : t("installSkillButton")}
</button>
</div>
{installStatus && (
<div
className={`mt-3 p-3 rounded-lg text-sm ${
installStatus.type === "success"
? "bg-emerald-500/10 text-emerald-400"
: "bg-red-500/10 text-red-400"
}`}
>
{installStatus.message}
</div>
)}
</div>
</div>
)}
</div>
);
}
export default OmniSkillsPageClient;

View File

@@ -0,0 +1,106 @@
"use client";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
interface Execution {
id: string;
skillId: string;
skillName: string;
status: string;
duration: number;
createdAt: string;
}
interface OmniExecutionsTabProps {
executions: Execution[];
execPage: number;
execTotalPages: number;
execTotal: number;
onPagePrev: () => void;
onPageNext: () => void;
}
export function OmniExecutionsTab({
executions,
execPage,
execTotalPages,
execTotal,
onPagePrev,
onPageNext,
}: OmniExecutionsTabProps): JSX.Element {
const t = useTranslations("skills");
return (
<Card>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="text-left text-sm text-text-muted border-b border-border">
<th className="pb-3 font-medium">{t("skill")}</th>
<th className="pb-3 font-medium">{t("status")}</th>
<th className="pb-3 font-medium">{t("duration")}</th>
<th className="pb-3 font-medium">{t("time")}</th>
</tr>
</thead>
<tbody>
{executions.length === 0 ? (
<tr>
<td colSpan={4} className="py-8 text-center text-text-muted">
{t("noExecutions")}
</td>
</tr>
) : (
executions.map((exec) => (
<tr key={exec.id} className="border-b border-border/50">
<td className="py-3 font-medium">{exec.skillName}</td>
<td className="py-3">
<span
className={`text-xs px-2 py-1 rounded ${
exec.status === "success"
? "bg-emerald-500/10 text-emerald-400"
: exec.status === "error"
? "bg-red-500/10 text-red-400"
: "bg-amber-500/10 text-amber-400"
}`}
>
{exec.status}
</span>
</td>
<td className="py-3 text-text-muted">{exec.duration}ms</td>
<td className="py-3 text-text-muted text-sm">
{new Date(exec.createdAt).toLocaleString()}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
<div className="flex items-center justify-between mt-4 pt-4 border-t border-border">
<span className="text-sm text-text-muted">
{t("pageInfo", { page: execPage, totalPages: execTotalPages, total: execTotal }) ||
`Page ${execPage} of ${execTotalPages} (${execTotal} total)`}
</span>
<div className="flex gap-2">
<button
onClick={onPagePrev}
disabled={execPage === 1}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("previous") || "Prev"}
</button>
<button
onClick={onPageNext}
disabled={execPage === execTotalPages || execTotalPages === 0}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("next") || "Next"}
</button>
</div>
</div>
</Card>
);
}
export default OmniExecutionsTab;

View File

@@ -0,0 +1,244 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
import type { SkillsProvider } from "@/lib/skills/providerSettings";
interface MarketplaceSkill {
name: string;
description: string;
skillMdContent?: string;
version?: string;
sourceUrl?: string;
}
interface SkillsShSkill {
id: string;
skillId: string;
name: string;
installs: number;
source: string;
}
interface OmniMarketplaceTabProps {
skillsProvider: SkillsProvider;
onRefreshSkills: () => Promise<void>;
}
export function OmniMarketplaceTab({
skillsProvider,
onRefreshSkills,
}: OmniMarketplaceTabProps): JSX.Element {
const t = useTranslations("skills");
const [mpQuery, setMpQuery] = useState("");
const [mpResults, setMpResults] = useState<MarketplaceSkill[]>([]);
const [mpLoading, setMpLoading] = useState(false);
const [mpError, setMpError] = useState("");
const [mpInstallingId, setMpInstallingId] = useState<string | null>(null);
const [shQuery, setShQuery] = useState("");
const [shResults, setShResults] = useState<SkillsShSkill[]>([]);
const [shLoading, setShLoading] = useState(false);
const [shError, setShError] = useState("");
const [shInstallingId, setShInstallingId] = useState<string | null>(null);
const searchMarketplace = async () => {
setMpLoading(true);
setMpError("");
setMpResults([]);
try {
const res = await fetch(`/api/skills/marketplace?q=${encodeURIComponent(mpQuery)}`);
const data = await res.json();
if (!res.ok) {
setMpError((data as { error?: string }).error || t("marketplaceError"));
} else {
setMpResults(Array.isArray(data) ? data : (data as { skills?: MarketplaceSkill[] }).skills || []);
}
} catch (err) {
setMpError(err instanceof Error ? err.message : t("marketplaceError"));
} finally {
setMpLoading(false);
}
};
const installFromMarketplace = async (skill: MarketplaceSkill) => {
setMpInstallingId(skill.name);
try {
const res = await fetch("/api/skills/marketplace/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: skill.name,
description: skill.description,
skillMdContent: skill.skillMdContent || skill.description,
version: skill.version || "1.0.0",
sourceUrl: skill.sourceUrl,
}),
});
const data = await res.json();
if (res.ok && (data as { success?: boolean }).success) {
await onRefreshSkills();
setMpInstallingId(null);
} else {
setMpError((data as { error?: string }).error || t("installError"));
setMpInstallingId(null);
}
} catch (err) {
setMpError(err instanceof Error ? err.message : t("installError"));
setMpInstallingId(null);
}
};
const searchSkillsSh = async () => {
setShLoading(true);
setShError("");
setShResults([]);
try {
const res = await fetch(`/api/skills/skillssh?q=${encodeURIComponent(shQuery)}`);
const data = await res.json();
if (!res.ok) {
setShError((data as { error?: string }).error || t("marketplaceError"));
} else {
setShResults((data as { skills?: SkillsShSkill[] }).skills || []);
}
} catch (err) {
setShError(err instanceof Error ? err.message : t("marketplaceError"));
} finally {
setShLoading(false);
}
};
const installFromSkillsSh = async (skill: SkillsShSkill) => {
setShInstallingId(skill.id);
try {
const res = await fetch("/api/skills/skillssh/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: skill.name,
description: `Installed from skills.sh (${skill.source})`,
source: skill.source,
skillId: skill.skillId,
}),
});
const data = await res.json();
if (res.ok && (data as { success?: boolean }).success) {
await onRefreshSkills();
setShInstallingId(null);
} else {
setShError((data as { error?: string }).error || t("installError"));
setShInstallingId(null);
}
} catch (err) {
setShError(err instanceof Error ? err.message : t("installError"));
setShInstallingId(null);
}
};
return (
<div className="grid gap-4">
<Card>
<h3 className="font-semibold mb-2">{t("skillsMarketplace")}</h3>
<p className="text-sm text-text-muted mb-4">
{t("activeProvider")}{" "}
<span className="font-medium">
{skillsProvider === "skillsmp" ? "SkillsMP" : "skills.sh"}
</span>
. {t("changeInSettings")}
</p>
<div className="flex gap-2 mb-4">
<input
type="text"
value={skillsProvider === "skillsmp" ? mpQuery : shQuery}
onChange={(e) =>
skillsProvider === "skillsmp" ? setMpQuery(e.target.value) : setShQuery(e.target.value)
}
onKeyDown={(e) =>
e.key === "Enter" &&
(skillsProvider === "skillsmp" ? searchMarketplace() : searchSkillsSh())
}
placeholder={t("searchMarketplacePlaceholder")}
className="flex-1 px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
/>
<button
onClick={() => (skillsProvider === "skillsmp" ? searchMarketplace() : searchSkillsSh())}
disabled={skillsProvider === "skillsmp" ? mpLoading : shLoading}
className="px-4 py-2 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{skillsProvider === "skillsmp"
? mpLoading
? t("searching")
: t("searchMarketplace")
: shLoading
? t("searching")
: t("searchMarketplace")}
</button>
</div>
{(skillsProvider === "skillsmp" ? mpError : shError) && (
<div className="p-3 rounded-lg bg-red-500/10 text-red-400 text-sm mb-4">
{skillsProvider === "skillsmp" ? mpError : shError}
</div>
)}
</Card>
{skillsProvider === "skillsmp" && mpResults.length > 0 && (
<div className="grid gap-3">
{mpResults.map((skill) => (
<Card key={skill.name}>
<div className="flex items-center justify-between">
<div>
<h4 className="font-semibold">{skill.name}</h4>
<p className="text-sm text-text-muted mt-1">{skill.description}</p>
</div>
<button
onClick={() => installFromMarketplace(skill)}
disabled={mpInstallingId === skill.name}
className="px-4 py-1.5 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{mpInstallingId === skill.name ? t("installing") : t("installSkillButton")}
</button>
</div>
</Card>
))}
</div>
)}
{skillsProvider === "skillssh" && shResults.length > 0 && (
<div className="grid gap-3">
{shResults.map((skill) => (
<Card key={skill.id}>
<div className="flex items-center justify-between">
<div>
<h4 className="font-semibold">{skill.name}</h4>
<p className="text-sm text-text-muted mt-1">
{skill.source} · {skill.installs.toLocaleString()} {t("installs")}
</p>
</div>
<button
onClick={() => installFromSkillsSh(skill)}
disabled={shInstallingId === skill.id}
className="px-4 py-1.5 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{shInstallingId === skill.id ? t("installing") : t("installSkillButton")}
</button>
</div>
</Card>
))}
</div>
)}
{skillsProvider === "skillsmp" && !mpLoading && mpResults.length === 0 && !mpError && (
<Card>
<div className="text-center py-8 text-text-muted">{t("marketplaceSkillsMpHint")}</div>
</Card>
)}
{skillsProvider === "skillssh" && !shLoading && shResults.length === 0 && !shError && (
<Card>
<div className="text-center py-8 text-text-muted">{t("marketplaceSkillsShHint")}</div>
</Card>
)}
</div>
);
}
export default OmniMarketplaceTab;

View File

@@ -0,0 +1,48 @@
"use client";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
export function OmniSandboxTab(): JSX.Element {
const t = useTranslations("skills");
return (
<div className="grid gap-4">
<Card>
<h3 className="font-semibold mb-4">{t("sandboxConfig")}</h3>
<div className="grid gap-4">
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("cpuLimit")}</p>
<p className="text-xs text-text-muted">{t("cpuLimitDesc")}</p>
</div>
<span className="font-mono">100ms</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("memoryLimit")}</p>
<p className="text-xs text-text-muted">{t("memoryLimitDesc")}</p>
</div>
<span className="font-mono">256MB</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("timeout")}</p>
<p className="text-xs text-text-muted">{t("timeoutDesc")}</p>
</div>
<span className="font-mono">30s</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("networkAccess")}</p>
<p className="text-xs text-text-muted">{t("networkAccessDesc")}</p>
</div>
<span className="text-text-muted">{t("disabled")}</span>
</div>
</div>
</Card>
</div>
);
}
export default OmniSandboxTab;

View File

@@ -0,0 +1,98 @@
"use client";
import { useTranslations } from "next-intl";
export interface OmniSkill {
id: string;
name: string;
version: string;
description: string;
enabled: boolean;
mode?: "on" | "off" | "auto";
sourceProvider?: "skillsmp" | "skillssh" | "local";
tags?: string[];
installCount?: number;
createdAt: string;
}
interface OmniSkillCardProps {
skill: OmniSkill;
selected: boolean;
onClick: () => void;
}
export function OmniSkillCard({ skill, selected, onClick }: OmniSkillCardProps): JSX.Element {
const t = useTranslations("skills");
const effectiveMode = skill.mode || (skill.enabled ? "on" : "off");
const modeColor =
effectiveMode === "on"
? "text-emerald-400"
: effectiveMode === "auto"
? "text-amber-400"
: "text-text-muted";
const modeDot =
effectiveMode === "on"
? "bg-emerald-400"
: effectiveMode === "auto"
? "bg-amber-400"
: "bg-border";
return (
<button
role="button"
onClick={onClick}
aria-pressed={selected}
className={`w-full text-left rounded-lg border p-3 transition-all focus:outline-none focus:ring-2 focus:ring-violet-500/50 ${
selected
? "border-violet-500 bg-violet-500/5"
: "border-border bg-surface/30 hover:border-border/80 hover:bg-surface/50"
}`}
>
<div className="flex items-start gap-2">
<div
className={`flex items-center justify-center size-8 rounded-md shrink-0 ${
selected ? "bg-violet-500/20" : "bg-surface/60"
}`}
>
<span className="material-symbols-outlined text-[18px] text-text-muted">
auto_fix_high
</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5 flex-wrap">
<span className="text-sm font-semibold text-text-main truncate">{skill.name}</span>
<span className="text-[10px] px-1.5 py-0.5 rounded bg-surface/60 text-text-muted shrink-0">
v{skill.version}
</span>
<span className="text-[10px] px-1.5 py-0.5 rounded bg-surface/60 text-text-muted shrink-0">
{(skill.sourceProvider || "local").toUpperCase()}
</span>
</div>
<p className="text-xs text-text-muted mt-0.5 line-clamp-2">{skill.description}</p>
{Array.isArray(skill.tags) && skill.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-1.5">
{skill.tags.slice(0, 3).map((tag) => (
<span
key={`${skill.id}-${tag}`}
className="text-[10px] px-1.5 py-0.5 rounded bg-violet-500/10 text-violet-300"
>
{tag}
</span>
))}
</div>
)}
</div>
<div className="flex items-center gap-1.5 shrink-0">
<span className={`inline-block size-2 rounded-full ${modeDot}`} />
<span className={`text-[10px] font-medium ${modeColor}`}>
{t(effectiveMode === "on" ? "onMode" : effectiveMode === "auto" ? "autoMode" : "offMode")}
</span>
</div>
</div>
</button>
);
}
export default OmniSkillCard;

View File

@@ -0,0 +1,159 @@
"use client";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
import { OmniSkillCard } from "./OmniSkillCard";
import { SkillInspectorPane } from "./SkillInspectorPane";
import type { OmniSkill } from "./OmniSkillCard";
interface OmniSkillsListProps {
skills: OmniSkill[];
skillsTotal: number;
skillsPage: number;
skillsTotalPages: number;
popularDefaults: string[];
searchTerm: string;
modeFilter: "all" | "on" | "off" | "auto";
selectedSkillId: string | null;
onSearchTermChange: (v: string) => void;
onModeFilterChange: (v: "all" | "on" | "off" | "auto") => void;
onApplyFilters: () => void;
onPagePrev: () => void;
onPageNext: () => void;
onSelectSkill: (id: string) => void;
onSetMode: (skillId: string, mode: "on" | "off" | "auto") => void;
onUninstall: (skillId: string) => void;
}
export function OmniSkillsList({
skills,
skillsTotal,
skillsPage,
skillsTotalPages,
popularDefaults,
searchTerm,
modeFilter,
selectedSkillId,
onSearchTermChange,
onModeFilterChange,
onApplyFilters,
onPagePrev,
onPageNext,
onSelectSkill,
onSetMode,
onUninstall,
}: OmniSkillsListProps): JSX.Element {
const t = useTranslations("skills");
const selectedSkill = selectedSkillId
? (skills.find((s) => s.id === selectedSkillId) ?? null)
: null;
return (
<div className="grid grid-cols-12 gap-4">
{/* Left: grid + filters */}
<div className="col-span-12 lg:col-span-7 flex flex-col gap-4">
<Card>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 items-center">
<input
type="text"
value={searchTerm}
onChange={(e) => onSearchTermChange(e.target.value)}
placeholder={t("filterSkillsPlaceholder")}
className="px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
/>
<select
value={modeFilter}
onChange={(e) => onModeFilterChange(e.target.value as "all" | "on" | "off" | "auto")}
className="px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
>
<option value="all">{t("allModes")}</option>
<option value="on">{t("onMode")}</option>
<option value="auto">{t("autoMode")}</option>
<option value="off">{t("offMode")}</option>
</select>
<button
onClick={onApplyFilters}
className="px-4 py-2 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 transition-colors"
>
{t("applyFilters")}
</button>
</div>
{popularDefaults.length > 0 && (
<div className="mt-3">
<p className="text-xs text-text-muted mb-2">{t("popularDefaultsLabel")}</p>
<div className="flex flex-wrap gap-2">
{popularDefaults.map((name) => (
<span
key={name}
className="text-xs px-2 py-1 rounded bg-violet-500/10 text-violet-300 border border-violet-500/20"
>
{name}
</span>
))}
</div>
</div>
)}
</Card>
{skills.length === 0 ? (
<Card>
<div className="text-center py-8 text-text-muted">{t("noSkills")}</div>
</Card>
) : (
<div className="flex flex-col gap-2">
{skills.map((skill) => (
<OmniSkillCard
key={skill.id}
skill={skill}
selected={selectedSkillId === skill.id}
onClick={() => onSelectSkill(skill.id)}
/>
))}
</div>
)}
<div className="flex items-center justify-between pt-2 border-t border-border">
<span className="text-sm text-text-muted">
{t("pageInfo", {
page: skillsPage,
totalPages: skillsTotalPages,
total: skillsTotal,
})}
</span>
<div className="flex gap-2">
<button
onClick={onPagePrev}
disabled={skillsPage === 1}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("previous")}
</button>
<button
onClick={onPageNext}
disabled={skillsPage === skillsTotalPages || skillsTotalPages === 0}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("next")}
</button>
</div>
</div>
</div>
{/* Right: inspector pane */}
<div className="col-span-12 lg:col-span-5">
<div className="sticky top-4 rounded-xl border border-border bg-surface/30 overflow-hidden min-h-[400px]">
<SkillInspectorPane
selectedSkillId={selectedSkillId}
skill={selectedSkill}
onSetMode={onSetMode}
onUninstall={onUninstall}
/>
</div>
</div>
</div>
);
}
export default OmniSkillsList;

View File

@@ -0,0 +1,297 @@
"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
import type { OmniSkill } from "./OmniSkillCard";
interface SkillDetail extends OmniSkill {
schema?: {
input?: Record<string, unknown>;
output?: Record<string, unknown>;
};
handler?: string;
}
interface SkillExecution {
id: string;
skillId: string;
skillName: string;
status: string;
duration: number;
createdAt: string;
}
type InspectorTab = "schema" | "handler" | "executions" | "sandbox";
interface SkillInspectorPaneProps {
selectedSkillId: string | null;
skill: OmniSkill | null;
onSetMode: (skillId: string, mode: "on" | "off" | "auto") => void;
onUninstall: (skillId: string) => void;
}
export function SkillInspectorPane({
selectedSkillId,
skill,
onSetMode,
onUninstall,
}: SkillInspectorPaneProps): JSX.Element {
const t = useTranslations("skills");
const [activeTab, setActiveTab] = useState<InspectorTab>("schema");
const [detail, setDetail] = useState<SkillDetail | null>(null);
const [executions, setExecutions] = useState<SkillExecution[]>([]);
const [loadingExecs, setLoadingExecs] = useState(false);
useEffect(() => {
let cancelled = false;
if (!selectedSkillId) {
// Reset via microtask to avoid synchronous setState-in-effect
Promise.resolve().then(() => {
if (!cancelled) {
setDetail(null);
setExecutions([]);
}
});
return () => {
cancelled = true;
};
}
// Fetch full skill detail for schema + handler
fetch(`/api/skills/${selectedSkillId}`)
.then((r) => (r.ok ? r.json() : null))
.then((data: SkillDetail | null) => {
if (!cancelled && data) setDetail(data);
})
.catch(() => null);
return () => {
cancelled = true;
};
}, [selectedSkillId]);
useEffect(() => {
if (!selectedSkillId || activeTab !== "executions") return;
let cancelled = false;
Promise.resolve().then(() => {
if (!cancelled) setLoadingExecs(true);
});
fetch(`/api/skills/executions?skillId=${selectedSkillId}&limit=20`)
.then((r) => (r.ok ? r.json() : { data: [] }))
.then((data: { data?: SkillExecution[] }) => {
if (!cancelled) {
setExecutions(data.data || []);
setLoadingExecs(false);
}
})
.catch(() => {
if (!cancelled) setLoadingExecs(false);
});
return () => {
cancelled = true;
};
}, [selectedSkillId, activeTab]);
if (!selectedSkillId || !skill) {
return (
<div className="flex flex-col items-center justify-center h-full min-h-[300px] text-text-muted text-sm text-center p-6">
<span className="material-symbols-outlined text-[40px] mb-3 opacity-30">
manage_search
</span>
<span>Selecione uma skill à esquerda para inspecionar.</span>
</div>
);
}
const effectiveMode = skill.mode || (skill.enabled ? "on" : "off");
const tabs: { id: InspectorTab; label: string }[] = [
{ id: "schema", label: "Schema" },
{ id: "handler", label: "Handler" },
{ id: "executions", label: t("executionsTab") },
{ id: "sandbox", label: t("sandboxTab") },
];
return (
<div className="flex flex-col h-full">
{/* Inspector header */}
<div className="px-4 pt-4 pb-2 border-b border-border">
<div className="flex items-center gap-2 mb-1">
<span className="material-symbols-outlined text-[18px] text-violet-400">
auto_fix_high
</span>
<h3 className="font-semibold text-text-main text-sm">{skill.name}</h3>
</div>
<p className="text-xs text-text-muted line-clamp-2">{skill.description}</p>
<div className="flex items-center gap-1.5 mt-1.5">
<span
className={`text-[10px] px-1.5 py-0.5 rounded ${
effectiveMode === "on"
? "bg-emerald-500/10 text-emerald-400"
: effectiveMode === "auto"
? "bg-amber-500/10 text-amber-400"
: "bg-surface/60 text-text-muted"
}`}
>
{t("mode")}: {effectiveMode}
</span>
<span className="text-[10px] px-1.5 py-0.5 rounded bg-surface/60 text-text-muted">
{(skill.sourceProvider || "local").toUpperCase()}
</span>
</div>
</div>
{/* Inspector sub-tabs */}
<div className="flex gap-0 border-b border-border px-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-3 py-2 text-xs font-medium border-b-2 transition-colors ${
activeTab === tab.id
? "border-violet-500 text-violet-400"
: "border-transparent text-text-muted hover:text-text-main"
}`}
>
{tab.label}
</button>
))}
</div>
{/* Inspector content */}
<div className="flex-1 overflow-auto p-4">
{activeTab === "schema" && (
<div className="space-y-3">
<div>
<p className="text-xs font-medium text-text-muted uppercase tracking-wide mb-1.5">
Input Schema
</p>
<pre className="text-xs bg-surface/40 rounded-lg p-3 overflow-auto max-h-[200px] text-text-main font-mono whitespace-pre-wrap">
{JSON.stringify(detail?.schema?.input ?? {}, null, 2) || "{}"}
</pre>
</div>
<div>
<p className="text-xs font-medium text-text-muted uppercase tracking-wide mb-1.5">
Output Schema
</p>
<pre className="text-xs bg-surface/40 rounded-lg p-3 overflow-auto max-h-[200px] text-text-main font-mono whitespace-pre-wrap">
{JSON.stringify(detail?.schema?.output ?? {}, null, 2) || "{}"}
</pre>
</div>
</div>
)}
{activeTab === "handler" && (
<div>
<p className="text-xs font-medium text-text-muted uppercase tracking-wide mb-1.5">
Handler Code
</p>
<pre className="text-xs bg-surface/40 rounded-lg p-3 overflow-auto max-h-[400px] text-text-main font-mono whitespace-pre-wrap">
{detail?.handler ?? "// Handler not available"}
</pre>
</div>
)}
{activeTab === "executions" && (
<div>
{loadingExecs ? (
<div className="text-xs text-text-muted">{t("loading")}...</div>
) : executions.length === 0 ? (
<div className="text-xs text-text-muted text-center py-6">{t("noExecutions")}</div>
) : (
<table className="w-full text-xs">
<thead>
<tr className="text-left text-text-muted border-b border-border">
<th className="pb-2 font-medium">{t("status")}</th>
<th className="pb-2 font-medium">{t("duration")}</th>
<th className="pb-2 font-medium">{t("time")}</th>
</tr>
</thead>
<tbody>
{executions.map((exec) => (
<tr key={exec.id} className="border-b border-border/40">
<td className="py-2">
<span
className={`px-1.5 py-0.5 rounded ${
exec.status === "success"
? "bg-emerald-500/10 text-emerald-400"
: exec.status === "error"
? "bg-red-500/10 text-red-400"
: "bg-amber-500/10 text-amber-400"
}`}
>
{exec.status}
</span>
</td>
<td className="py-2 text-text-muted">{exec.duration}ms</td>
<td className="py-2 text-text-muted">
{new Date(exec.createdAt).toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
)}
{activeTab === "sandbox" && (
<div className="space-y-2">
<p className="text-xs font-medium text-text-muted uppercase tracking-wide mb-1.5">
{t("sandboxConfig")}
</p>
{[
{ label: t("cpuLimit"), desc: t("cpuLimitDesc"), value: "100ms" },
{ label: t("memoryLimit"), desc: t("memoryLimitDesc"), value: "256MB" },
{ label: t("timeout"), desc: t("timeoutDesc"), value: "30s" },
{ label: t("networkAccess"), desc: t("networkAccessDesc"), value: t("disabled") },
].map((item) => (
<div
key={item.label}
className="flex items-center justify-between p-2.5 rounded-lg bg-surface/30"
>
<div>
<p className="text-xs font-medium">{item.label}</p>
<p className="text-[10px] text-text-muted">{item.desc}</p>
</div>
<span className="text-xs font-mono text-text-muted">{item.value}</span>
</div>
))}
<button className="w-full mt-3 px-3 py-2 text-xs font-medium rounded-lg border border-border text-text-muted hover:text-text-main hover:border-violet-500/50 transition-colors">
Run test (placeholder)
</button>
</div>
)}
</div>
{/* Inspector action buttons */}
<div className="px-4 py-3 border-t border-border flex items-center gap-1.5">
{(["on", "auto", "off"] as const).map((mode) => (
<button
key={mode}
onClick={() => onSetMode(skill.id, mode)}
aria-label={`Set mode ${mode}`}
className={`flex-1 text-xs px-2 py-1.5 rounded border transition-colors ${
effectiveMode === mode
? mode === "on"
? "border-emerald-500 text-emerald-400 bg-emerald-500/5"
: mode === "auto"
? "border-amber-500 text-amber-400 bg-amber-500/5"
: "border-red-500 text-red-400 bg-red-500/5"
: "border-border text-text-muted hover:border-border/80"
}`}
>
{mode === "on" ? t("onMode") : mode === "auto" ? t("autoMode") : t("offMode")}
</button>
))}
<button
onClick={() => onUninstall(skill.id)}
aria-label="Uninstall skill"
className="flex-1 text-xs px-2 py-1.5 rounded border border-border text-red-400 hover:bg-red-500/10 transition-colors"
>
{t("delete")}
</button>
</div>
</div>
);
}
export default SkillInspectorPane;

View File

@@ -0,0 +1,5 @@
import { OmniSkillsPageClient } from "./OmniSkillsPageClient";
export default function Page() {
return <OmniSkillsPageClient />;
}

View File

@@ -1,871 +0,0 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { Card } from "@/shared/components";
import { useTranslations } from "next-intl";
import type { SkillsProvider } from "@/lib/skills/providerSettings";
interface Skill {
id: string;
name: string;
version: string;
description: string;
enabled: boolean;
mode?: "on" | "off" | "auto";
sourceProvider?: "skillsmp" | "skillssh" | "local";
tags?: string[];
installCount?: number;
createdAt: string;
}
interface Execution {
id: string;
skillId: string;
skillName: string;
status: string;
duration: number;
createdAt: string;
}
export default function SkillsPage() {
const [skills, setSkills] = useState<Skill[]>([]);
const [executions, setExecutions] = useState<Execution[]>([]);
const [loading, setLoading] = useState(true);
const [skillsPage, setSkillsPage] = useState(1);
const [skillsTotal, setSkillsTotal] = useState(0);
const [skillsTotalPages, setSkillsTotalPages] = useState(1);
const [popularDefaults, setPopularDefaults] = useState<string[]>([]);
const [searchTerm, setSearchTerm] = useState("");
const [modeFilter, setModeFilter] = useState<"all" | "on" | "off" | "auto">("all");
const [execPage, setExecPage] = useState(1);
const [execTotal, setExecTotal] = useState(0);
const [execTotalPages, setExecTotalPages] = useState(1);
const [activeTab, setActiveTab] = useState<"skills" | "executions" | "sandbox" | "marketplace">(
"skills"
);
const [showInstallModal, setShowInstallModal] = useState(false);
const [installJson, setInstallJson] = useState("");
const [installStatus, setInstallStatus] = useState<{
type: "success" | "error";
message: string;
} | null>(null);
const [installing, setInstalling] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const [mpQuery, setMpQuery] = useState("");
const [mpResults, setMpResults] = useState<
{
name: string;
description: string;
skillMdContent?: string;
version?: string;
sourceUrl?: string;
}[]
>([]);
const [mpLoading, setMpLoading] = useState(false);
const [mpError, setMpError] = useState("");
const [mpInstallingId, setMpInstallingId] = useState<string | null>(null);
const [shQuery, setShQuery] = useState("");
const [shResults, setShResults] = useState<
{ id: string; skillId: string; name: string; installs: number; source: string }[]
>([]);
const [shLoading, setShLoading] = useState(false);
const [shError, setShError] = useState("");
const [shInstallingId, setShInstallingId] = useState<string | null>(null);
const [skillsProvider, setSkillsProvider] = useState<SkillsProvider>("skillsmp");
const t = useTranslations("skills");
const commonT = useTranslations("common");
const fetchSkills = async (page: number) => {
const params = new URLSearchParams({ page: String(page), limit: "20" });
if (searchTerm.trim()) params.set("q", searchTerm.trim());
if (modeFilter !== "all") params.set("mode", modeFilter);
const res = await fetch(`/api/skills?${params.toString()}`).then((r) => r.json());
setSkills(res.data || []);
setSkillsTotal(res.total || 0);
setSkillsTotalPages(res.totalPages || 1);
setPopularDefaults(Array.isArray(res.popularDefaults) ? res.popularDefaults : []);
};
const fetchExecutions = async (page: number) => {
const res = await fetch(`/api/skills/executions?page=${page}&limit=20`).then((r) => r.json());
setExecutions(res.data || []);
setExecTotal(res.total || 0);
setExecTotalPages(res.totalPages || 1);
};
useEffect(() => {
Promise.all([
fetch("/api/skills?page=1&limit=20").then((r) => r.json()),
fetch("/api/skills/executions?page=1&limit=20").then((r) => r.json()),
fetch("/api/settings").then((r) => (r.ok ? r.json() : null)),
])
.then(([skillsData, executionsData, settingsData]) => {
setSkills(skillsData.data || []);
setSkillsTotal(skillsData.total || 0);
setSkillsTotalPages(skillsData.totalPages || 1);
setPopularDefaults(
Array.isArray(skillsData.popularDefaults) ? skillsData.popularDefaults : []
);
setExecutions(executionsData.data || []);
setExecTotal(executionsData.total || 0);
setExecTotalPages(executionsData.totalPages || 1);
if (
settingsData?.skillsProvider === "skillsmp" ||
settingsData?.skillsProvider === "skillssh"
) {
setSkillsProvider(settingsData.skillsProvider);
}
setLoading(false);
})
.catch(() => setLoading(false));
}, []);
const refreshSkills = async () => {
setSkillsPage(1);
await fetchSkills(1);
};
const toggleSkill = async (skillId: string, enabled: boolean) => {
await fetch(`/api/skills/${skillId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: !enabled }),
});
setSkills(skills.map((s) => (s.id === skillId ? { ...s, enabled: !enabled } : s)));
};
const setSkillMode = async (skillId: string, mode: "on" | "off" | "auto") => {
await fetch(`/api/skills/${skillId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mode }),
});
setSkills(skills.map((s) => (s.id === skillId ? { ...s, mode, enabled: mode !== "off" } : s)));
};
const deleteSkill = async (skillId: string) => {
const res = await fetch(`/api/skills/${skillId}`, { method: "DELETE" });
if (res.ok) {
setSkills(skills.filter((s) => s.id !== skillId));
}
};
const handleInstall = async () => {
setInstalling(true);
setInstallStatus(null);
try {
const manifest = JSON.parse(installJson);
const res = await fetch("/api/skills/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(manifest),
});
const data = await res.json();
if (res.ok && data.success) {
setInstallStatus({ type: "success", message: t("installSuccess", { id: data.id }) });
setInstallJson("");
await refreshSkills();
} else {
setInstallStatus({
type: "error",
message: data.error || data.message || t("installError"),
});
}
} catch (err) {
setInstallStatus({
type: "error",
message: err instanceof Error ? err.message : t("invalidJson"),
});
} finally {
setInstalling(false);
}
};
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
setInstallJson((ev.target?.result as string) || "");
};
reader.readAsText(file);
};
const searchMarketplace = async () => {
setMpLoading(true);
setMpError("");
setMpResults([]);
try {
const res = await fetch(`/api/skills/marketplace?q=${encodeURIComponent(mpQuery)}`);
const data = await res.json();
if (!res.ok) {
setMpError(data.error || t("marketplaceError"));
} else {
setMpResults(Array.isArray(data) ? data : data.skills || []);
}
} catch (err) {
setMpError(err instanceof Error ? err.message : t("marketplaceError"));
} finally {
setMpLoading(false);
}
};
const installFromMarketplace = async (skill: {
name: string;
description: string;
skillMdContent?: string;
version?: string;
sourceUrl?: string;
}) => {
setMpInstallingId(skill.name);
try {
const res = await fetch("/api/skills/marketplace/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: skill.name,
description: skill.description,
skillMdContent: skill.skillMdContent || skill.description,
version: skill.version || "1.0.0",
sourceUrl: skill.sourceUrl,
}),
});
const data = await res.json();
if (res.ok && data.success) {
await refreshSkills();
setMpInstallingId(null);
} else {
setMpError(data.error || t("installError"));
setMpInstallingId(null);
}
} catch (err) {
setMpError(err instanceof Error ? err.message : t("installError"));
setMpInstallingId(null);
}
};
const searchSkillsSh = async () => {
setShLoading(true);
setShError("");
setShResults([]);
try {
const res = await fetch(`/api/skills/skillssh?q=${encodeURIComponent(shQuery)}`);
const data = await res.json();
if (!res.ok) {
setShError(data.error || t("marketplaceError"));
} else {
setShResults(data.skills || []);
}
} catch (err) {
setShError(err instanceof Error ? err.message : t("marketplaceError"));
} finally {
setShLoading(false);
}
};
const installFromSkillsSh = async (skill: {
id: string;
skillId: string;
name: string;
installs: number;
source: string;
}) => {
setShInstallingId(skill.id);
try {
const res = await fetch("/api/skills/skillssh/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: skill.name,
description: `Installed from skills.sh (${skill.source})`,
source: skill.source,
skillId: skill.skillId,
}),
});
const data = await res.json();
if (res.ok && data.success) {
await refreshSkills();
setShInstallingId(null);
} else {
setShError(data.error || t("installError"));
setShInstallingId(null);
}
} catch (err) {
setShError(err instanceof Error ? err.message : t("installError"));
setShInstallingId(null);
}
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-text-muted">{t("loading")}...</div>
</div>
);
}
// ── Stats computation ────────────────────────────────────────────────────
const enabledCount = skills.filter((s) => s.enabled).length;
const execSuccessCount = executions.filter((e) => e.status === "success").length;
const successRate =
executions.length > 0 ? Math.round((execSuccessCount / executions.length) * 100) : 0;
return (
<div className="flex flex-col gap-6">
{/* ── Stats Cards ─────────────────────────────────────────────────── */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">{t("totalSkills")}</p>
<p className="text-2xl font-bold text-text-main mt-1">{skillsTotal}</p>
</Card>
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">{t("enabledSkills")}</p>
<p className="text-2xl font-bold text-emerald-400 mt-1">{enabledCount}</p>
</Card>
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">{t("totalExecutions")}</p>
<p className="text-2xl font-bold text-violet-400 mt-1">{execTotal}</p>
</Card>
<Card className="p-4">
<p className="text-xs text-text-muted uppercase tracking-wide">{t("successRate")}</p>
<p className="text-2xl font-bold text-amber-400 mt-1">{successRate}%</p>
</Card>
</div>
<div className="flex justify-end">
<button
onClick={() => setShowInstallModal(true)}
className="px-4 py-2 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 transition-colors"
>
{t("installSkillButton")}
</button>
</div>
<div className="flex gap-2 border-b border-border">
<button
onClick={() => setActiveTab("skills")}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === "skills"
? "border-violet-500 text-violet-400"
: "border-transparent text-text-muted hover:text-text-main"
}`}
>
{t("skillsTab")}
</button>
<button
onClick={() => setActiveTab("executions")}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === "executions"
? "border-violet-500 text-violet-400"
: "border-transparent text-text-muted hover:text-text-main"
}`}
>
{t("executionsTab")}
</button>
<button
onClick={() => setActiveTab("sandbox")}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === "sandbox"
? "border-violet-500 text-violet-400"
: "border-transparent text-text-muted hover:text-text-main"
}`}
>
{t("sandboxTab")}
</button>
<button
onClick={() => setActiveTab("marketplace")}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === "marketplace"
? "border-violet-500 text-violet-400"
: "border-transparent text-text-muted hover:text-text-main"
}`}
>
{t("marketplaceTab")}
</button>
</div>
{activeTab === "skills" && (
<div className="grid gap-4">
<Card>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 items-center">
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t("filterSkillsPlaceholder")}
className="px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
/>
<select
value={modeFilter}
onChange={(e) => setModeFilter(e.target.value as "all" | "on" | "off" | "auto")}
className="px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
>
<option value="all">{t("allModes")}</option>
<option value="on">{t("onMode")}</option>
<option value="auto">{t("autoMode")}</option>
<option value="off">{t("offMode")}</option>
</select>
<button
onClick={() => {
setSkillsPage(1);
void fetchSkills(1);
}}
className="px-4 py-2 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 transition-colors"
>
{t("applyFilters")}
</button>
</div>
{popularDefaults.length > 0 && (
<div className="mt-3">
<p className="text-xs text-text-muted mb-2">{t("popularDefaultsLabel")}</p>
<div className="flex flex-wrap gap-2">
{popularDefaults.map((name) => (
<span
key={name}
className="text-xs px-2 py-1 rounded bg-violet-500/10 text-violet-300 border border-violet-500/20"
>
{name}
</span>
))}
</div>
</div>
)}
</Card>
{skills.length === 0 ? (
<Card>
<div className="text-center py-8 text-text-muted">{t("noSkills")}</div>
</Card>
) : (
skills.map((skill) => (
<Card key={skill.id}>
<div className="flex items-center justify-between">
<div>
<div className="flex items-center gap-2">
<h3 className="font-semibold">{skill.name}</h3>
<span className="text-xs px-2 py-0.5 rounded bg-surface/50 text-text-muted">
v{skill.version}
</span>
<span className="text-xs px-2 py-0.5 rounded bg-surface/50 text-text-muted">
{(skill.sourceProvider || "local").toUpperCase()}
</span>
<span className="text-xs px-2 py-0.5 rounded bg-amber-500/10 text-amber-400">
{t("mode")}: {skill.mode || (skill.enabled ? "on" : "off")}
</span>
</div>
<p className="text-sm text-text-muted mt-1">{skill.description}</p>
{Array.isArray(skill.tags) && skill.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{skill.tags.map((tag) => (
<span
key={`${skill.id}-${tag}`}
className="text-[11px] px-1.5 py-0.5 rounded bg-surface/60 text-text-muted"
>
{tag}
</span>
))}
</div>
)}
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1">
<button
onClick={() => setSkillMode(skill.id, "on")}
className={`text-xs px-2 py-1 rounded border ${
(skill.mode || (skill.enabled ? "on" : "off")) === "on"
? "border-emerald-500 text-emerald-400"
: "border-border text-text-muted"
}`}
>
{t("onMode")}
</button>
<button
onClick={() => setSkillMode(skill.id, "auto")}
className={`text-xs px-2 py-1 rounded border ${
(skill.mode || (skill.enabled ? "on" : "off")) === "auto"
? "border-amber-500 text-amber-400"
: "border-border text-text-muted"
}`}
>
{t("autoMode")}
</button>
<button
onClick={() => setSkillMode(skill.id, "off")}
className={`text-xs px-2 py-1 rounded border ${
(skill.mode || (skill.enabled ? "on" : "off")) === "off"
? "border-red-500 text-red-400"
: "border-border text-text-muted"
}`}
>
{t("offMode")}
</button>
</div>
<button
onClick={() => deleteSkill(skill.id)}
className="text-xs px-2 py-1 rounded text-red-400 hover:bg-red-500/10 transition-colors"
>
{t("delete")}
</button>
<button
onClick={() => toggleSkill(skill.id, skill.enabled)}
className={`relative w-11 h-6 rounded-full transition-colors ${
skill.enabled ? "bg-violet-500" : "bg-border"
}`}
role="switch"
aria-checked={skill.enabled}
>
<span
className={`absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform ${
skill.enabled ? "translate-x-5" : "translate-x-0"
}`}
/>
</button>
</div>
</div>
</Card>
))
)}
<div className="flex items-center justify-between mt-4 pt-4 border-t border-border">
<span className="text-sm text-text-muted">
{t("pageInfo", {
page: skillsPage,
totalPages: skillsTotalPages,
total: skillsTotal,
})}
</span>
<div className="flex gap-2">
<button
onClick={() => {
const p = Math.max(1, skillsPage - 1);
setSkillsPage(p);
fetchSkills(p);
}}
disabled={skillsPage === 1}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("previous")}
</button>
<button
onClick={() => {
const p = Math.min(skillsTotalPages, skillsPage + 1);
setSkillsPage(p);
fetchSkills(p);
}}
disabled={skillsPage === skillsTotalPages || skillsTotalPages === 0}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("next")}
</button>
</div>
</div>
</div>
)}
{activeTab === "executions" && (
<Card>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="text-left text-sm text-text-muted border-b border-border">
<th className="pb-3 font-medium">{t("skill")}</th>
<th className="pb-3 font-medium">{t("status")}</th>
<th className="pb-3 font-medium">{t("duration")}</th>
<th className="pb-3 font-medium">{t("time")}</th>
</tr>
</thead>
<tbody>
{executions.length === 0 ? (
<tr>
<td colSpan={4} className="py-8 text-center text-text-muted">
{t("noExecutions")}
</td>
</tr>
) : (
executions.map((exec) => (
<tr key={exec.id} className="border-b border-border/50">
<td className="py-3 font-medium">{exec.skillName}</td>
<td className="py-3">
<span
className={`text-xs px-2 py-1 rounded ${
exec.status === "success"
? "bg-emerald-500/10 text-emerald-400"
: exec.status === "error"
? "bg-red-500/10 text-red-400"
: "bg-amber-500/10 text-amber-400"
}`}
>
{exec.status}
</span>
</td>
<td className="py-3 text-text-muted">{exec.duration}ms</td>
<td className="py-3 text-text-muted text-sm">
{new Date(exec.createdAt).toLocaleString()}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
<div className="flex items-center justify-between mt-4 pt-4 border-t border-border">
<span className="text-sm text-text-muted">
{t("pageInfo", { page: execPage, totalPages: execTotalPages, total: execTotal }) ||
`Page ${execPage} of ${execTotalPages} (${execTotal} total)`}
</span>
<div className="flex gap-2">
<button
onClick={() => {
const p = Math.max(1, execPage - 1);
setExecPage(p);
fetchExecutions(p);
}}
disabled={execPage === 1}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("previous") || "Prev"}
</button>
<button
onClick={() => {
const p = Math.min(execTotalPages, execPage + 1);
setExecPage(p);
fetchExecutions(p);
}}
disabled={execPage === execTotalPages || execTotalPages === 0}
className="px-3 py-1 text-sm rounded border border-border text-text-muted hover:text-text-main disabled:opacity-40 transition-colors"
>
{t("next") || "Next"}
</button>
</div>
</div>
</Card>
)}
{activeTab === "sandbox" && (
<div className="grid gap-4">
<Card>
<h3 className="font-semibold mb-4">{t("sandboxConfig")}</h3>
<div className="grid gap-4">
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("cpuLimit")}</p>
<p className="text-xs text-text-muted">{t("cpuLimitDesc")}</p>
</div>
<span className="font-mono">100ms</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("memoryLimit")}</p>
<p className="text-xs text-text-muted">{t("memoryLimitDesc")}</p>
</div>
<span className="font-mono">256MB</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("timeout")}</p>
<p className="text-xs text-text-muted">{t("timeoutDesc")}</p>
</div>
<span className="font-mono">30s</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-surface/30">
<div>
<p className="font-medium">{t("networkAccess")}</p>
<p className="text-xs text-text-muted">{t("networkAccessDesc")}</p>
</div>
<span className="text-text-muted">{t("disabled")}</span>
</div>
</div>
</Card>
</div>
)}
{activeTab === "marketplace" && (
<div className="grid gap-4">
<Card>
<h3 className="font-semibold mb-2">{t("skillsMarketplace")}</h3>
<p className="text-sm text-text-muted mb-4">
{t("activeProvider")}{" "}
<span className="font-medium">
{skillsProvider === "skillsmp" ? "SkillsMP" : "skills.sh"}
</span>
. {t("changeInSettings")}
</p>
<div className="flex gap-2 mb-4">
<input
type="text"
value={skillsProvider === "skillsmp" ? mpQuery : shQuery}
onChange={(e) =>
skillsProvider === "skillsmp"
? setMpQuery(e.target.value)
: setShQuery(e.target.value)
}
onKeyDown={(e) =>
e.key === "Enter" &&
(skillsProvider === "skillsmp" ? searchMarketplace() : searchSkillsSh())
}
placeholder={t("searchMarketplacePlaceholder")}
className="flex-1 px-3 py-2 rounded-lg bg-background border border-border text-sm focus:outline-none focus:ring-1 focus:ring-violet-500"
/>
<button
onClick={() =>
skillsProvider === "skillsmp" ? searchMarketplace() : searchSkillsSh()
}
disabled={skillsProvider === "skillsmp" ? mpLoading : shLoading}
className="px-4 py-2 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{skillsProvider === "skillsmp"
? mpLoading
? t("searching")
: t("searchMarketplace")
: shLoading
? t("searching")
: t("searchMarketplace")}
</button>
</div>
{(skillsProvider === "skillsmp" ? mpError : shError) && (
<div className="p-3 rounded-lg bg-red-500/10 text-red-400 text-sm mb-4">
{skillsProvider === "skillsmp" ? mpError : shError}
</div>
)}
</Card>
{skillsProvider === "skillsmp" && mpResults.length > 0 && (
<div className="grid gap-3">
{mpResults.map((skill) => (
<Card key={skill.name}>
<div className="flex items-center justify-between">
<div>
<h4 className="font-semibold">{skill.name}</h4>
<p className="text-sm text-text-muted mt-1">{skill.description}</p>
</div>
<button
onClick={() => installFromMarketplace(skill)}
disabled={mpInstallingId === skill.name}
className="px-4 py-1.5 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{mpInstallingId === skill.name ? t("installing") : t("installSkillButton")}
</button>
</div>
</Card>
))}
</div>
)}
{skillsProvider === "skillssh" && shResults.length > 0 && (
<div className="grid gap-3">
{shResults.map((skill) => (
<Card key={skill.id}>
<div className="flex items-center justify-between">
<div>
<h4 className="font-semibold">{skill.name}</h4>
<p className="text-sm text-text-muted mt-1">
{skill.source} · {skill.installs.toLocaleString()} {t("installs")}
</p>
</div>
<button
onClick={() => installFromSkillsSh(skill)}
disabled={shInstallingId === skill.id}
className="px-4 py-1.5 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{shInstallingId === skill.id ? t("installing") : t("installSkillButton")}
</button>
</div>
</Card>
))}
</div>
)}
{skillsProvider === "skillsmp" && !mpLoading && mpResults.length === 0 && !mpError && (
<Card>
<div className="text-center py-8 text-text-muted">{t("marketplaceSkillsMpHint")}</div>
</Card>
)}
{skillsProvider === "skillssh" && !shLoading && shResults.length === 0 && !shError && (
<Card>
<div className="text-center py-8 text-text-muted">{t("marketplaceSkillsShHint")}</div>
</Card>
)}
</div>
)}
{showInstallModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-surface border border-border rounded-xl p-6 w-full max-w-lg mx-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">{t("installSkillModalTitle")}</h2>
<button
onClick={() => {
setShowInstallModal(false);
setInstallStatus(null);
setInstallJson("");
}}
className="text-text-muted hover:text-text-main"
>
X
</button>
</div>
<p className="text-sm text-text-muted mb-4">{t("installSkillModalDesc")}</p>
<textarea
value={installJson}
onChange={(e) => setInstallJson(e.target.value)}
placeholder={t("installJsonPlaceholder")}
className="w-full h-48 p-3 rounded-lg bg-background border border-border text-sm font-mono resize-none focus:outline-none focus:ring-1 focus:ring-violet-500"
/>
<div className="flex items-center gap-3 mt-3">
<input
ref={fileInputRef}
type="file"
accept=".json"
onChange={handleFileUpload}
className="hidden"
/>
<button
onClick={() => fileInputRef.current?.click()}
className="px-3 py-1.5 text-sm rounded-lg border border-border text-text-muted hover:text-text-main transition-colors"
>
{t("uploadJson")}
</button>
<div className="flex-1" />
<button
onClick={() => {
setShowInstallModal(false);
setInstallStatus(null);
setInstallJson("");
}}
className="px-3 py-1.5 text-sm rounded-lg border border-border text-text-muted hover:text-text-main transition-colors"
>
{t("cancel")}
</button>
<button
onClick={handleInstall}
disabled={installing || !installJson.trim()}
className="px-4 py-1.5 text-sm font-medium rounded-lg bg-violet-500 text-white hover:bg-violet-600 disabled:opacity-50 transition-colors"
>
{installing ? t("installing") : t("installSkillButton")}
</button>
</div>
{installStatus && (
<div
className={`mt-3 p-3 rounded-lg text-sm ${
installStatus.type === "success"
? "bg-emerald-500/10 text-emerald-400"
: "bg-red-500/10 text-red-400"
}`}
>
{installStatus.message}
</div>
)}
</div>
</div>
)}
</div>
);
}

View File

@@ -102,6 +102,15 @@ export async function GET() {
"Summarize active rate limits and lockouts",
],
},
{
id: "list-capabilities",
name: "List Capabilities",
description:
"Returns the full catalog of 42 OmniRoute agent skills (22 API + 20 CLI) " +
"with raw URLs for the SKILL.md docs.",
tags: ["discovery", "capabilities"],
examples: ["What can you do?", "List your skills", "Show capabilities"],
},
],
authentication: {
schemes: ["api-key"],

View File

@@ -0,0 +1,73 @@
/**
* GET /api/agent-skills/[id]/raw
*
* Returns the raw SKILL.md content for a given skill as text/markdown.
* Resolution order: local filesystem → GitHub raw URL (1-hour cache).
*
* Response: text/markdown; charset=utf-8
* 404 if skill not found in catalog.
* 502 if upstream GitHub fetch fails.
*/
import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts";
import { getSkillById, fetchSkillMarkdown } from "@/lib/agentSkills/catalog";
export const dynamic = "force-dynamic";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try {
const { id } = await params;
if (!id || typeof id !== "string") {
return new Response(JSON.stringify(buildErrorBody(400, "Missing skill id")), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
// Validate the skill exists in catalog before attempting fetch
const skill = getSkillById(id);
if (!skill) {
return new Response(JSON.stringify(buildErrorBody(404, `Skill not found: ${id}`)), {
status: 404,
headers: { "Content-Type": "application/json" },
});
}
const markdown = await fetchSkillMarkdown(id);
// Return the full markdown content (frontmatter + body reconstructed)
const content =
markdown.frontmatter.name
? `---\nname: ${markdown.frontmatter.name}\ndescription: ${markdown.frontmatter.description}\n---\n${markdown.body}`
: markdown.body;
return new Response(content, {
status: 200,
headers: {
"Content-Type": "text/markdown; charset=utf-8",
"Cache-Control": "public, max-age=3600",
"X-Skill-Source": markdown.source,
"X-Skill-Fetched-At": markdown.fetchedAt,
},
});
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to fetch skill markdown";
// Distinguish GitHub upstream failures (502) from internal errors (500)
if (message.includes("GitHub raw fetch failed")) {
return new Response(
JSON.stringify(buildErrorBody(502, "Upstream fetch failed — try again later")),
{ status: 502, headers: { "Content-Type": "application/json" } },
);
}
console.error("[API] GET /api/agent-skills/[id]/raw error:", error);
return new Response(JSON.stringify(buildErrorBody(500, "Failed to fetch skill content")), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}

View File

@@ -0,0 +1,37 @@
/**
* GET /api/agent-skills/[id]
*
* Returns a single AgentSkill by its canonical ID.
*
* Response: AgentSkill
* 404 if skill not found in catalog.
*/
import { NextResponse } from "next/server";
import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts";
import { getSkillById } from "@/lib/agentSkills/catalog";
export const dynamic = "force-dynamic";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try {
const { id } = await params;
if (!id || typeof id !== "string") {
return NextResponse.json(buildErrorBody(400, "Missing skill id"), { status: 400 });
}
const skill = getSkillById(id);
if (!skill) {
return NextResponse.json(buildErrorBody(404, `Skill not found: ${id}`), { status: 404 });
}
return NextResponse.json(skill);
} catch (error) {
console.error("[API] GET /api/agent-skills/[id] error:", error);
return NextResponse.json(buildErrorBody(500, "Failed to load skill"), { status: 500 });
}
}

View File

@@ -0,0 +1,25 @@
/**
* GET /api/agent-skills/coverage
*
* Returns SkillCoverage: how many skills have a SKILL.md on filesystem vs catalog total.
*
* Response: SkillCoverage
*/
import { NextResponse } from "next/server";
import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts";
import { computeCoverage } from "@/lib/agentSkills/catalog";
export const dynamic = "force-dynamic";
export async function GET() {
try {
const coverage = computeCoverage();
return NextResponse.json(coverage);
} catch (error) {
console.error("[API] GET /api/agent-skills/coverage error:", error);
return NextResponse.json(buildErrorBody(500, "Failed to compute skill coverage"), {
status: 500,
});
}
}

View File

@@ -0,0 +1,68 @@
/**
* POST /api/agent-skills/generate
*
* Triggers the Agent Skills generator. Requires management auth.
* Default: dryRun=true, prune=false (safe preview mode).
*
* Body (all optional):
* - dryRun?: boolean (default true)
* - prune?: boolean (default false)
* - onlyIds?: string[] (only regenerate these skill IDs)
*
* Response: GeneratorReport
* 401 if unauthenticated.
* 400 if body is malformed.
* 503 if generator module is not yet available (F3 not merged).
* 500 on internal error.
*/
import { NextResponse } from "next/server";
import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { GenerateBodySchema } from "@/lib/agentSkills/schemas";
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
// Management auth — returns null if OK, error Response if not
const authError = await requireManagementAuth(request);
if (authError) return authError;
// Parse + validate request body
let rawBody: unknown;
try {
rawBody = await request.json();
} catch {
return NextResponse.json(buildErrorBody(400, "Request body must be valid JSON"), {
status: 400,
});
}
const parsed = GenerateBodySchema.safeParse(rawBody ?? {});
if (!parsed.success) {
return NextResponse.json(
buildErrorBody(400, parsed.error.issues[0]?.message ?? "Invalid request body"),
{ status: 400 },
);
}
// Dynamic import — F3 (generator.ts) may not be merged yet
let generateAgentSkills: (opts: typeof parsed.data) => Promise<unknown>;
try {
const mod = await import("@/lib/agentSkills/generator");
generateAgentSkills = mod.generateAgentSkills;
} catch {
return NextResponse.json(
buildErrorBody(503, "Generator module is not yet available"),
{ status: 503 },
);
}
try {
const report = await generateAgentSkills(parsed.data);
return NextResponse.json(report);
} catch (error) {
console.error("[API] POST /api/agent-skills/generate error:", error);
return NextResponse.json(buildErrorBody(500, "Generator failed"), { status: 500 });
}
}

View File

@@ -0,0 +1,47 @@
/**
* GET /api/agent-skills
*
* Returns the full Agent Skills catalog with optional filtering by category or area.
*
* Query params:
* - category?: "api" | "cli"
* - area?: string
*
* Response: { skills: AgentSkill[], count: number, coverage: SkillCoverage }
*/
import { NextResponse } from "next/server";
import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts";
import { ListQuerySchema } from "@/lib/agentSkills/schemas";
import { filterCatalog, computeCoverage } from "@/lib/agentSkills/catalog";
// Catalog reads filesystem on demand — disable static caching
export const dynamic = "force-dynamic";
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const parsed = ListQuerySchema.safeParse({
category: searchParams.get("category") ?? undefined,
area: searchParams.get("area") ?? undefined,
});
if (!parsed.success) {
return NextResponse.json(
buildErrorBody(400, parsed.error.issues[0]?.message ?? "Invalid query parameters"),
{ status: 400 },
);
}
const skills = filterCatalog(parsed.data);
const coverage = computeCoverage();
return NextResponse.json({ skills, count: skills.length, coverage });
} catch (error) {
console.error("[API] GET /api/agent-skills error:", error);
return NextResponse.json(buildErrorBody(500, "Failed to load agent skills catalog"), {
status: 500,
});
}
}

View File

@@ -1261,7 +1261,8 @@
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining",
"omniSkillsDescription": "Install and manage sandbox skills for automated prompt and tool execution"
},
"home": {
"quickStart": "Quick Start",
@@ -7878,5 +7879,76 @@
"spawnArgs": "Spawn arguments",
"spawnArgsPlaceholder": "e.g.: --quiet, --json",
"cliCodeRedirectCta": "Open CLI Code's"
},
"agentSkills": {
"pageTitle": "Agent Skills",
"pageSubtitle": "Teach your agent to operate OmniRoute — 22 API areas + 20 CLI families",
"conceptCard": {
"agent": {
"title": "Agent Skills — Outbound",
"description": "Agent Skills are machine-readable SKILL.md documents that external AI agents (Claude Code, Cursor, Copilot…) fetch from GitHub to learn how to operate OmniRoute via REST or CLI. They are read by the agent, not executed by OmniRoute.",
"crossLinkLabel": "Understand the difference →"
},
"omni": {
"title": "Omni Skills — Inbound",
"description": "Omni Skills are sandbox tools that OmniRoute injects into the model's context on every request. They are executed by OmniRoute, not read by the agent.",
"crossLinkLabel": "Understand the difference →"
},
"comparison": {
"colAgent": "Agent Skills",
"colOmni": "Omni Skills",
"whatIs": {
"label": "What it is",
"agent": "Machine-readable SKILL.md that teaches an external agent to operate OmniRoute",
"omni": "Executable tools that OmniRoute injects as tools into LLM requests"
},
"direction": {
"label": "Direction",
"agent": "Outbound — external agent learns to control OmniRoute",
"omni": "Inbound — OmniRoute gives tools to models passing through it"
},
"executor": {
"label": "Executor",
"agent": "External agent (reads markdown → acts via API/CLI)",
"omni": "OmniRoute itself (intercepts tool_calls, Docker sandbox)"
},
"storage": {
"label": "Storage",
"agent": "Dynamic catalog (/api/agent-skills) → SKILL.md on GitHub",
"omni": "SQLite (SkillsMP / skills.sh / local)"
},
"tagline": {
"label": "Tagline",
"agent": "Teach your agent to use OmniRoute",
"omni": "Give executable tools to OmniRoute models"
}
}
},
"filters": {
"category": "Category",
"area": "Area",
"searchPlaceholder": "Search skills…"
},
"categoryApi": "API",
"categoryCli": "CLI",
"filterAll": "All",
"coverageLabel": "Coverage",
"mcpUrl": "MCP URL",
"a2aLink": "A2A",
"copyUrl": "Copy URL",
"viewOnGithub": "View on GitHub",
"previewLoading": "Loading skill documentation…",
"previewError": "Failed to load skill documentation.",
"previewEmpty": "Select a skill to preview its documentation.",
"generateButton": "Generate missing skills",
"coverageBar": {
"complete": "Complete",
"partial": "Partial"
},
"noSkillsFound": "No skills found matching your filters.",
"regenerateConfirm": "This will regenerate all missing SKILL.md files. Continue?",
"regenerateRunning": "Regenerating skills…",
"regenerateSuccess": "Skills regenerated successfully.",
"regenerateError": "Failed to regenerate skills."
}
}

View File

@@ -3110,7 +3110,8 @@
"settingsResilienceDescription": "Circuit breaker, retry, and fallback configuration",
"settingsAdvancedDescription": "Advanced payload rules, request limits, and proxy API settings",
"mitmProxyDescription": "Configure MITM proxy settings for traffic inspection and debugging",
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining"
"oneProxyDescription": "Configure 1Proxy settings for advanced proxy chaining",
"omniSkillsDescription": "Instale e gerencie skills sandbox para execução automatizada de prompts e ferramentas"
},
"health": {
"title": "Saúde do Sistema",
@@ -7870,5 +7871,76 @@
"spawnArgs": "Argumentos de spawn",
"spawnArgsPlaceholder": "ex: --quiet, --json",
"cliCodeRedirectCta": "Abrir CLI Code's"
},
"agentSkills": {
"pageTitle": "Agent Skills",
"pageSubtitle": "Ensine seu agente a operar o OmniRoute — 22 áreas de API + 20 famílias de CLI",
"conceptCard": {
"agent": {
"title": "Agent Skills — Saída",
"description": "Agent Skills são documentos SKILL.md legíveis por máquina que agentes externos (Claude Code, Cursor, Copilot…) baixam do GitHub para aprender a operar o OmniRoute via REST ou CLI. São lidos pelo agente, não executados pelo OmniRoute.",
"crossLinkLabel": "Entenda a diferença →"
},
"omni": {
"title": "Omni Skills — Entrada",
"description": "Omni Skills são ferramentas sandbox que o OmniRoute injeta no contexto do modelo a cada solicitação. São executadas pelo OmniRoute, não lidas pelo agente.",
"crossLinkLabel": "Entenda a diferença →"
},
"comparison": {
"colAgent": "Agent Skills",
"colOmni": "Omni Skills",
"whatIs": {
"label": "O que é",
"agent": "Documentação (SKILL.md) que ensina um agente externo a operar o OmniRoute",
"omni": "Ferramentas executáveis que o Omni injeta como tools nos requests LLM"
},
"direction": {
"label": "Direção",
"agent": "Outbound — externo aprende a controlar o Omni",
"omni": "Inbound — Omni dá tools aos modelos que passam por ele"
},
"executor": {
"label": "Executor",
"agent": "Agente externo (lê markdown → age via API/CLI)",
"omni": "O próprio Omni (intercepta tool_calls, sandbox Docker)"
},
"storage": {
"label": "Storage",
"agent": "Catálogo dinâmico (/api/agent-skills) → SKILL.md no GitHub",
"omni": "SQLite (SkillsMP / skills.sh / local)"
},
"tagline": {
"label": "Frase",
"agent": "Ensine seu agente a usar o OmniRoute",
"omni": "Dê ferramentas executáveis aos modelos do OmniRoute"
}
}
},
"filters": {
"category": "Categoria",
"area": "Área",
"searchPlaceholder": "Buscar skills…"
},
"categoryApi": "API",
"categoryCli": "CLI",
"filterAll": "Todas",
"coverageLabel": "Cobertura",
"mcpUrl": "URL MCP",
"a2aLink": "A2A",
"copyUrl": "Copiar URL",
"viewOnGithub": "Ver no GitHub",
"previewLoading": "Carregando documentação da skill…",
"previewError": "Falha ao carregar documentação da skill.",
"previewEmpty": "Selecione uma skill para visualizar sua documentação.",
"generateButton": "Gerar skills faltantes",
"coverageBar": {
"complete": "Completo",
"partial": "Parcial"
},
"noSkillsFound": "Nenhuma skill encontrada para os filtros selecionados.",
"regenerateConfirm": "Isso irá regenerar todos os arquivos SKILL.md faltantes. Continuar?",
"regenerateRunning": "Regenerando skills…",
"regenerateSuccess": "Skills regeneradas com sucesso.",
"regenerateError": "Falha ao regenerar skills."
}
}

Some files were not shown because too many files have changed in this diff Show More