@oracle-agent/oracle 0.13.1 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +99 -0
- package/README.md +20 -14
- package/SECURITY.md +6 -8
- package/SETUP.md +25 -27
- package/bin/desk-server.mjs +473 -0
- package/bin/oracle-data-mcp.mjs +1252 -0
- package/bin/oracle-equities.mjs +168 -0
- package/bin/oracle-init.mjs +525 -0
- package/bin/oracle-public-server.mjs +36 -0
- package/bin/oracle-route.mjs +254 -0
- package/bin/oracle-scan.mjs +192 -0
- package/bin/oracle-upgrade.mjs +42 -0
- package/bin/oracle.mjs +34 -0
- package/docs/adding-a-chain.md +229 -0
- package/docs/architecture.md +139 -0
- package/docs/buzz-integration.md +115 -0
- package/docs/cli.md +143 -0
- package/docs/connectors.md +13 -0
- package/docs/hyperliquid-builder-code.md +50 -0
- package/docs/locals-only-fee-waiver.md +34 -0
- package/docs/oracle-pack-standard.md +29 -0
- package/docs/profiles.md +151 -0
- package/docs/public-surface.md +206 -0
- package/examples/add-a-chain.mjs +65 -0
- package/examples/oracle-pack-template.mjs +38 -0
- package/examples/research-a-token.mjs +70 -0
- package/package.json +78 -32
- package/plugins/oracle-owner-gate/__init__.py +288 -0
- package/plugins/oracle-owner-gate/plugin.yaml +9 -0
- package/{dist/assets/profiles → profiles}/oracle/SOUL.md +0 -14
- package/protocols/templates/safe-erc20/SECURITY.md +16 -0
- package/protocols/templates/safe-erc20/foundry.toml +12 -0
- package/protocols/templates/safe-erc20/remappings.txt +2 -0
- package/protocols/templates/safe-erc20/src/SafeERC20.sol +53 -0
- package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +72 -0
- package/public/oracle-splash/_variants/ice.html +3 -3
- package/public/oracle-splash/_variants/ivory.html +3 -3
- package/public/oracle-splash/downloads/index.html +34 -223
- package/public/oracle-splash/index.html +11 -11
- package/scripts/adversarial-bench.mjs +114 -0
- package/scripts/build-dist.mjs +119 -0
- package/scripts/build-inscription.py +230 -0
- package/scripts/check-doc-drift.mjs +123 -0
- package/scripts/check-test-count.mjs +105 -0
- package/scripts/e2e-hl-markets.mjs +21 -0
- package/scripts/e2e-hl-perps.mjs +48 -0
- package/scripts/e2e-hypercore-staking.mjs +128 -0
- package/scripts/e2e-solana-bitcoin.mjs +183 -0
- package/scripts/guard-publish.mjs +22 -0
- package/scripts/protocol-template-gate.mjs +15 -0
- package/scripts/public-api-scan.mjs +23 -0
- package/scripts/publish-dist.mjs +67 -0
- package/scripts/record-hero-cli.sh +80 -0
- package/scripts/secret-scan.mjs +169 -0
- package/scripts/smoke-dist-bins.mjs +89 -0
- package/scripts/verify-dist.mjs +29 -0
- package/scripts/verify-v3-venues.mjs +233 -0
- package/skins/oracle.yaml +56 -0
- package/src/action-receipts.mjs +265 -0
- package/src/action-semantics.mjs +62 -0
- package/src/address-book.mjs +208 -0
- package/src/agent-auth.mjs +206 -0
- package/src/approval-guard.mjs +282 -0
- package/src/attestation-secret.mjs +94 -0
- package/src/audit-log.mjs +196 -0
- package/src/auth/oauth.mjs +672 -0
- package/src/auto-slippage.mjs +378 -0
- package/src/capability-posture.mjs +125 -0
- package/src/cards.mjs +422 -0
- package/src/chains.mjs +62 -0
- package/src/cli/chain-catalog.mjs +215 -0
- package/src/cli/chain-state.mjs +74 -0
- package/src/cli/commands/auth.mjs +193 -0
- package/src/cli/commands/bootstrap.mjs +87 -0
- package/src/cli/commands/chain.mjs +143 -0
- package/src/cli/commands/chat.mjs +465 -0
- package/src/cli/commands/credential.mjs +9 -0
- package/src/cli/commands/data-mcp.mjs +18 -0
- package/src/cli/commands/data.mjs +75 -0
- package/src/cli/commands/doctor.mjs +136 -0
- package/src/cli/commands/equities.mjs +41 -0
- package/src/cli/commands/farm.mjs +129 -0
- package/src/cli/commands/fees.mjs +109 -0
- package/src/cli/commands/follow.mjs +130 -0
- package/src/cli/commands/harness.mjs +63 -0
- package/src/cli/commands/help.mjs +7 -0
- package/src/cli/commands/init.mjs +27 -0
- package/src/cli/commands/mcp.mjs +154 -0
- package/src/cli/commands/model.mjs +106 -0
- package/src/cli/commands/plugins.mjs +275 -0
- package/src/cli/commands/prepare.mjs +12 -0
- package/src/cli/commands/public.mjs +20 -0
- package/src/cli/commands/resolve.mjs +64 -0
- package/src/cli/commands/route.mjs +12 -0
- package/src/cli/commands/runner.mjs +9 -0
- package/src/cli/commands/scan.mjs +12 -0
- package/src/cli/commands/setup.mjs +294 -0
- package/src/cli/commands/sign.mjs +21 -0
- package/src/cli/commands/signer.mjs +9 -0
- package/src/cli/commands/swap.mjs +102 -0
- package/src/cli/commands/upgrade.mjs +12 -0
- package/src/cli/commands/vault.mjs +9 -0
- package/src/cli/commands/version.mjs +22 -0
- package/src/cli/first-run.mjs +23 -0
- package/src/cli/kernel.mjs +262 -0
- package/src/cli/mcp-targets/chatgpt.mjs +51 -0
- package/src/cli/mcp-targets/claude-code.mjs +39 -0
- package/src/cli/mcp-targets/claude-desktop.mjs +24 -0
- package/src/cli/mcp-targets/codex.mjs +41 -0
- package/src/cli/mcp-targets/cursor.mjs +32 -0
- package/src/cli/mcp-targets/shared.mjs +71 -0
- package/src/cli/mcp-targets/vscode.mjs +39 -0
- package/src/cli/messaging-platforms.mjs +217 -0
- package/src/cli/model-config.mjs +78 -0
- package/src/cli/operator-dispatch.mjs +194 -0
- package/src/cli/oracle-harness.py +415 -0
- package/src/cli/paths.mjs +107 -0
- package/src/cli/runtime.mjs +351 -0
- package/src/cli/setup-state.mjs +168 -0
- package/src/cli/spawn-child.mjs +43 -0
- package/src/data/catalog.mjs +858 -0
- package/src/data/desk-data.mjs +877 -0
- package/src/data/http.mjs +253 -0
- package/src/data/names.mjs +188 -0
- package/src/data/provider-endpoint.mjs +94 -0
- package/src/data/providers/aave-v3.mjs +139 -0
- package/src/data/providers/across-bridge.mjs +41 -0
- package/src/data/providers/aerodrome.mjs +245 -0
- package/src/data/providers/aevo.mjs +33 -0
- package/src/data/providers/approval-indexer.mjs +133 -0
- package/src/data/providers/approvals.mjs +594 -0
- package/src/data/providers/babylon-staking.mjs +149 -0
- package/src/data/providers/balancer.mjs +209 -0
- package/src/data/providers/beefy-yields.mjs +45 -0
- package/src/data/providers/birdeye-tokens.mjs +33 -0
- package/src/data/providers/bisq-markets.mjs +145 -0
- package/src/data/providers/bitcoin-esplora.mjs +216 -0
- package/src/data/providers/bitcoin-meta.mjs +378 -0
- package/src/data/providers/blockscout.mjs +14 -0
- package/src/data/providers/bob-rootstock.mjs +198 -0
- package/src/data/providers/braiins-insights.mjs +103 -0
- package/src/data/providers/bridge-aggregator.mjs +259 -0
- package/src/data/providers/bridges.mjs +242 -0
- package/src/data/providers/bungee.mjs +29 -0
- package/src/data/providers/celestia-da.mjs +38 -0
- package/src/data/providers/changenow.mjs +135 -0
- package/src/data/providers/compound.mjs +32 -0
- package/src/data/providers/connext.mjs +28 -0
- package/src/data/providers/convex.mjs +30 -0
- package/src/data/providers/cow-protocol.mjs +110 -0
- package/src/data/providers/cowswap.mjs +415 -0
- package/src/data/providers/curve.mjs +201 -0
- package/src/data/providers/das-assets.mjs +59 -0
- package/src/data/providers/debridge.mjs +109 -0
- package/src/data/providers/defillama.mjs +88 -0
- package/src/data/providers/dexscreener.mjs +43 -0
- package/src/data/providers/drift.mjs +32 -0
- package/src/data/providers/dydx.mjs +41 -0
- package/src/data/providers/evm-rpc.mjs +225 -0
- package/src/data/providers/farming.mjs +291 -0
- package/src/data/providers/geckoterminal.mjs +34 -0
- package/src/data/providers/gmx.mjs +496 -0
- package/src/data/providers/hiro-stacks.mjs +150 -0
- package/src/data/providers/hl-assets.mjs +165 -0
- package/src/data/providers/hl-info.mjs +108 -0
- package/src/data/providers/hl-markets.mjs +210 -0
- package/src/data/providers/hl-outcome.mjs +61 -0
- package/src/data/providers/hl-perps.mjs +419 -0
- package/src/data/providers/hl-staking.mjs +353 -0
- package/src/data/providers/hl-ws.mjs +119 -0
- package/src/data/providers/hyperevm-dex.mjs +49 -0
- package/src/data/providers/jito-mev.mjs +117 -0
- package/src/data/providers/jupiter-venues.mjs +119 -0
- package/src/data/providers/jupiter.mjs +315 -0
- package/src/data/providers/kamino-strategies.mjs +143 -0
- package/src/data/providers/l2beat-tvl.mjs +42 -0
- package/src/data/providers/lifi.mjs +166 -0
- package/src/data/providers/liquid-esplora.mjs +152 -0
- package/src/data/providers/magiceden-ordinals.mjs +141 -0
- package/src/data/providers/magiceden-sol.mjs +406 -0
- package/src/data/providers/mempool-lightning.mjs +144 -0
- package/src/data/providers/mempool-mining.mjs +142 -0
- package/src/data/providers/meteora-dlmm.mjs +168 -0
- package/src/data/providers/morpho.mjs +177 -0
- package/src/data/providers/nft-gallery.mjs +163 -0
- package/src/data/providers/nft-portfolio.mjs +494 -0
- package/src/data/providers/odos.mjs +156 -0
- package/src/data/providers/ondo.mjs +31 -0
- package/src/data/providers/oneinch.mjs +174 -0
- package/src/data/providers/opensea-multichain.mjs +136 -0
- package/src/data/providers/opensea-nft.mjs +371 -0
- package/src/data/providers/orca.mjs +29 -0
- package/src/data/providers/ordinals-runes.mjs +180 -0
- package/src/data/providers/pancakeswap.mjs +28 -0
- package/src/data/providers/paraswap.mjs +125 -0
- package/src/data/providers/pendle.mjs +188 -0
- package/src/data/providers/poly-clob.mjs +283 -0
- package/src/data/providers/poly-public.mjs +96 -0
- package/src/data/providers/poly-ws.mjs +103 -0
- package/src/data/providers/polygon-staking.mjs +227 -0
- package/src/data/providers/portfolio-history.mjs +394 -0
- package/src/data/providers/portfolio.mjs +600 -0
- package/src/data/providers/pyth-price-feeds.mjs +172 -0
- package/src/data/providers/raydium.mjs +35 -0
- package/src/data/providers/relayscan-mev.mjs +72 -0
- package/src/data/providers/rfq.mjs +15 -0
- package/src/data/providers/rh-agent.mjs +59 -0
- package/src/data/providers/sanctum-lst.mjs +180 -0
- package/src/data/providers/satflow.mjs +341 -0
- package/src/data/providers/signed-material-guard.mjs +108 -0
- package/src/data/providers/solana-rpc.mjs +197 -0
- package/src/data/providers/solend-lending.mjs +166 -0
- package/src/data/providers/spark-lend.mjs +42 -0
- package/src/data/providers/squid-router.mjs +29 -0
- package/src/data/providers/stargate.mjs +29 -0
- package/src/data/providers/sushiswap.mjs +30 -0
- package/src/data/providers/symbiotic-restaking.mjs +41 -0
- package/src/data/providers/thorchain.mjs +36 -0
- package/src/data/providers/traderjoe.mjs +28 -0
- package/src/data/providers/twap.mjs +152 -0
- package/src/data/providers/uniswap-v3.mjs +457 -0
- package/src/data/providers/uniswap-v4.mjs +414 -0
- package/src/data/providers/vertex.mjs +41 -0
- package/src/data/providers/wallet-watch.mjs +264 -0
- package/src/data/providers/wormhole.mjs +28 -0
- package/src/data/providers/yearn.mjs +31 -0
- package/src/data/providers/zerox.mjs +167 -0
- package/src/data/public-api-scan.mjs +61 -0
- package/src/data/quote-placeholder.mjs +31 -0
- package/src/desk/swap-prepare.mjs +194 -0
- package/src/equities/bridge.mjs +388 -0
- package/src/equities/fixtures/bridge_routes.json +159 -0
- package/src/equities/fixtures/dst_transitions.json +29 -0
- package/src/equities/fixtures/equity_dex_pools.json +154 -0
- package/src/equities/fixtures/hip3_l2books.json +2711 -0
- package/src/equities/fixtures/nyse_holidays.json +1 -0
- package/src/equities/fixtures/oracles.json +100 -0
- package/src/equities/fixtures/rh_pool_state.json +244 -0
- package/src/equities/fixtures/solana_xstocks.json +704 -0
- package/src/equities/fixtures/ton_xstocks.json +35 -0
- package/src/equities/fixtures/universe.json +411 -0
- package/src/equities/fixtures/venues.json +3542 -0
- package/src/equities/fixtures.mjs +405 -0
- package/src/equities/funding.mjs +142 -0
- package/src/equities/index.mjs +166 -0
- package/src/equities/liveness.mjs +148 -0
- package/src/equities/marketHours.mjs +249 -0
- package/src/equities/normalize.mjs +295 -0
- package/src/equities/num.mjs +146 -0
- package/src/equities/prepare.mjs +95 -0
- package/src/equities/rank.mjs +260 -0
- package/src/equities/sourceNum.mjs +69 -0
- package/src/equities/types.mjs +220 -0
- package/src/equities/venues/arcus-perp.mjs +84 -0
- package/src/equities/venues/arcus-spot.mjs +105 -0
- package/src/equities/venues/hyperliquid-hip3.mjs +224 -0
- package/src/equities/venues/rh-uniswap.mjs +216 -0
- package/src/equities/venues/solana-xstocks.mjs +139 -0
- package/src/equities/venues/ton-stonfi.mjs +139 -0
- package/src/exact-integer.mjs +72 -0
- package/src/exec-policy.mjs +419 -0
- package/src/flags.mjs +15 -0
- package/src/fresh-window.mjs +76 -0
- package/src/gmx-attestation.mjs +176 -0
- package/src/index.mjs +107 -0
- package/src/licensing/locals-only.mjs +25 -0
- package/src/nft-gas-war-guard.mjs +139 -0
- package/src/onboarding/agent-keys.mjs +161 -0
- package/src/onboarding/harness-configs.mjs +102 -0
- package/src/onboarding/index.mjs +18 -0
- package/src/onboarding/tiers.mjs +139 -0
- package/src/oracle-env.mjs +55 -0
- package/src/package-manifest.mjs +42 -0
- package/src/plugins/agent-plugin-loader.mjs +225 -0
- package/src/portfolio-risk.mjs +170 -0
- package/src/prepare-envelope.mjs +204 -0
- package/src/profile-upgrade.mjs +277 -0
- package/src/protocol-execution.mjs +84 -0
- package/src/protocol-templates/gate.mjs +179 -0
- package/src/protocol-templates/prepare-deploy.mjs +81 -0
- package/src/public-api/agent-grants.mjs +57 -0
- package/src/public-api/buzz-integration.mjs +267 -0
- package/src/public-api/connect-agent.mjs +424 -0
- package/src/public-api/grants.mjs +142 -0
- package/src/public-api/http.mjs +707 -0
- package/src/public-api/metering.mjs +111 -0
- package/src/public-control/aa-adapter.mjs +402 -0
- package/src/public-control/build-registry.mjs +227 -0
- package/src/public-control/bundler-client.mjs +357 -0
- package/src/public-control/grant-indexer.mjs +296 -0
- package/src/public-control/policy-render.mjs +69 -0
- package/src/public-control/policy-schema.mjs +322 -0
- package/src/public-control/runtime-config.mjs +275 -0
- package/src/public-control/session-key-model.mjs +379 -0
- package/src/public-control/session-orchestrator.mjs +418 -0
- package/src/rfq/intent.mjs +180 -0
- package/src/rfq/sources.mjs +181 -0
- package/src/route-attestation.mjs +132 -0
- package/src/router/best-execution.mjs +326 -0
- package/src/router/index.mjs +250 -0
- package/src/router/integrator-fee.mjs +135 -0
- package/src/router/prepare-bridge.mjs +293 -0
- package/src/router/prepare-route.mjs +398 -0
- package/src/router/proposal.mjs +311 -0
- package/src/router/risk-classifier.mjs +119 -0
- package/src/router/route-sources.mjs +364 -0
- package/src/scanner/chains.config.mjs +543 -0
- package/src/scanner/contract.mjs +270 -0
- package/src/scanner/evm-scanner.mjs +394 -0
- package/src/scanner/index.mjs +9 -0
- package/src/scanner/v2-venue.mjs +351 -0
- package/src/scanner/v3-venue.mjs +308 -0
- package/src/scopes.mjs +44 -0
- package/src/sell-simulation.mjs +167 -0
- package/src/signals/engine.mjs +146 -0
- package/src/signals/index.mjs +1 -0
- package/src/token-transfer-guard.mjs +188 -0
- package/src/tui/app.mjs +438 -0
- package/src/tui/backend.mjs +215 -0
- package/src/tui/format.mjs +272 -0
- package/src/tui/gateway-client.mjs +177 -0
- package/src/tui/input.mjs +501 -0
- package/src/tui/memory.mjs +105 -0
- package/src/tui/meta-learn.mjs +85 -0
- package/src/tui/renderer.mjs +443 -0
- package/src/tui/scheduler.mjs +205 -0
- package/src/tui/skills-loader.mjs +140 -0
- package/src/tui/standalone-client.mjs +752 -0
- package/src/tui/theme.mjs +81 -0
- package/src/vault-attestation.mjs +146 -0
- package/src/venues.mjs +206 -0
- package/src/watch-preferences.mjs +85 -0
- package/dist/action-receipts.mjs +0 -1
- package/dist/action-semantics.mjs +0 -1
- package/dist/address-book.mjs +0 -2
- package/dist/bin/desk-server.mjs +0 -59
- package/dist/bin/oracle-data-mcp.mjs +0 -8
- package/dist/bin/oracle-equities.mjs +0 -20
- package/dist/bin/oracle-init.mjs +0 -24
- package/dist/bin/oracle-public-server.mjs +0 -5
- package/dist/bin/oracle-route.mjs +0 -22
- package/dist/bin/oracle-scan.mjs +0 -18
- package/dist/bin/oracle-upgrade.mjs +0 -21
- package/dist/bin/oracle.mjs +0 -76
- package/dist/chains.mjs +0 -1
- package/dist/cli/commands/auth.mjs +0 -16
- package/dist/cli/commands/bootstrap.mjs +0 -18
- package/dist/cli/commands/chain.mjs +0 -21
- package/dist/cli/commands/chat.mjs +0 -45
- package/dist/cli/commands/credential.mjs +0 -1
- package/dist/cli/commands/data-mcp.mjs +0 -2
- package/dist/cli/commands/data.mjs +0 -12
- package/dist/cli/commands/doctor.mjs +0 -5
- package/dist/cli/commands/equities.mjs +0 -15
- package/dist/cli/commands/farm.mjs +0 -59
- package/dist/cli/commands/follow.mjs +0 -28
- package/dist/cli/commands/gate.mjs +0 -13
- package/dist/cli/commands/harness.mjs +0 -15
- package/dist/cli/commands/help.mjs +0 -1
- package/dist/cli/commands/init.mjs +0 -3
- package/dist/cli/commands/mcp.mjs +0 -60
- package/dist/cli/commands/model.mjs +0 -54
- package/dist/cli/commands/plugins.mjs +0 -38
- package/dist/cli/commands/prepare.mjs +0 -2
- package/dist/cli/commands/public.mjs +0 -3
- package/dist/cli/commands/resolve.mjs +0 -23
- package/dist/cli/commands/route.mjs +0 -2
- package/dist/cli/commands/runner.mjs +0 -1
- package/dist/cli/commands/scan.mjs +0 -2
- package/dist/cli/commands/setup.mjs +0 -34
- package/dist/cli/commands/sign.mjs +0 -21
- package/dist/cli/commands/signer.mjs +0 -1
- package/dist/cli/commands/swap.mjs +0 -36
- package/dist/cli/commands/upgrade.mjs +0 -2
- package/dist/cli/commands/vault.mjs +0 -1
- package/dist/cli/commands/version.mjs +0 -2
- package/dist/data/desk-data.mjs +0 -55
- package/dist/data/names.mjs +0 -2
- package/dist/data/providers/farming.mjs +0 -2
- package/dist/equities/index.mjs +0 -1
- package/dist/index.mjs +0 -60
- package/dist/nft-gas-war-guard.mjs +0 -1
- package/dist/onboarding/harness-configs.mjs +0 -4
- package/dist/portfolio-risk.mjs +0 -1
- package/dist/prepare-envelope.mjs +0 -1
- package/dist/public-api/agent-grants.mjs +0 -3
- package/dist/public-control/policy-schema.mjs +0 -1
- package/dist/router/index.mjs +0 -2
- package/dist/scanner/index.mjs +0 -4
- package/dist/signals/index.mjs +0 -1
- package/dist/watch-preferences.mjs +0 -1
- /package/{dist/assets/profiles → profiles}/_template/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/_template/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/bitcoin-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/bitcoin-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/hyperliquid-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/hyperliquid-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/oracle/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/polymarket-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/polymarket-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/profile.schema.json +0 -0
- /package/{dist/assets/profiles → profiles}/protocol-builder/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/protocol-builder/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/robinhood-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/robinhood-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/solana-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/solana-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/stable-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/stable-agent/profile.json +0 -0
- /package/{dist/assets/skills → skills}/balance/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/bitcoin-l1.md +0 -0
- /package/{dist/assets/skills → skills}/chain-defi-ecosystem-profiling.md +0 -0
- /package/{dist/assets/skills → skills}/evm-contract-research.md +0 -0
- /package/{dist/assets/skills → skills}/hyperliquid.md +0 -0
- /package/{dist/assets/skills → skills}/multichain-exec-desk.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-action-semantics/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-best-execution/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-best-execution.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-bitcoin/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chain-graphs-telegram-cards/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chat/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chat/chain.SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chat/setup.SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-circuit-breaker/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-contract-research/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-desk/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-desk.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-dex-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-equities/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-grants/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-hypercore-staking/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-hyperliquid/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-meme-token-sniper/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-multichain-nft-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-multichain-token-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-nft-gacha-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-nft-mint-gas-war/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-polymarket/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-protocol-builder/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-protocol-security/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-public-product/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-receipts/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-receipts.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-rfq-tokenized-assets/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-smart-wallet-scanner/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-solana/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-solana-nft/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-token-research/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/solana.md +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const PACKAGE = "@oracle-agent/oracle";
|
|
2
|
+
const COMMAND = "npx";
|
|
3
|
+
const ARGS = ["-y", "--package", PACKAGE, "oracle-data-mcp"];
|
|
4
|
+
const FORBIDDEN_NAME = /(?:^|_)(?:PRIVATE_KEY|SECRET|TOKEN|PASSWORD|PASSPHRASE|MNEMONIC|SEED)(?:_|$)/i;
|
|
5
|
+
|
|
6
|
+
function requiredText(value, name) {
|
|
7
|
+
if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be a non-empty string`);
|
|
8
|
+
if (/\r|\n|\0/.test(value)) throw new TypeError(`${name} must be a single-line string`);
|
|
9
|
+
return value.trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function validateUrl(value) {
|
|
13
|
+
const text = requiredText(value, "url");
|
|
14
|
+
let parsed;
|
|
15
|
+
try { parsed = new URL(text); } catch { throw new TypeError("url must be a valid http(s) URL"); }
|
|
16
|
+
if (!/^https?:$/.test(parsed.protocol) || parsed.username || parsed.password) {
|
|
17
|
+
throw new TypeError("url must be a credential-free http(s) URL");
|
|
18
|
+
}
|
|
19
|
+
for (const name of parsed.searchParams.keys()) {
|
|
20
|
+
if (FORBIDDEN_NAME.test(name) || /^(?:key|api[_-]?key|auth)$/i.test(name)) {
|
|
21
|
+
throw new TypeError(`url must not contain secret-shaped query parameter: ${name}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return parsed.href;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateKey(value) {
|
|
28
|
+
const text = requiredText(value, "key");
|
|
29
|
+
if (/^[A-Z][A-Z0-9_]*\s*=/.test(text) || /-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(text)) {
|
|
30
|
+
throw new TypeError("key must be an Oracle agent key, not an environment assignment or private key");
|
|
31
|
+
}
|
|
32
|
+
if (/^(?:0x)?[0-9a-f]{64}$/i.test(text) || text.trim().split(/\s+/).length >= 12) {
|
|
33
|
+
throw new TypeError("key looks like private signing material");
|
|
34
|
+
}
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validateLabel(value) {
|
|
39
|
+
const label = requiredText(value, "label");
|
|
40
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,62}$/i.test(label)) {
|
|
41
|
+
throw new TypeError("label must contain only letters, digits, underscores, and hyphens");
|
|
42
|
+
}
|
|
43
|
+
if (FORBIDDEN_NAME.test(label)) throw new TypeError("label must not be a secret-shaped environment name");
|
|
44
|
+
return label;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function json(value) {
|
|
48
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function yamlString(value) {
|
|
52
|
+
return JSON.stringify(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function tomlString(value) {
|
|
56
|
+
return JSON.stringify(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return deterministic, copy-pasteable MCP configuration snippets.
|
|
61
|
+
* This function performs no I/O and never reads from process.env.
|
|
62
|
+
*/
|
|
63
|
+
export function emitHarnessConfigs({ url, key, label = "oracle-data" } = {}) {
|
|
64
|
+
const endpoint = validateUrl(url);
|
|
65
|
+
const agentKey = validateKey(key);
|
|
66
|
+
const name = validateLabel(label);
|
|
67
|
+
const server = {
|
|
68
|
+
command: COMMAND,
|
|
69
|
+
args: [...ARGS],
|
|
70
|
+
env: { ORACLE_DATA_URL: endpoint, ORACLE_AGENT_KEY: agentKey },
|
|
71
|
+
};
|
|
72
|
+
const mcp = { mcpServers: { [name]: server } };
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
hermes: [
|
|
76
|
+
"mcp_servers:",
|
|
77
|
+
` ${name}:`,
|
|
78
|
+
` command: ${yamlString(COMMAND)}`,
|
|
79
|
+
" args:",
|
|
80
|
+
...ARGS.map((arg) => ` - ${yamlString(arg)}`),
|
|
81
|
+
" env:",
|
|
82
|
+
` ORACLE_DATA_URL: ${yamlString(endpoint)}`,
|
|
83
|
+
` ORACLE_AGENT_KEY: ${yamlString(agentKey)}`,
|
|
84
|
+
"",
|
|
85
|
+
].join("\n"),
|
|
86
|
+
claudeCode: json(mcp),
|
|
87
|
+
codex: [
|
|
88
|
+
`[mcp_servers.${tomlString(name)}]`,
|
|
89
|
+
`command = ${tomlString(COMMAND)}`,
|
|
90
|
+
`args = [${ARGS.map(tomlString).join(", ")}]`,
|
|
91
|
+
"",
|
|
92
|
+
`[mcp_servers.${tomlString(name)}.env]`,
|
|
93
|
+
`ORACLE_DATA_URL = ${tomlString(endpoint)}`,
|
|
94
|
+
`ORACLE_AGENT_KEY = ${tomlString(agentKey)}`,
|
|
95
|
+
"",
|
|
96
|
+
].join("\n"),
|
|
97
|
+
cursor: json(mcp),
|
|
98
|
+
genericMcp: json(mcp),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const createHarnessConfigs = emitHarnessConfigs;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createAgentKey,
|
|
3
|
+
listAgentKeys,
|
|
4
|
+
revokeAgentKey,
|
|
5
|
+
authenticateAgentKey,
|
|
6
|
+
defaultStorePath,
|
|
7
|
+
__resetAgentKeysForTest,
|
|
8
|
+
} from "./agent-keys.mjs";
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
TIERS,
|
|
12
|
+
listTiers,
|
|
13
|
+
getTier,
|
|
14
|
+
onboardUser,
|
|
15
|
+
onboardAuthenticate,
|
|
16
|
+
onboardListKeys,
|
|
17
|
+
onboardRevoke,
|
|
18
|
+
} from "./tiers.mjs";
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Product onboarding tiers — multi-user, non-custodial.
|
|
2
|
+
// Tier chooses default scopes. Higher tiers never put user private keys on our box.
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_SCOPES, EXECUTE_SCOPE } from "../scopes.mjs";
|
|
5
|
+
import { createAgentKey, listAgentKeys, revokeAgentKey, authenticateAgentKey } from "./agent-keys.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tier catalog (product story):
|
|
9
|
+
* - observer: read-only market/position data
|
|
10
|
+
* - preparer: can prepare trades/mints (unsigned / advisory)
|
|
11
|
+
* - shadow: paper/shadow agent loops
|
|
12
|
+
* - executor: live agent:execute — ONLY if owner is on controller allowlist
|
|
13
|
+
* - local_signer: no API key needed for signing; user signs off-infra (docs only)
|
|
14
|
+
*/
|
|
15
|
+
export const TIERS = {
|
|
16
|
+
observer: {
|
|
17
|
+
id: "observer",
|
|
18
|
+
label: "Observer",
|
|
19
|
+
description: "Read markets, NFTs, positions. No prepare/execute.",
|
|
20
|
+
scopes: ["read:markets", "read:nfts", "read:positions"],
|
|
21
|
+
custody: "none",
|
|
22
|
+
canExecute: false,
|
|
23
|
+
},
|
|
24
|
+
preparer: {
|
|
25
|
+
id: "preparer",
|
|
26
|
+
label: "Preparer",
|
|
27
|
+
description: "Prepare trades/mints; user or operator co-signs. No live execute scope.",
|
|
28
|
+
scopes: [...DEFAULT_SCOPES].filter((s) => s !== "agent:shadow"),
|
|
29
|
+
custody: "none",
|
|
30
|
+
canExecute: false,
|
|
31
|
+
},
|
|
32
|
+
shadow: {
|
|
33
|
+
id: "shadow",
|
|
34
|
+
label: "Shadow agent",
|
|
35
|
+
description: "Full prepare + agent:shadow paper loops. Still no live execute.",
|
|
36
|
+
scopes: [...DEFAULT_SCOPES],
|
|
37
|
+
custody: "none",
|
|
38
|
+
canExecute: false,
|
|
39
|
+
},
|
|
40
|
+
executor: {
|
|
41
|
+
id: "executor",
|
|
42
|
+
label: "Executor (controller)",
|
|
43
|
+
description:
|
|
44
|
+
"Live agent:execute on the owner's own wallet only. Owner must be an executor controller. Key is NOT a wallet key.",
|
|
45
|
+
scopes: [...DEFAULT_SCOPES, EXECUTE_SCOPE],
|
|
46
|
+
custody: "none — scoped API key only; user wallet key never stored",
|
|
47
|
+
canExecute: true,
|
|
48
|
+
},
|
|
49
|
+
local_signer: {
|
|
50
|
+
id: "local_signer",
|
|
51
|
+
label: "Local signer",
|
|
52
|
+
description:
|
|
53
|
+
"User keeps keys on their device. Desk issues prepare-only key; signing happens off our infra.",
|
|
54
|
+
scopes: ["read:markets", "read:positions", "prepare:trade", "prepare:mint"],
|
|
55
|
+
custody: "none — signing off-box",
|
|
56
|
+
canExecute: false,
|
|
57
|
+
mintKey: true,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function listTiers() {
|
|
62
|
+
return Object.values(TIERS).map((t) => ({
|
|
63
|
+
id: t.id,
|
|
64
|
+
label: t.label,
|
|
65
|
+
description: t.description,
|
|
66
|
+
scopes: t.scopes,
|
|
67
|
+
custody: t.custody,
|
|
68
|
+
canExecute: t.canExecute,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getTier(id) {
|
|
73
|
+
const t = TIERS[id];
|
|
74
|
+
if (!t) throw new Error(`Unknown tier: ${id}`);
|
|
75
|
+
return t;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Onboard a user wallet onto a tier. Returns one-time raw API key (except pure docs).
|
|
80
|
+
* NEVER accepts or stores a private key.
|
|
81
|
+
*/
|
|
82
|
+
export function onboardUser({
|
|
83
|
+
owner,
|
|
84
|
+
tier = "observer",
|
|
85
|
+
name,
|
|
86
|
+
controllerAllowlist = [],
|
|
87
|
+
storePath,
|
|
88
|
+
scopes, // optional override
|
|
89
|
+
} = {}) {
|
|
90
|
+
if (owner && typeof owner === "object" && owner.privateKey) {
|
|
91
|
+
throw new Error("Refusing privateKey — non-custodial onboarding accepts owner address only");
|
|
92
|
+
}
|
|
93
|
+
const t = getTier(tier);
|
|
94
|
+
const finalScopes = scopes || t.scopes;
|
|
95
|
+
const result = createAgentKey({
|
|
96
|
+
owner,
|
|
97
|
+
name: name || `${t.label} key`,
|
|
98
|
+
scopes: finalScopes,
|
|
99
|
+
tier: t.id,
|
|
100
|
+
controllerAllowlist,
|
|
101
|
+
storePath,
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
tier: t.id,
|
|
105
|
+
custody: t.custody,
|
|
106
|
+
canExecute: t.canExecute && finalScopes.includes(EXECUTE_SCOPE),
|
|
107
|
+
key: result.key, // show once
|
|
108
|
+
record: result.record,
|
|
109
|
+
nextSteps:
|
|
110
|
+
t.id === "local_signer"
|
|
111
|
+
? [
|
|
112
|
+
"Store the API key for prepare/read calls only",
|
|
113
|
+
"Keep your wallet key on your device / hardware signer",
|
|
114
|
+
"Submit prepared payloads to your local signer — never upload the key here",
|
|
115
|
+
]
|
|
116
|
+
: t.canExecute
|
|
117
|
+
? [
|
|
118
|
+
"Store the API key securely",
|
|
119
|
+
"Live execute stays disabled until the separately installed operator enables it",
|
|
120
|
+
"This key can only act on YOUR owner wallet (custody firewall)",
|
|
121
|
+
]
|
|
122
|
+
: [
|
|
123
|
+
"Store the API key securely",
|
|
124
|
+
"Use with getSigner({ identity: auth.identity, venue }) after authenticateAgentKey",
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function onboardAuthenticate(rawKey, opts) {
|
|
130
|
+
return authenticateAgentKey(rawKey, opts);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function onboardListKeys(owner, opts) {
|
|
134
|
+
return listAgentKeys(owner, opts);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function onboardRevoke(opts) {
|
|
138
|
+
return revokeAgentKey(opts);
|
|
139
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const PUBLIC_DESKTOP = process.env.ORACLE_PUBLIC_DESKTOP === "1";
|
|
6
|
+
|
|
7
|
+
export const ORACLE_CONFIG_DIR = PUBLIC_DESKTOP
|
|
8
|
+
? (process.env.ORACLE_CONFIG_DIR || join(homedir(), ".config", "oracle"))
|
|
9
|
+
: join(homedir(), ".config", "oracle");
|
|
10
|
+
// Legacy config dir from before the rename. Kept so existing installs keep
|
|
11
|
+
// working; new installs only ever see ~/.config/oracle. The packaged public
|
|
12
|
+
// desktop never consults it — it owns its own config root, so an operator/desk
|
|
13
|
+
// install on the same machine cannot be inherited.
|
|
14
|
+
export const LEGACY_CONFIG_DIR = PUBLIC_DESKTOP
|
|
15
|
+
? ORACLE_CONFIG_DIR
|
|
16
|
+
: join(homedir(), ".config", "mad-desk");
|
|
17
|
+
|
|
18
|
+
export function env(primary, legacy, fallback = "") {
|
|
19
|
+
const pv = process.env[primary];
|
|
20
|
+
if (pv != null && String(pv).trim() !== "") return pv;
|
|
21
|
+
const lv = legacy ? process.env[legacy] : undefined;
|
|
22
|
+
if (lv != null && String(lv).trim() !== "") return lv;
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function envFlag(primary, legacy, defaultValue = false) {
|
|
27
|
+
const fallback = defaultValue ? "1" : "";
|
|
28
|
+
return String(env(primary, legacy, fallback)).trim() === "1";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DISABLE_WORDS = ["0", "false", "off", "no"];
|
|
32
|
+
|
|
33
|
+
// Reading a flag that may default ON. Only an explicit disable word turns it
|
|
34
|
+
// off, and an unset value keeps `defaultValue`.
|
|
35
|
+
//
|
|
36
|
+
// The old shape (`!== "0"`) inverted with a false default: ORACLE_ONBOARD_HTTP
|
|
37
|
+
// defaults off, so a user writing `false` to keep it off actually ENABLED it,
|
|
38
|
+
// because "false" is not "0". A disable word must never enable a surface.
|
|
39
|
+
export function envEnabled(primary, legacy, defaultValue = true) {
|
|
40
|
+
const raw = String(env(primary, legacy, "")).trim().toLowerCase();
|
|
41
|
+
if (raw === "") return defaultValue;
|
|
42
|
+
return !DISABLE_WORDS.includes(raw);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function csvEnv(primary, legacy) {
|
|
46
|
+
return String(env(primary, legacy, ""));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function configPath(file, primary, legacy) {
|
|
50
|
+
const explicit = env(primary, legacy, "");
|
|
51
|
+
if (explicit) return explicit;
|
|
52
|
+
const oraclePath = join(ORACLE_CONFIG_DIR, file);
|
|
53
|
+
if (existsSync(oraclePath)) return oraclePath;
|
|
54
|
+
return join(LEGACY_CONFIG_DIR, file);
|
|
55
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Resolve this package's own root without assuming how deep the caller sits.
|
|
2
|
+
//
|
|
3
|
+
// Bin entry points run from `bin/` in the source tree and from `dist/bin/` in
|
|
4
|
+
// the published bundle, so a hard-coded `path.join(__dirname, "..")` is correct
|
|
5
|
+
// in exactly one layout. That is what broke 0.12.0: `oracle-data` and
|
|
6
|
+
// `oracle init` both crashed at startup with ENOENT because they resolved
|
|
7
|
+
// dist/package.json and dist/profiles/ instead of the real package root.
|
|
8
|
+
//
|
|
9
|
+
// Runtime assets (profiles/, skills/) live at the root in the source tree and
|
|
10
|
+
// under dist/assets/ in the bundle, so asset lookup checks both.
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
export function packageRoot(fromUrl) {
|
|
16
|
+
let dir = dirname(fileURLToPath(fromUrl));
|
|
17
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
18
|
+
const manifest = join(dir, "package.json");
|
|
19
|
+
if (existsSync(manifest)) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
22
|
+
if (parsed && typeof parsed.version === "string") return dir;
|
|
23
|
+
} catch {
|
|
24
|
+
// an unreadable manifest is not the root we want; keep walking
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const parent = dirname(dir);
|
|
28
|
+
if (parent === dir) break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`oracle: could not resolve package root from ${fromUrl}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readPackageManifest(fromUrl) {
|
|
35
|
+
return JSON.parse(readFileSync(join(packageRoot(fromUrl), "package.json"), "utf8"));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function packageAssetDir(name, fromUrl) {
|
|
39
|
+
const root = packageRoot(fromUrl);
|
|
40
|
+
const bundled = join(root, "dist", "assets", name);
|
|
41
|
+
return existsSync(bundled) ? bundled : join(root, name);
|
|
42
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Agent Plugins loader — implements the open standard at https://agent-plugins.org/.
|
|
2
|
+
//
|
|
3
|
+
// Oracle can load any conformant Agent Plugin from a directory, validate its
|
|
4
|
+
// manifest against the canonical schema, and discover skills and MCP servers.
|
|
5
|
+
// This makes Oracle an Agent Plugins-compatible client.
|
|
6
|
+
//
|
|
7
|
+
// Spec: https://agent-plugins.org/schemas/1.0.0/plugin.schema.json
|
|
8
|
+
//
|
|
9
|
+
// Custody: read-only. This module never signs, never broadcasts, never
|
|
10
|
+
// touches key material. Skills and MCP servers are inert until invoked.
|
|
11
|
+
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
export const SCHEMA_ID = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
|
|
16
|
+
|
|
17
|
+
const NAME_RE = /^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\.[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate a plugin.json manifest against the canonical schema.
|
|
21
|
+
* Returns { valid, errors[], warnings[] }.
|
|
22
|
+
* Non-fatal violations are warnings; fatal violations are errors.
|
|
23
|
+
*/
|
|
24
|
+
export function validateManifest(raw) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
|
|
28
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
29
|
+
errors.push("plugin.json must be a JSON object");
|
|
30
|
+
return { valid: false, errors, warnings };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// $schema — required, must match canonical
|
|
34
|
+
if (raw.$schema !== SCHEMA_ID) {
|
|
35
|
+
if (!raw.$schema) {
|
|
36
|
+
errors.push("$schema is required — must be " + SCHEMA_ID);
|
|
37
|
+
} else {
|
|
38
|
+
errors.push(`unsupported $schema: ${raw.$schema} (expected ${SCHEMA_ID})`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// name — required, kebab-case dot-separated
|
|
43
|
+
if (!raw.name || typeof raw.name !== "string") {
|
|
44
|
+
errors.push("name is required (string)");
|
|
45
|
+
} else if (!NAME_RE.test(raw.name)) {
|
|
46
|
+
errors.push(`name "${raw.name}" must be kebab-case dot-separated (e.g. deployment.tools)`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// version — optional, warn if missing
|
|
50
|
+
if (raw.version === undefined || raw.version === null || raw.version === "") {
|
|
51
|
+
warnings.push("version is missing — semantic versioning recommended");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// description — optional
|
|
55
|
+
if (raw.description !== undefined && typeof raw.description !== "string") {
|
|
56
|
+
warnings.push("description must be a string");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// author — optional object
|
|
60
|
+
if (raw.author !== undefined) {
|
|
61
|
+
if (typeof raw.author !== "object" || Array.isArray(raw.author)) {
|
|
62
|
+
warnings.push("author must be an object with optional name, email, url");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// keywords — optional array
|
|
67
|
+
if (raw.keywords !== undefined) {
|
|
68
|
+
if (!Array.isArray(raw.keywords)) {
|
|
69
|
+
warnings.push("keywords must be an array");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// license — optional string
|
|
74
|
+
if (raw.license !== undefined && typeof raw.license !== "string") {
|
|
75
|
+
warnings.push("license must be a string");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Extension fields (unknown top-level) — warn, don't error
|
|
79
|
+
const known = new Set(["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions"]);
|
|
80
|
+
for (const key of Object.keys(raw)) {
|
|
81
|
+
if (!known.has(key)) {
|
|
82
|
+
warnings.push(`unknown field "${key}" — will be ignored (use extensions.<namespace> for client data)`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Discover skills in a plugin's skills/ directory.
|
|
91
|
+
* Each .md file is a skill. Subdirectories are supported for organization.
|
|
92
|
+
* Returns [{ name, path, content? }]
|
|
93
|
+
*/
|
|
94
|
+
export function discoverSkills(pluginRoot) {
|
|
95
|
+
const skillsDir = path.join(pluginRoot, "skills");
|
|
96
|
+
const skills = [];
|
|
97
|
+
|
|
98
|
+
if (!fs.existsSync(skillsDir) || !fs.statSync(skillsDir).isDirectory()) {
|
|
99
|
+
return skills;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function walk(dir, prefix = "") {
|
|
103
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
104
|
+
const full = path.join(dir, entry.name);
|
|
105
|
+
if (entry.isDirectory()) {
|
|
106
|
+
walk(full, prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
107
|
+
} else if (entry.name.endsWith(".md")) {
|
|
108
|
+
const skillName = prefix ? `${prefix}/${entry.name.replace(/\.md$/, "")}` : entry.name.replace(/\.md$/, "");
|
|
109
|
+
skills.push({
|
|
110
|
+
name: skillName,
|
|
111
|
+
path: full,
|
|
112
|
+
relativePath: path.relative(skillsDir, full),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
walk(skillsDir);
|
|
119
|
+
return skills;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Discover MCP server configs in a plugin's mcp/ directory.
|
|
124
|
+
* Each .json file is an MCP server definition.
|
|
125
|
+
* Returns [{ name, path, config }]
|
|
126
|
+
*/
|
|
127
|
+
export function discoverMcpServers(pluginRoot) {
|
|
128
|
+
const mcpDir = path.join(pluginRoot, "mcp");
|
|
129
|
+
const servers = [];
|
|
130
|
+
|
|
131
|
+
if (!fs.existsSync(mcpDir) || !fs.statSync(mcpDir).isDirectory()) {
|
|
132
|
+
return servers;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const entry of fs.readdirSync(mcpDir, { withFileTypes: true })) {
|
|
136
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
137
|
+
const full = path.join(mcpDir, entry.name);
|
|
138
|
+
try {
|
|
139
|
+
const config = JSON.parse(fs.readFileSync(full, "utf8"));
|
|
140
|
+
servers.push({
|
|
141
|
+
name: entry.name.replace(/\.json$/, ""),
|
|
142
|
+
path: full,
|
|
143
|
+
config,
|
|
144
|
+
});
|
|
145
|
+
} catch (e) {
|
|
146
|
+
// Invalid JSON in an MCP config — skip this server, don't fail the plugin
|
|
147
|
+
servers.push({
|
|
148
|
+
name: entry.name.replace(/\.json$/, ""),
|
|
149
|
+
path: full,
|
|
150
|
+
error: `invalid JSON: ${e.message}`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return servers;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Load a complete Agent Plugin from a directory.
|
|
160
|
+
* Validates the manifest, discovers skills and MCP servers.
|
|
161
|
+
*
|
|
162
|
+
* Returns:
|
|
163
|
+
* { ok: true, plugin: { name, version, root, manifest, skills[], mcpServers[], warnings[] } }
|
|
164
|
+
* { ok: false, error, errors[] }
|
|
165
|
+
*/
|
|
166
|
+
export function loadPlugin(pluginRoot) {
|
|
167
|
+
const root = path.resolve(pluginRoot);
|
|
168
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
169
|
+
return { ok: false, error: `plugin directory not found: ${root}` };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const manifestPath = path.join(root, "plugin.json");
|
|
173
|
+
if (!fs.existsSync(manifestPath)) {
|
|
174
|
+
return { ok: false, error: `missing plugin.json in ${root}` };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let manifest;
|
|
178
|
+
try {
|
|
179
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
180
|
+
} catch (e) {
|
|
181
|
+
return { ok: false, error: `invalid plugin.json: ${e.message}` };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const validation = validateManifest(manifest);
|
|
185
|
+
if (!validation.valid) {
|
|
186
|
+
return { ok: false, error: "manifest validation failed", errors: validation.errors };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const skills = discoverSkills(root);
|
|
190
|
+
const mcpServers = discoverMcpServers(root);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
ok: true,
|
|
194
|
+
plugin: {
|
|
195
|
+
name: manifest.name,
|
|
196
|
+
version: manifest.version || "0.0.0",
|
|
197
|
+
description: manifest.description || "",
|
|
198
|
+
author: manifest.author || {},
|
|
199
|
+
license: manifest.license || "UNLICENSED",
|
|
200
|
+
root,
|
|
201
|
+
manifest,
|
|
202
|
+
skills,
|
|
203
|
+
mcpServers,
|
|
204
|
+
warnings: [...validation.warnings],
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Scan a directory for installed plugins (directories containing plugin.json).
|
|
211
|
+
* Returns an array of loaded plugin descriptors.
|
|
212
|
+
*/
|
|
213
|
+
export function scanInstalledPlugins(pluginsDir) {
|
|
214
|
+
const plugins = [];
|
|
215
|
+
if (!fs.existsSync(pluginsDir)) return plugins;
|
|
216
|
+
|
|
217
|
+
for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
|
|
218
|
+
if (!entry.isDirectory()) continue;
|
|
219
|
+
const pluginRoot = path.join(pluginsDir, entry.name);
|
|
220
|
+
const result = loadPlugin(pluginRoot);
|
|
221
|
+
if (result.ok) plugins.push(result.plugin);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return plugins;
|
|
225
|
+
}
|