From c8ff32a32fc8a78ebee7e407ce3655d4dde32af1 Mon Sep 17 00:00:00 2001 From: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:49:33 -0400 Subject: [PATCH 1/4] feat: add skill growth endpoints to registry broker client Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> --- .../registry-broker-skills-client.test.ts | 671 ++++++++++++++++++ .../registry-broker/client/base-client.ts | 102 ++- src/services/registry-broker/client/skills.ts | 307 +++++++- src/services/registry-broker/schemas.ts | 410 ++++++++--- src/services/registry-broker/types.ts | 165 +++-- 5 files changed, 1467 insertions(+), 188 deletions(-) create mode 100644 __tests__/services/registry-broker-skills-client.test.ts diff --git a/__tests__/services/registry-broker-skills-client.test.ts b/__tests__/services/registry-broker-skills-client.test.ts new file mode 100644 index 0000000..e1540e6 --- /dev/null +++ b/__tests__/services/registry-broker-skills-client.test.ts @@ -0,0 +1,671 @@ +import { jest } from '@jest/globals'; +import { RegistryBrokerClient } from '../../src/services/registry-broker'; + +function createResponse(payload: { + status?: number; + json?: () => Promise; +}): Response { + return { + ok: (payload.status ?? 200) >= 200 && (payload.status ?? 200) < 300, + status: payload.status ?? 200, + statusText: 'OK', + headers: new Headers({ 'content-type': 'application/json' }), + json: payload.json ?? (async () => ({})), + text: async () => JSON.stringify(await (payload.json?.() ?? {})), + } as unknown as Response; +} + +describe('RegistryBrokerClient skill contract methods', () => { + const fetchImplementation = jest.fn(); + + beforeEach(() => { + fetchImplementation.mockReset(); + }); + + it('parses publisher metadata from skills config', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + enabled: true, + network: 'testnet', + publisher: { + cliPackageUrl: 'https://www.npmjs.com/package/skill-publish', + cliCommand: 'npx skill-publish', + actionMarketplaceUrl: + 'https://github.com/marketplace/actions/skill-publish', + repositoryUrl: 'https://github.com/hashgraph-online/skill-publish', + guideUrl: + 'https://hol.org/registry/docs#guides/skill-publishing-workflow.md', + docsUrl: 'https://hol.org/registry/docs', + submitUrl: 'https://hol.org/registry/skills/submit', + skillsIndexUrl: 'https://hol.org/registry/skills', + quickstartCommands: [ + { + id: 'setup', + label: 'Authenticate and store your API key', + description: + 'Create a broker API key with ledger auth and persist it locally for future publishes.', + command: 'npx skill-publish setup', + href: 'https://hol.org/registry/skills/publish', + }, + ], + templatePresets: [ + { + presetId: 'general', + label: 'General skill', + description: + 'Balanced starter for most reusable skill releases with no strong ecosystem assumptions.', + recommendedFor: + 'First-time publishers and broad reusable skills', + command: + 'npx skill-publish scaffold-repo ./my-skill --preset general --name my-skill', + }, + ], + }, + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const config = await client.skillsConfig(); + + expect(config.publisher?.cliCommand).toBe('npx skill-publish'); + expect(config.publisher?.submitUrl).toBe( + 'https://hol.org/registry/skills/submit', + ); + expect(config.publisher?.quickstartCommands[0]?.command).toBe( + 'npx skill-publish setup', + ); + expect(config.publisher?.templatePresets[0]?.presetId).toBe('general'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/config', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('retrieves skill trust-tier status', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + name: 'preview-skill', + version: '0.1.0', + published: false, + verifiedDomain: false, + trustTier: 'validated', + badgeMetric: 'tier', + checks: { + repoCommitIntegrity: false, + manifestIntegrity: false, + domainProof: false, + }, + verificationSignals: { + publisherBound: false, + domainProof: false, + verifiedDomain: false, + previewValidated: true, + }, + provenanceSignals: { + repoCommitIntegrity: false, + manifestIntegrity: false, + canonicalRelease: false, + previewAvailable: true, + previewAuthoritative: false, + }, + nextSteps: [ + { + kind: 'publish_first_release', + priority: 100, + id: 'publish', + label: 'Publish the first immutable release', + description: + 'This repo already passes validate-first checks. Publish an immutable release to mint canonical install URLs and a durable registry page.', + url: 'https://hol.org/registry/skills/submit', + href: 'https://hol.org/registry/skills/submit', + command: 'npx skill-publish publish', + }, + ], + publisher: { + cliPackageUrl: 'https://www.npmjs.com/package/skill-publish', + cliCommand: 'npx skill-publish', + actionMarketplaceUrl: + 'https://github.com/marketplace/actions/skill-publish', + repositoryUrl: 'https://github.com/hashgraph-online/skill-publish', + guideUrl: 'https://hol.org/registry/skills/about', + docsUrl: 'https://hol.org/registry/docs', + submitUrl: 'https://hol.org/registry/skills/submit', + skillsIndexUrl: 'https://hol.org/registry/skills', + quickstartCommands: [], + templatePresets: [], + }, + preview: { + previewId: 'preview_demo', + repoUrl: + 'https://github.com/hashgraph-online/registry-broker-skill', + repoOwner: 'hashgraph-online', + repoName: 'registry-broker-skill', + commitSha: 'abc123', + ref: 'refs/pull/5/merge', + eventName: 'pull_request', + skillDir: '.', + generatedAt: '2026-04-04T10:00:00.000Z', + expiresAt: '2026-04-11T10:00:00.000Z', + statusUrl: 'https://hol.org/registry/skills/preview-skill', + }, + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const status = await client.getSkillStatus({ + name: 'preview-skill', + version: '0.1.0', + }); + + expect(status.trustTier).toBe('validated'); + expect(status.preview?.repoName).toBe('registry-broker-skill'); + expect(status.preview?.previewId).toBe('preview_demo'); + expect(status.checks.domainProof).toBe(false); + expect(status.badgeMetric).toBe('tier'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/status?name=preview-skill&version=0.1.0', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('uploads a GitHub OIDC skill preview report', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + id: 'preview-1', + previewId: 'preview_demo', + source: 'github-oidc', + generatedAt: '2026-04-04T10:00:00.000Z', + expiresAt: '2026-04-11T10:00:00.000Z', + statusUrl: 'https://hol.org/registry/skills/preview-skill', + authoritative: false, + report: { + schema_version: 'skill-preview.v1', + tool_version: '1.0.0', + preview_id: 'preview_demo', + repo_url: + 'https://github.com/hashgraph-online/registry-broker-skill', + repo_owner: 'hashgraph-online', + repo_name: 'registry-broker-skill', + default_branch: 'main', + commit_sha: 'abc123', + ref: 'refs/pull/5/merge', + event_name: 'pull_request', + workflow_run_url: + 'https://github.com/hashgraph-online/registry-broker-skill/actions/runs/123456789', + skill_dir: '.', + name: 'preview-skill', + version: '0.1.0', + validation_status: 'passed', + findings: [], + package_summary: { + fileCount: 2, + }, + suggested_next_steps: [ + { + id: 'publish', + label: 'Publish', + description: 'Publish the validated skill.', + href: 'https://hol.org/registry/skills/submit', + command: 'npx skill-publish publish', + }, + ], + generated_at: '2026-04-04T10:00:00.000Z', + }, + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const preview = await client.uploadSkillPreviewFromGithubOidc({ + token: 'preview-token', + report: { + schema_version: 'skill-preview.v1', + tool_version: '1.0.0', + preview_id: 'preview_demo', + repo_url: 'https://github.com/hashgraph-online/registry-broker-skill', + repo_owner: 'hashgraph-online', + repo_name: 'registry-broker-skill', + default_branch: 'main', + commit_sha: 'abc123', + ref: 'refs/pull/5/merge', + event_name: 'pull_request', + workflow_run_url: + 'https://github.com/hashgraph-online/registry-broker-skill/actions/runs/123456789', + skill_dir: '.', + name: 'preview-skill', + version: '0.1.0', + validation_status: 'passed', + findings: [], + package_summary: { + fileCount: 2, + }, + suggested_next_steps: [], + generated_at: '2026-04-04T10:00:00.000Z', + }, + }); + + expect(preview.id).toBe('preview-1'); + expect(preview.previewId).toBe('preview_demo'); + expect(preview.report.name).toBe('preview-skill'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/preview/github-oidc', + expect.objectContaining({ + method: 'POST', + headers: expect.any(Headers), + }), + ); + const request = fetchImplementation.mock.calls.at(-1); + const headers = request?.[1]?.headers as Headers; + expect(headers.get('authorization')).toBe('Bearer preview-token'); + }); + + it('retrieves a stored skill preview by name and version', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + found: true, + authoritative: false, + statusUrl: 'https://hol.org/registry/skills/preview/preview_demo', + expiresAt: '2026-04-11T10:00:00.000Z', + preview: { + id: 'preview-1', + previewId: 'preview_demo', + source: 'github-oidc', + generatedAt: '2026-04-04T10:00:00.000Z', + expiresAt: '2026-04-11T10:00:00.000Z', + statusUrl: 'https://hol.org/registry/skills/preview/preview_demo', + authoritative: false, + report: { + schema_version: 'skill-preview.v1', + tool_version: '1.0.0', + preview_id: 'preview_demo', + repo_url: + 'https://github.com/hashgraph-online/registry-broker-skill', + repo_owner: 'hashgraph-online', + repo_name: 'registry-broker-skill', + default_branch: 'main', + commit_sha: 'abc123', + ref: 'refs/pull/5/merge', + event_name: 'pull_request', + workflow_run_url: + 'https://github.com/hashgraph-online/registry-broker-skill/actions/runs/123456789', + skill_dir: '.', + name: 'preview-skill', + version: '0.1.0', + validation_status: 'passed', + findings: [], + package_summary: { + fileCount: 2, + }, + suggested_next_steps: [], + generated_at: '2026-04-04T10:00:00.000Z', + }, + }, + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const preview = await client.getSkillPreview({ + name: 'preview-skill', + version: '0.1.0', + }); + + expect(preview.found).toBe(true); + expect(preview.preview?.report.repo_owner).toBe('hashgraph-online'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/preview?name=preview-skill&version=0.1.0', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('retrieves repo-based skill status and preview metadata', async () => { + fetchImplementation + .mockResolvedValueOnce( + createResponse({ + json: async () => ({ + name: 'preview-skill', + version: '0.1.0', + published: false, + verifiedDomain: false, + trustTier: 'validated', + badgeMetric: 'tier', + checks: { + repoCommitIntegrity: false, + manifestIntegrity: false, + domainProof: false, + }, + verificationSignals: { + publisherBound: false, + domainProof: false, + verifiedDomain: false, + previewValidated: true, + }, + provenanceSignals: { + repoCommitIntegrity: false, + manifestIntegrity: false, + canonicalRelease: false, + previewAvailable: true, + previewAuthoritative: false, + }, + nextSteps: [ + { + kind: 'publish_first_release', + priority: 100, + id: 'publish', + label: 'Publish the first immutable release', + description: 'Publish it.', + url: 'https://hol.org/registry/skills/submit', + href: 'https://hol.org/registry/skills/submit', + command: 'npx skill-publish publish', + }, + ], + publisher: null, + preview: { + previewId: 'preview_demo', + repoUrl: + 'https://github.com/hashgraph-online/registry-broker-skill', + repoOwner: 'hashgraph-online', + repoName: 'registry-broker-skill', + commitSha: 'abc123', + ref: 'refs/pull/5/merge', + eventName: 'pull_request', + skillDir: '.', + generatedAt: '2026-04-04T10:00:00.000Z', + expiresAt: '2026-04-11T10:00:00.000Z', + statusUrl: 'https://hol.org/registry/skills/preview/preview_demo', + }, + statusUrl: 'https://hol.org/registry/skills/preview/preview_demo', + }), + }), + ) + .mockResolvedValueOnce( + createResponse({ + json: async () => ({ + found: false, + authoritative: false, + preview: null, + statusUrl: null, + expiresAt: null, + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const status = await client.getSkillStatusByRepo({ + repo: 'https://github.com/hashgraph-online/registry-broker-skill', + skillDir: '.', + ref: 'refs/pull/5/merge', + }); + expect(status.preview?.previewId).toBe('preview_demo'); + + const preview = await client.getSkillPreviewByRepo({ + repo: 'https://github.com/hashgraph-online/registry-broker-skill', + skillDir: '.', + }); + expect(preview.found).toBe(false); + expect(fetchImplementation).toHaveBeenNthCalledWith( + 1, + 'https://api.example.com/api/v1/skills/status/by-repo?repo=https%3A%2F%2Fgithub.com%2Fhashgraph-online%2Fregistry-broker-skill&skillDir=.&ref=refs%2Fpull%2F5%2Fmerge', + expect.objectContaining({ method: 'GET' }), + ); + expect(fetchImplementation).toHaveBeenNthCalledWith( + 2, + 'https://api.example.com/api/v1/skills/preview/by-repo?repo=https%3A%2F%2Fgithub.com%2Fhashgraph-online%2Fregistry-broker-skill&skillDir=.', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('retrieves anonymous quote preview estimates', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + estimatedCredits: { min: 64, max: 76 }, + estimatedHbar: { min: 0.64, max: 0.76 }, + pricingVersion: 'heuristic-v1', + assumptions: [ + 'Estimate derived from package file count and total bytes.', + ], + purchaseUrl: 'https://hol.org/registry/skills/submit', + publishUrl: 'https://hol.org/registry/skills/submit', + verificationUrl: 'https://hol.org/registry/skills/submit', + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const quote = await client.quoteSkillPublishPreview({ + fileCount: 4, + totalBytes: 18_500, + name: 'preview-skill', + version: '0.1.0', + repoUrl: 'https://github.com/hashgraph-online/registry-broker-skill', + skillDir: '.', + }); + + expect(quote.estimatedCredits.min).toBe(64); + expect(quote.pricingVersion).toBe('heuristic-v1'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/quote-preview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + fileCount: 4, + totalBytes: 18_500, + name: 'preview-skill', + version: '0.1.0', + repoUrl: 'https://github.com/hashgraph-online/registry-broker-skill', + skillDir: '.', + }), + }), + ); + }); + + it('retrieves repo conversion signals for growth-loop routing', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + repoUrl: 'https://github.com/hashgraph-online/registry-broker-skill', + skillDir: '.', + trustTier: 'validated', + actionInstalled: true, + previewUploaded: true, + previewId: 'preview_demo', + lastValidateSuccessAt: '2026-04-04T10:00:00.000Z', + stalePreviewAgeDays: 1, + published: false, + verified: false, + publishReady: true, + publishBlockedByMissingAuth: true, + statusUrl: 'https://hol.org/registry/skills/preview/preview_demo', + purchaseUrl: 'https://hol.org/registry/skills/submit', + publishUrl: 'https://github.com/marketplace/actions/skill-publish', + verificationUrl: 'https://hol.org/registry/skills/submit', + nextSteps: [ + { + kind: 'publish_first_release', + priority: 100, + id: 'publish', + label: 'Publish the first immutable release', + description: 'Publish it.', + url: 'https://hol.org/registry/skills/submit', + href: 'https://hol.org/registry/skills/submit', + command: 'npx skill-publish publish', + }, + ], + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const signals = await client.getSkillConversionSignalsByRepo({ + repo: 'https://github.com/hashgraph-online/registry-broker-skill', + skillDir: '.', + ref: 'refs/heads/main', + }); + + expect(signals.publishReady).toBe(true); + expect(signals.previewId).toBe('preview_demo'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/conversion-signals/by-repo?repo=https%3A%2F%2Fgithub.com%2Fhashgraph-online%2Fregistry-broker-skill&skillDir=.&ref=refs%2Fheads%2Fmain', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('retrieves skill install metadata for a pinned release', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + name: 'registry-broker', + version: '1.2.3', + skillRef: 'registry-broker@1.2.3', + network: 'testnet', + detailUrl: + 'https://hol.org/registry/skills/registry-broker?version=1.2.3', + artifacts: { + skillMd: { + url: 'https://api.example.com/api/v1/skills/registry-broker%401.2.3/SKILL.md', + pointer: 'hcs://1/0.0.7000001', + sha256: 'skill-md-sha', + }, + manifest: { + url: 'https://api.example.com/api/v1/skills/registry-broker%401.2.3/manifest', + pointer: 'hcs://1/0.0.6000101', + sha256: 'manifest-sha', + }, + }, + resolvers: { + pinned: { + skillRef: 'registry-broker@1.2.3', + skillMdUrl: + 'https://api.example.com/api/v1/skills/registry-broker%401.2.3/SKILL.md', + manifestUrl: + 'https://api.example.com/api/v1/skills/registry-broker%401.2.3/manifest', + }, + latest: { + skillRef: 'registry-broker@latest', + skillMdUrl: + 'https://api.example.com/api/v1/skills/registry-broker%40latest/SKILL.md', + manifestUrl: + 'https://api.example.com/api/v1/skills/registry-broker%40latest/manifest', + }, + }, + share: { + canonicalUrl: + 'https://hol.org/registry/skills/registry-broker?version=1.2.3', + latestUrl: 'https://hol.org/registry/skills/registry-broker', + markdownLink: + '[registry-broker on HOL Registry](https://hol.org/registry/skills/registry-broker?version=1.2.3)', + htmlLink: + 'registry-broker on HOL Registry', + badge: { + apiUrl: + 'https://api.example.com/api/v1/skills/badge?name=registry-broker&metric=version&style=for-the-badge&label=registry-broker', + imageUrl: + 'https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.example.com%2Fapi%2Fv1%2Fskills%2Fbadge%3Fname%3Dregistry-broker%26metric%3Dversion%26style%3Dfor-the-badge%26label%3Dregistry-broker', + markdown: + '[![registry-broker on HOL Registry (Version + Verification)](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.example.com%2Fapi%2Fv1%2Fskills%2Fbadge%3Fname%3Dregistry-broker%26metric%3Dversion%26style%3Dfor-the-badge%26label%3Dregistry-broker)](https://hol.org/registry/skills/registry-broker?version=1.2.3)', + html: 'registry-broker on HOL Registry (Version + Verification)', + }, + }, + snippets: { + cli: 'npx @hol-org/registry skills get --name "registry-broker" --version "1.2.3"', + claude: + 'Skill URL (Claude):\nhttps://api.example.com/api/v1/skills/registry-broker%401.2.3/SKILL.md', + cursor: + 'Skill URL (Cursor):\nhttps://api.example.com/api/v1/skills/registry-broker%401.2.3/SKILL.md', + codex: + 'Skill URL (Codex):\nhttps://api.example.com/api/v1/skills/registry-broker%401.2.3/SKILL.md', + openclaw: + 'skill_url: https://api.example.com/api/v1/skills/registry-broker%401.2.3/SKILL.md\nmanifest_url: https://api.example.com/api/v1/skills/registry-broker%401.2.3/manifest', + }, + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const install = await client.getSkillInstall('registry-broker@1.2.3'); + + expect(install.skillRef).toBe('registry-broker@1.2.3'); + expect(install.artifacts.manifest.pointer).toBe('hcs://1/0.0.6000101'); + expect(install.resolvers.latest.skillRef).toBe('registry-broker@latest'); + expect(install.share.badge?.markdown).toContain( + 'registry-broker on HOL Registry', + ); + expect(install.snippets.openclaw).toContain('/manifest'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/registry-broker%401.2.3/install', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('records install copy telemetry for a pinned release', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ accepted: true }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const response = await client.recordSkillInstallCopy( + 'registry-broker@1.2.3', + { + source: 'detail_install_card', + installType: 'cli', + }, + ); + + expect(response.accepted).toBe(true); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/skills/registry-broker%401.2.3/telemetry/install-copy', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + source: 'detail_install_card', + installType: 'cli', + }), + }), + ); + }); +}); diff --git a/src/services/registry-broker/client/base-client.ts b/src/services/registry-broker/client/base-client.ts index 6df02f6..199feaf 100644 --- a/src/services/registry-broker/client/base-client.ts +++ b/src/services/registry-broker/client/base-client.ts @@ -50,8 +50,6 @@ import type { CreateSessionResponse, CreditPurchaseResponse, DashboardStatsResponse, - DelegationPlanRequest, - DelegationPlanResponse, DetectProtocolResponse, EncryptionHandshakeRecord, EncryptionHandshakeSubmissionPayload, @@ -109,8 +107,6 @@ import type { VerificationVerifySenderResponse, X402MinimumsResponse, SkillRegistryConfigResponse, - SkillSecurityBreakdownRequest, - SkillSecurityBreakdownResponse, SkillBadgeQuery, SkillBadgeResponse, SkillCatalogQueryOptions, @@ -129,9 +125,22 @@ import type { SkillRegistryPublishResponse, SkillRegistryQuoteRequest, SkillRegistryQuoteResponse, + SkillQuotePreviewRequest, + SkillQuotePreviewResponse, SkillRegistryTagsResponse, SkillRegistryVoteRequest, SkillRegistryVoteStatusResponse, + SkillStatusRequest, + SkillStatusResponse, + SkillPreviewByRepoRequest, + SkillPreviewLookupRequest, + SkillPreviewLookupResponse, + SkillPreviewRecord, + SkillConversionSignalsResponse, + UploadSkillPreviewFromGithubOidcRequest, + SkillInstallResponse, + SkillInstallCopyTelemetryRequest, + SkillInstallCopyTelemetryResponse, SkillRecommendedVersionResponse, SkillRecommendedVersionSetRequest, SkillResolverManifestResponse, @@ -234,7 +243,6 @@ import { verifyLedgerChallenge as verifyLedgerChallengeImpl, } from './ledger-auth'; import { - delegate as delegateImpl, detectProtocol as detectProtocolImpl, facets as facetsImpl, getAdditionalRegistries as getAdditionalRegistriesImpl, @@ -254,11 +262,17 @@ import { getRecommendedSkillVersion as getRecommendedSkillVersionImpl, getSkillBadge as getSkillBadgeImpl, getSkillDeprecations as getSkillDeprecationsImpl, + getSkillInstall as getSkillInstallImpl, getSkillOwnership as getSkillOwnershipImpl, getSkillPublishJob as getSkillPublishJobImpl, + getSkillConversionSignalsByRepo as getSkillConversionSignalsByRepoImpl, + getSkillPreviewById as getSkillPreviewByIdImpl, + getSkillPreviewByRepo as getSkillPreviewByRepoImpl, + getSkillPreview as getSkillPreviewImpl, + getSkillStatusByRepo as getSkillStatusByRepoImpl, + getSkillStatus as getSkillStatusImpl, getSkillVerificationStatus as getSkillVerificationStatusImpl, getSkillVoteStatus as getSkillVoteStatusImpl, - getSkillSecurityBreakdown as getSkillSecurityBreakdownImpl, getSkillsCatalog as getSkillsCatalogImpl, getMySkillsList as getMySkillsListImpl, listSkillCategories as listSkillCategoriesImpl, @@ -267,14 +281,17 @@ import { listMySkills as listMySkillsImpl, listSkillVersions as listSkillVersionsImpl, publishSkill as publishSkillImpl, + quoteSkillPublishPreview as quoteSkillPublishPreviewImpl, quoteSkillPublish as quoteSkillPublishImpl, resolveSkillManifest as resolveSkillManifestImpl, resolveSkillMarkdown as resolveSkillMarkdownImpl, requestSkillVerification as requestSkillVerificationImpl, + recordSkillInstallCopy as recordSkillInstallCopyImpl, setRecommendedSkillVersion as setRecommendedSkillVersionImpl, setSkillDeprecation as setSkillDeprecationImpl, setSkillVote as setSkillVoteImpl, skillsConfig as skillsConfigImpl, + uploadSkillPreviewFromGithubOidc as uploadSkillPreviewFromGithubOidcImpl, verifySkillDomainProof as verifySkillDomainProofImpl, } from './skills'; import { @@ -660,12 +677,6 @@ export class RegistryBrokerClient { return searchImpl(this, params); } - async delegate( - request: DelegationPlanRequest, - ): Promise { - return delegateImpl(this, request); - } - async searchErc8004ByAgentId(params: { chainId: number; agentId: number | bigint | string; @@ -774,12 +785,6 @@ export class RegistryBrokerClient { return listSkillsImpl(this, options); } - async getSkillSecurityBreakdown( - params: SkillSecurityBreakdownRequest, - ): Promise { - return getSkillSecurityBreakdownImpl(this, params); - } - async getSkillsCatalog( options?: SkillCatalogQueryOptions, ): Promise { @@ -812,6 +817,12 @@ export class RegistryBrokerClient { return quoteSkillPublishImpl(this, payload); } + async quoteSkillPublishPreview( + payload: SkillQuotePreviewRequest, + ): Promise { + return quoteSkillPublishPreviewImpl(this, payload); + } + async publishSkill( payload: SkillRegistryPublishRequest, ): Promise { @@ -860,6 +871,59 @@ export class RegistryBrokerClient { return getSkillBadgeImpl(this, params); } + async getSkillStatus( + params: SkillStatusRequest, + ): Promise { + return getSkillStatusImpl(this, params); + } + + async getSkillStatusByRepo( + params: SkillPreviewByRepoRequest, + ): Promise { + return getSkillStatusByRepoImpl(this, params); + } + + async getSkillConversionSignalsByRepo( + params: SkillPreviewByRepoRequest, + ): Promise { + return getSkillConversionSignalsByRepoImpl(this, params); + } + + async uploadSkillPreviewFromGithubOidc( + payload: UploadSkillPreviewFromGithubOidcRequest, + ): Promise { + return uploadSkillPreviewFromGithubOidcImpl(this, payload); + } + + async getSkillPreview( + params: SkillPreviewLookupRequest, + ): Promise { + return getSkillPreviewImpl(this, params); + } + + async getSkillPreviewByRepo( + params: SkillPreviewByRepoRequest, + ): Promise { + return getSkillPreviewByRepoImpl(this, params); + } + + async getSkillPreviewById( + previewId: string, + ): Promise { + return getSkillPreviewByIdImpl(this, previewId); + } + + async getSkillInstall(skillRef: string): Promise { + return getSkillInstallImpl(this, skillRef); + } + + async recordSkillInstallCopy( + skillRef: string, + payload?: SkillInstallCopyTelemetryRequest, + ): Promise { + return recordSkillInstallCopyImpl(this, skillRef, payload); + } + async listSkillTags(): Promise { return listSkillTagsImpl(this); } @@ -1398,7 +1462,7 @@ export class RegistryBrokerClient { parseWithSchema( value: JsonValue, - schema: z.ZodType, + schema: z.ZodSchema, context: string, ): T { try { diff --git a/src/services/registry-broker/client/skills.ts b/src/services/registry-broker/client/skills.ts index 732f2b1..c03aa77 100644 --- a/src/services/registry-broker/client/skills.ts +++ b/src/services/registry-broker/client/skills.ts @@ -12,8 +12,6 @@ import type { SkillRecommendedVersionSetRequest, SkillRegistryConfigResponse, SkillRegistryCategoriesResponse, - SkillSecurityBreakdownRequest, - SkillSecurityBreakdownResponse, SkillRegistryJobStatusResponse, SkillRegistryListResponse, SkillRegistryMineResponse, @@ -23,9 +21,22 @@ import type { SkillRegistryPublishResponse, SkillRegistryQuoteRequest, SkillRegistryQuoteResponse, + SkillQuotePreviewRequest, + SkillQuotePreviewResponse, SkillRegistryTagsResponse, SkillRegistryVoteRequest, SkillRegistryVoteStatusResponse, + SkillStatusRequest, + SkillStatusResponse, + SkillPreviewLookupRequest, + SkillPreviewByRepoRequest, + SkillPreviewLookupResponse, + SkillPreviewRecord, + SkillConversionSignalsResponse, + UploadSkillPreviewFromGithubOidcRequest, + SkillInstallResponse, + SkillInstallCopyTelemetryRequest, + SkillInstallCopyTelemetryResponse, SkillResolverManifestResponse, SkillRegistryVersionsResponse, SkillVerificationDomainProofChallengeRequest, @@ -46,14 +57,20 @@ import { skillRegistryCategoriesResponseSchema, skillRegistryJobStatusResponseSchema, skillRegistryListResponseSchema, - skillSecurityBreakdownResponseSchema, skillRegistryMineResponseSchema, skillRegistryMyListResponseSchema, skillRegistryOwnershipResponseSchema, skillRegistryPublishResponseSchema, skillRegistryQuoteResponseSchema, + skillQuotePreviewResponseSchema, + skillConversionSignalsResponseSchema, skillRegistryTagsResponseSchema, skillRegistryVoteStatusResponseSchema, + skillStatusResponseSchema, + skillPreviewLookupResponseSchema, + skillPreviewRecordSchema, + skillInstallResponseSchema, + skillInstallCopyTelemetryResponseSchema, skillResolverManifestResponseSchema, skillVerificationDomainProofChallengeResponseSchema, skillVerificationDomainProofVerifyResponseSchema, @@ -131,27 +148,6 @@ export async function listSkills( ); } -export async function getSkillSecurityBreakdown( - client: RegistryBrokerClient, - params: SkillSecurityBreakdownRequest, -): Promise { - const normalizedJobId = params.jobId.trim(); - if (!normalizedJobId) { - throw new Error('jobId is required'); - } - - const raw = await client.requestJson( - `/skills/${encodeURIComponent(normalizedJobId)}/security-breakdown`, - { method: 'GET' }, - ); - - return client.parseWithSchema( - raw, - skillSecurityBreakdownResponseSchema, - 'skill security breakdown response', - ); -} - export async function getSkillsCatalog( client: RegistryBrokerClient, params: SkillCatalogQueryOptions = {}, @@ -288,6 +284,23 @@ export async function quoteSkillPublish( ); } +export async function quoteSkillPublishPreview( + client: RegistryBrokerClient, + payload: SkillQuotePreviewRequest, +): Promise { + const raw = await client.requestJson('/skills/quote-preview', { + method: 'POST', + body: payload, + headers: { 'content-type': 'application/json' }, + }); + + return client.parseWithSchema( + raw, + skillQuotePreviewResponseSchema, + 'skill quote preview response', + ); +} + export async function publishSkill( client: RegistryBrokerClient, payload: SkillRegistryPublishRequest, @@ -492,6 +505,252 @@ export async function getSkillBadge( ); } +export async function getSkillStatus( + client: RegistryBrokerClient, + params: SkillStatusRequest, +): Promise { + const normalizedName = params.name.trim(); + if (!normalizedName) { + throw new Error('name is required'); + } + + const query = new URLSearchParams(); + query.set('name', normalizedName); + if (params.version?.trim()) { + query.set('version', params.version.trim()); + } + + const raw = await client.requestJson( + `/skills/status?${query.toString()}`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillStatusResponseSchema, + 'skill status response', + ); +} + +export async function getSkillStatusByRepo( + client: RegistryBrokerClient, + params: SkillPreviewByRepoRequest, +): Promise { + const repo = params.repo.trim(); + const skillDir = params.skillDir.trim(); + if (!repo) { + throw new Error('repo is required'); + } + if (!skillDir) { + throw new Error('skillDir is required'); + } + + const query = new URLSearchParams(); + query.set('repo', repo); + query.set('skillDir', skillDir); + if (params.ref?.trim()) { + query.set('ref', params.ref.trim()); + } + + const raw = await client.requestJson( + `/skills/status/by-repo?${query.toString()}`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillStatusResponseSchema, + 'skill status response', + ); +} + +export async function getSkillConversionSignalsByRepo( + client: RegistryBrokerClient, + params: SkillPreviewByRepoRequest, +): Promise { + const repo = params.repo.trim(); + const skillDir = params.skillDir.trim(); + if (!repo) { + throw new Error('repo is required'); + } + if (!skillDir) { + throw new Error('skillDir is required'); + } + + const query = new URLSearchParams(); + query.set('repo', repo); + query.set('skillDir', skillDir); + if (params.ref?.trim()) { + query.set('ref', params.ref.trim()); + } + + const raw = await client.requestJson( + `/skills/conversion-signals/by-repo?${query.toString()}`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillConversionSignalsResponseSchema, + 'skill conversion signals response', + ); +} + +export async function uploadSkillPreviewFromGithubOidc( + client: RegistryBrokerClient, + payload: UploadSkillPreviewFromGithubOidcRequest, +): Promise { + const token = payload.token.trim(); + if (!token) { + throw new Error('token is required'); + } + + const raw = await client.requestJson( + '/skills/preview/github-oidc', + { + method: 'POST', + body: payload.report, + headers: { + 'content-type': 'application/json', + authorization: `Bearer ${token}`, + }, + }, + ); + + return client.parseWithSchema( + raw, + skillPreviewRecordSchema, + 'skill preview record response', + ); +} + +export async function getSkillPreview( + client: RegistryBrokerClient, + params: SkillPreviewLookupRequest, +): Promise { + const normalizedName = params.name.trim(); + if (!normalizedName) { + throw new Error('name is required'); + } + + const query = new URLSearchParams(); + query.set('name', normalizedName); + if (params.version?.trim()) { + query.set('version', params.version.trim()); + } + + const raw = await client.requestJson( + `/skills/preview?${query.toString()}`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillPreviewLookupResponseSchema, + 'skill preview response', + ); +} + +export async function getSkillPreviewByRepo( + client: RegistryBrokerClient, + params: SkillPreviewByRepoRequest, +): Promise { + const repo = params.repo.trim(); + const skillDir = params.skillDir.trim(); + if (!repo) { + throw new Error('repo is required'); + } + if (!skillDir) { + throw new Error('skillDir is required'); + } + + const query = new URLSearchParams(); + query.set('repo', repo); + query.set('skillDir', skillDir); + if (params.ref?.trim()) { + query.set('ref', params.ref.trim()); + } + + const raw = await client.requestJson( + `/skills/preview/by-repo?${query.toString()}`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillPreviewLookupResponseSchema, + 'skill preview by repo response', + ); +} + +export async function getSkillPreviewById( + client: RegistryBrokerClient, + previewId: string, +): Promise { + const normalizedPreviewId = previewId.trim(); + if (!normalizedPreviewId) { + throw new Error('previewId is required'); + } + + const raw = await client.requestJson( + `/skills/preview/${encodeURIComponent(normalizedPreviewId)}`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillPreviewLookupResponseSchema, + 'skill preview by id response', + ); +} + +export async function getSkillInstall( + client: RegistryBrokerClient, + skillRef: string, +): Promise { + const normalizedSkillRef = skillRef.trim(); + if (!normalizedSkillRef) { + throw new Error('skillRef is required'); + } + + const raw = await client.requestJson( + `/skills/${encodeURIComponent(normalizedSkillRef)}/install`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillInstallResponseSchema, + 'skill install response', + ); +} + +export async function recordSkillInstallCopy( + client: RegistryBrokerClient, + skillRef: string, + payload: SkillInstallCopyTelemetryRequest = {}, +): Promise { + const normalizedSkillRef = skillRef.trim(); + if (!normalizedSkillRef) { + throw new Error('skillRef is required'); + } + + const raw = await client.requestJson( + `/skills/${encodeURIComponent(normalizedSkillRef)}/telemetry/install-copy`, + { + method: 'POST', + body: payload, + headers: { 'content-type': 'application/json' }, + }, + ); + + return client.parseWithSchema( + raw, + skillInstallCopyTelemetryResponseSchema, + 'skill install copy telemetry response', + ); +} + export async function listSkillTags( client: RegistryBrokerClient, ): Promise { diff --git a/src/services/registry-broker/schemas.ts b/src/services/registry-broker/schemas.ts index 5df77cb..a557163 100644 --- a/src/services/registry-broker/schemas.ts +++ b/src/services/registry-broker/schemas.ts @@ -156,16 +156,6 @@ const metadataFacetSchema = z ) .optional(); -const searchHitMetadataSchema = z - .object({ - delegationRoles: jsonValueSchema.optional(), - delegationTaskTags: jsonValueSchema.optional(), - delegationProtocols: jsonValueSchema.optional(), - delegationSummary: jsonValueSchema.optional(), - delegationSignals: jsonValueSchema.optional(), - }) - .passthrough(); - const searchHitSchema = z .object({ id: z.string(), @@ -177,7 +167,7 @@ const searchHitSchema = z endpoints: z .union([z.record(jsonValueSchema), z.array(z.string())]) .optional(), - metadata: searchHitMetadataSchema.optional(), + metadata: z.record(jsonValueSchema).optional(), metadataFacet: metadataFacetSchema, profile: agentProfileSchema.optional(), protocols: z.array(z.string()).optional(), @@ -228,74 +218,6 @@ export const resolveResponseSchema = z.object({ agent: searchHitSchema, }); -const delegationPlanCandidateSchema = z - .object({ - uaid: z.string(), - label: z.string(), - registry: z.string().optional(), - agent: searchHitSchema, - score: z.number(), - matchedQueries: z.array(z.string()).optional(), - matchedRoles: z.array(z.string()).optional(), - matchedProtocols: z.array(z.string()).optional(), - matchedSurfaces: z.array(z.string()).optional(), - matchedLanguages: z.array(z.string()).optional(), - matchedArtifacts: z.array(z.string()).optional(), - matchedTaskTags: z.array(z.string()).optional(), - reasons: z.array(z.string()).optional(), - suggestedMessage: z.string().optional(), - trustScore: z.number().optional(), - verified: z.boolean().optional(), - communicationSupported: z.boolean().optional(), - availability: z.boolean().optional(), - explanation: z.string().optional(), - }) - .passthrough(); - -const delegationOpportunitySchema = z - .object({ - id: z.string(), - title: z.string(), - reason: z.string(), - role: z.string(), - type: z.enum(['ai-agents', 'mcp-servers']), - suggestedMode: z.enum(['best-match', 'fallback', 'parallel']), - searchQueries: z.array(z.string()), - protocols: z.array(z.string()).optional(), - surfaces: z.array(z.string()).optional(), - languages: z.array(z.string()).optional(), - artifacts: z.array(z.string()).optional(), - candidates: z.array(delegationPlanCandidateSchema), - }) - .passthrough(); - -const delegationRecommendationSchema = z - .object({ - action: z.enum(['delegate-now', 'review-shortlist', 'handle-locally']), - confidence: z.number(), - reason: z.string(), - opportunityId: z.string().optional(), - candidate: delegationPlanCandidateSchema.optional(), - }) - .passthrough(); - -export const delegationPlanResponseSchema = z - .object({ - task: z.string(), - context: z.string().optional(), - summary: z.string().optional(), - intents: z.array(z.string()), - protocols: z.array(z.string()), - surfaces: z.array(z.string()), - languages: z.array(z.string()).optional(), - artifacts: z.array(z.string()).optional(), - shouldDelegate: z.boolean(), - localFirstReason: z.string().optional(), - recommendation: delegationRecommendationSchema.optional(), - opportunities: z.array(delegationOpportunitySchema), - }) - .passthrough(); - const agentFeedbackSummarySchema = z.object({ averageScore: z.number(), totalFeedbacks: z.number(), @@ -1297,7 +1219,7 @@ const trustScoreBreakdownSchema = z const skillSafetyLabelSchema = z.enum(['safe', 'review', 'caution', 'unsafe']); -export const skillSafetySummarySchema = z +const skillSafetySummarySchema = z .object({ score: z.number(), label: skillSafetyLabelSchema, @@ -1310,7 +1232,7 @@ export const skillSafetySummarySchema = z const skillSafetyFindingSeveritySchema = z.enum(['low', 'medium', 'high']); -export const skillSafetyFindingSchema = z +const skillSafetyFindingSchema = z .object({ ruleId: z.string(), severity: skillSafetyFindingSeveritySchema, @@ -1366,17 +1288,6 @@ export const skillRegistryListResponseSchema = z }) .passthrough(); -export const skillSecurityBreakdownResponseSchema = z - .object({ - name: z.string(), - version: z.string(), - jobId: z.string(), - createdAt: z.string(), - safety: skillSafetySummarySchema.nullable(), - findings: z.array(skillSafetyFindingSchema), - }) - .passthrough(); - export const skillCatalogChannelSchema = z.enum([ 'stable', 'prerelease', @@ -1620,6 +1531,47 @@ export const skillRegistryConfigResponseSchema = z .union([z.literal('mainnet'), z.literal('testnet')]) .nullable() .optional(), + publisher: z + .object({ + cliPackageUrl: z.string(), + cliCommand: z.string(), + actionMarketplaceUrl: z.string(), + repositoryUrl: z.string(), + guideUrl: z.string().nullable().optional(), + docsUrl: z.string().nullable().optional(), + submitUrl: z.string().nullable().optional(), + skillsIndexUrl: z.string().nullable().optional(), + quickstartCommands: z + .array( + z + .object({ + id: z.string(), + label: z.string(), + description: z.string(), + command: z.string(), + href: z.string().nullable().optional(), + }) + .passthrough(), + ) + .optional() + .default([]), + templatePresets: z + .array( + z + .object({ + presetId: z.string(), + label: z.string(), + description: z.string(), + recommendedFor: z.string(), + command: z.string(), + }) + .passthrough(), + ) + .optional() + .default([]), + }) + .nullable() + .optional(), }) .passthrough(); @@ -1639,6 +1591,280 @@ export const skillRegistryVoteStatusResponseSchema = z }) .passthrough(); +export const skillTrustTierSchema = z.enum([ + 'unclaimed', + 'validated', + 'published', + 'verified', + 'hardened', +]); + +export const skillStatusNextStepSchema = z + .object({ + kind: z.enum([ + 'setup_validate', + 'publish_first_release', + 'verify_domain', + 'harden_workflow', + 'share_status', + ]), + priority: z.number().int(), + id: z.string(), + label: z.string(), + description: z.string(), + url: z.string().nullable().optional(), + href: z.string().nullable().optional(), + command: z.string().nullable().optional(), + }) + .passthrough(); + +export const skillPreviewSuggestedNextStepSchema = z + .object({ + id: z.string(), + label: z.string(), + description: z.string(), + command: z.string().optional(), + href: z.string().optional(), + }) + .passthrough(); + +export const skillPreviewReportSchema = z + .object({ + schema_version: z.literal('skill-preview.v1'), + tool_version: z.string(), + preview_id: z.string(), + repo_url: z.string(), + repo_owner: z.string(), + repo_name: z.string(), + default_branch: z.string(), + commit_sha: z.string(), + ref: z.string(), + event_name: z.string(), + workflow_run_url: z.string(), + skill_dir: z.string(), + name: z.string(), + version: z.string(), + validation_status: z.literal('passed'), + findings: z.array(z.unknown()), + package_summary: z.record(z.string(), z.unknown()), + suggested_next_steps: z.array(skillPreviewSuggestedNextStepSchema), + generated_at: z.string(), + }) + .passthrough(); + +export const skillPreviewRecordSchema = z + .object({ + id: z.string(), + previewId: z.string(), + source: z.literal('github-oidc'), + report: skillPreviewReportSchema, + generatedAt: z.string(), + expiresAt: z.string(), + statusUrl: z.string(), + authoritative: z.literal(false), + }) + .passthrough(); + +export const skillPreviewLookupResponseSchema = z + .object({ + found: z.boolean(), + authoritative: z.literal(false), + preview: skillPreviewRecordSchema.nullable(), + statusUrl: z.string().nullable(), + expiresAt: z.string().nullable(), + }) + .passthrough(); + +export const skillStatusPreviewMetadataSchema = z + .object({ + previewId: z.string(), + repoUrl: z.string(), + repoOwner: z.string(), + repoName: z.string(), + commitSha: z.string(), + ref: z.string(), + eventName: z.string(), + skillDir: z.string(), + generatedAt: z.string(), + expiresAt: z.string(), + statusUrl: z.string(), + }) + .passthrough(); + +export const skillStatusChecksSchema = z + .object({ + repoCommitIntegrity: z.boolean(), + manifestIntegrity: z.boolean(), + domainProof: z.boolean(), + }) + .passthrough(); + +export const skillStatusVerificationSignalsSchema = z + .object({ + publisherBound: z.boolean(), + domainProof: z.boolean(), + verifiedDomain: z.boolean(), + previewValidated: z.boolean(), + }) + .passthrough(); + +export const skillStatusProvenanceSignalsSchema = z + .object({ + repoCommitIntegrity: z.boolean(), + manifestIntegrity: z.boolean(), + canonicalRelease: z.boolean(), + previewAvailable: z.boolean(), + previewAuthoritative: z.boolean(), + }) + .passthrough(); + +export const skillStatusResponseSchema = z + .object({ + name: z.string(), + version: z.string().nullable(), + published: z.boolean(), + verifiedDomain: z.boolean(), + trustTier: skillTrustTierSchema, + badgeMetric: z.enum([ + 'version', + 'version_verification', + 'status', + 'verification', + 'repo_commit', + 'manifest', + 'domain', + 'trust', + 'tier', + 'upvotes', + 'updated', + 'safety', + ]), + checks: skillStatusChecksSchema, + nextSteps: z.array(skillStatusNextStepSchema), + verificationSignals: skillStatusVerificationSignalsSchema, + provenanceSignals: skillStatusProvenanceSignalsSchema, + publisher: skillRegistryConfigResponseSchema.shape.publisher, + preview: skillStatusPreviewMetadataSchema.nullable().optional(), + statusUrl: z.string().nullable().optional(), + }) + .passthrough(); + +export const skillQuotePreviewRangeSchema = z + .object({ + min: z.number(), + max: z.number(), + }) + .passthrough(); + +export const skillQuotePreviewResponseSchema = z + .object({ + estimatedCredits: skillQuotePreviewRangeSchema, + estimatedHbar: skillQuotePreviewRangeSchema, + pricingVersion: z.string(), + assumptions: z.array(z.string()), + purchaseUrl: z.string().nullable(), + publishUrl: z.string().nullable(), + verificationUrl: z.string().nullable(), + }) + .passthrough(); + +export const skillConversionSignalsResponseSchema = z + .object({ + repoUrl: z.string(), + skillDir: z.string(), + trustTier: skillTrustTierSchema, + actionInstalled: z.boolean(), + previewUploaded: z.boolean(), + previewId: z.string().nullable(), + lastValidateSuccessAt: z.string().nullable(), + stalePreviewAgeDays: z.number().nullable(), + published: z.boolean(), + verified: z.boolean(), + publishReady: z.boolean(), + publishBlockedByMissingAuth: z.boolean(), + statusUrl: z.string().nullable(), + purchaseUrl: z.string().nullable(), + publishUrl: z.string().nullable(), + verificationUrl: z.string().nullable(), + nextSteps: z.array(skillStatusNextStepSchema), + }) + .passthrough(); + +export const skillInstallArtifactDescriptorSchema = z + .object({ + url: z.string(), + pointer: z.string().nullable(), + sha256: z.string().nullable(), + }) + .passthrough(); + +export const skillInstallResolverDescriptorSchema = z + .object({ + skillRef: z.string(), + skillMdUrl: z.string(), + manifestUrl: z.string(), + }) + .passthrough(); + +export const skillInstallBadgeDescriptorSchema = z + .object({ + apiUrl: z.string(), + imageUrl: z.string(), + markdown: z.string(), + html: z.string(), + }) + .passthrough(); + +export const skillInstallShareDescriptorSchema = z + .object({ + canonicalUrl: z.string().nullable(), + latestUrl: z.string().nullable(), + markdownLink: z.string().nullable(), + htmlLink: z.string().nullable(), + badge: skillInstallBadgeDescriptorSchema.nullable(), + }) + .passthrough(); + +export const skillInstallSnippetSetSchema = z + .object({ + cli: z.string(), + claude: z.string(), + cursor: z.string(), + codex: z.string(), + openclaw: z.string(), + }) + .passthrough(); + +export const skillInstallResponseSchema = z + .object({ + name: z.string(), + version: z.string(), + skillRef: z.string(), + network: z.union([z.literal('mainnet'), z.literal('testnet')]), + detailUrl: z.string().nullable(), + artifacts: z + .object({ + skillMd: skillInstallArtifactDescriptorSchema, + manifest: skillInstallArtifactDescriptorSchema, + }) + .passthrough(), + resolvers: z + .object({ + pinned: skillInstallResolverDescriptorSchema, + latest: skillInstallResolverDescriptorSchema, + }) + .passthrough(), + share: skillInstallShareDescriptorSchema, + snippets: skillInstallSnippetSetSchema, + }) + .passthrough(); + +export const skillInstallCopyTelemetryResponseSchema = z + .object({ + accepted: z.boolean(), + }) + .passthrough(); + const skillVerificationTierSchema = z.enum(['basic', 'express']); const skillVerificationStatusSchema = z.enum([ 'pending', diff --git a/src/services/registry-broker/types.ts b/src/services/registry-broker/types.ts index 61341f2..70d3aab 100644 --- a/src/services/registry-broker/types.ts +++ b/src/services/registry-broker/types.ts @@ -12,7 +12,6 @@ import { encryptionHandshakeResponseSchema, detectProtocolResponseSchema, dashboardStatsResponseSchema, - delegationPlanResponseSchema, metricsSummaryResponseSchema, popularResponseSchema, protocolsResponseSchema, @@ -91,7 +90,6 @@ import { skillRegistryFileDescriptorSchema, skillRegistryJobStatusResponseSchema, skillRegistryListResponseSchema, - skillSecurityBreakdownResponseSchema, skillRegistryMineResponseSchema, skillRegistryMyListResponseSchema, skillRegistryOwnershipResponseSchema, @@ -104,6 +102,27 @@ import { skillVerificationDomainProofVerifyResponseSchema, skillVerificationRequestCreateResponseSchema, skillVerificationStatusResponseSchema, + skillTrustTierSchema, + skillStatusNextStepSchema, + skillPreviewSuggestedNextStepSchema, + skillPreviewReportSchema, + skillPreviewRecordSchema, + skillPreviewLookupResponseSchema, + skillStatusPreviewMetadataSchema, + skillStatusChecksSchema, + skillStatusVerificationSignalsSchema, + skillStatusProvenanceSignalsSchema, + skillStatusResponseSchema, + skillQuotePreviewRangeSchema, + skillQuotePreviewResponseSchema, + skillConversionSignalsResponseSchema, + skillInstallArtifactDescriptorSchema, + skillInstallResolverDescriptorSchema, + skillInstallBadgeDescriptorSchema, + skillInstallShareDescriptorSchema, + skillInstallSnippetSetSchema, + skillInstallResponseSchema, + skillInstallCopyTelemetryResponseSchema, } from './schemas'; export type JsonPrimitive = string | number | boolean | null; @@ -272,50 +291,6 @@ export interface SearchParams { metadata?: Record>; } -export interface DelegationWorkspaceContext { - openFiles?: string[]; - modifiedFiles?: string[]; - relatedPaths?: string[]; - errors?: string[]; - commands?: string[]; - languages?: string[]; -} - -export interface DelegationPlanFilter { - capabilities?: string[]; - type?: 'ai-agents' | 'mcp-servers'; - registry?: string; - registries?: string[]; - protocols?: string[]; - adapters?: string[]; - verifiedOnly?: boolean; - onlineOnly?: boolean; - minTrust?: number; - minTrustSignals?: Record; - metadata?: Record>; - metadataRanges?: Record; -} - -export interface DelegationPlanRequest { - task: string; - context?: string; - workspace?: DelegationWorkspaceContext; - limit?: number; - filter?: DelegationPlanFilter; -} - -export type DelegationPlanCandidate = z.infer< - typeof delegationPlanResponseSchema ->['opportunities'][number]['candidates'][number]; - -export type DelegationOpportunity = z.infer< - typeof delegationPlanResponseSchema ->['opportunities'][number]; - -export type DelegationPlanResponse = z.infer< - typeof delegationPlanResponseSchema ->; - export type RegistryStatsResponse = z.infer; export type RegistriesResponse = z.infer; @@ -384,9 +359,6 @@ export type SkillRegistryPublishSummary = z.infer< export type SkillRegistryListResponse = z.infer< typeof skillRegistryListResponseSchema >; -export type SkillSecurityBreakdownResponse = z.infer< - typeof skillSecurityBreakdownResponseSchema ->; export type SkillRegistryMineResponse = z.infer< typeof skillRegistryMineResponseSchema >; @@ -442,6 +414,55 @@ export type SkillRegistryVersionsResponse = z.infer< export type SkillRegistryVoteStatusResponse = z.infer< typeof skillRegistryVoteStatusResponseSchema >; +export type SkillTrustTier = z.infer; +export type SkillStatusNextStep = z.infer; +export type SkillPreviewSuggestedNextStep = z.infer< + typeof skillPreviewSuggestedNextStepSchema +>; +export type SkillPreviewReport = z.infer; +export type SkillPreviewRecord = z.infer; +export type SkillPreviewLookupResponse = z.infer< + typeof skillPreviewLookupResponseSchema +>; +export type SkillStatusPreviewMetadata = z.infer< + typeof skillStatusPreviewMetadataSchema +>; +export type SkillStatusChecks = z.infer; +export type SkillStatusVerificationSignals = z.infer< + typeof skillStatusVerificationSignalsSchema +>; +export type SkillStatusProvenanceSignals = z.infer< + typeof skillStatusProvenanceSignalsSchema +>; +export type SkillStatusResponse = z.infer; +export type SkillQuotePreviewRange = z.infer< + typeof skillQuotePreviewRangeSchema +>; +export type SkillQuotePreviewResponse = z.infer< + typeof skillQuotePreviewResponseSchema +>; +export type SkillConversionSignalsResponse = z.infer< + typeof skillConversionSignalsResponseSchema +>; +export type SkillInstallArtifactDescriptor = z.infer< + typeof skillInstallArtifactDescriptorSchema +>; +export type SkillInstallResolverDescriptor = z.infer< + typeof skillInstallResolverDescriptorSchema +>; +export type SkillInstallBadgeDescriptor = z.infer< + typeof skillInstallBadgeDescriptorSchema +>; +export type SkillInstallShareDescriptor = z.infer< + typeof skillInstallShareDescriptorSchema +>; +export type SkillInstallSnippetSet = z.infer< + typeof skillInstallSnippetSetSchema +>; +export type SkillInstallResponse = z.infer; +export type SkillInstallCopyTelemetryResponse = z.infer< + typeof skillInstallCopyTelemetryResponseSchema +>; export type SkillVerificationRequestCreateResponse = z.infer< typeof skillVerificationRequestCreateResponseSchema @@ -493,10 +514,6 @@ export interface SkillListOptions { view?: 'latest' | 'all'; } -export interface SkillSecurityBreakdownRequest { - jobId: string; -} - export interface SkillCatalogQueryOptions { q?: string; category?: string; @@ -522,6 +539,48 @@ export interface SkillRegistryVoteRequest { upvoted: boolean; } +export interface SkillStatusRequest { + name: string; + version?: string; +} + +export interface SkillPreviewLookupRequest { + name: string; + version?: string; +} + +export interface SkillPreviewByRepoRequest { + repo: string; + skillDir: string; + ref?: string; +} + +export interface SkillQuotePreviewRequest { + fileCount: number; + totalBytes: number; + name?: string; + version?: string; + repoUrl?: string; + skillDir?: string; +} + +export interface UploadSkillPreviewFromGithubOidcRequest { + token: string; + report: SkillPreviewReport; +} + +export interface SkillInstallCopyTelemetryRequest { + source?: string; + installType?: + | 'cli' + | 'skillmd' + | 'manifest' + | 'claude' + | 'cursor' + | 'codex' + | 'openclaw'; +} + export interface SkillVerificationRequestCreateRequest { name: string; version?: string; From 97195111340e18a6b473773f2b4e316bb5125d3b Mon Sep 17 00:00:00 2001 From: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:12:41 -0400 Subject: [PATCH 2/4] fix: restore registry broker client compat Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> --- .../registry-broker/client/base-client.ts | 18 +++++++ src/services/registry-broker/client/skills.ts | 24 +++++++++ src/services/registry-broker/types.ts | 53 +++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/src/services/registry-broker/client/base-client.ts b/src/services/registry-broker/client/base-client.ts index 199feaf..4dc916e 100644 --- a/src/services/registry-broker/client/base-client.ts +++ b/src/services/registry-broker/client/base-client.ts @@ -50,6 +50,8 @@ import type { CreateSessionResponse, CreditPurchaseResponse, DashboardStatsResponse, + DelegationPlanRequest, + DelegationPlanResponse, DetectProtocolResponse, EncryptionHandshakeRecord, EncryptionHandshakeSubmissionPayload, @@ -141,6 +143,8 @@ import type { SkillInstallResponse, SkillInstallCopyTelemetryRequest, SkillInstallCopyTelemetryResponse, + SkillSecurityBreakdownRequest, + SkillSecurityBreakdownResponse, SkillRecommendedVersionResponse, SkillRecommendedVersionSetRequest, SkillResolverManifestResponse, @@ -243,6 +247,7 @@ import { verifyLedgerChallenge as verifyLedgerChallengeImpl, } from './ledger-auth'; import { + delegate as delegateImpl, detectProtocol as detectProtocolImpl, facets as facetsImpl, getAdditionalRegistries as getAdditionalRegistriesImpl, @@ -273,6 +278,7 @@ import { getSkillStatus as getSkillStatusImpl, getSkillVerificationStatus as getSkillVerificationStatusImpl, getSkillVoteStatus as getSkillVoteStatusImpl, + getSkillSecurityBreakdown as getSkillSecurityBreakdownImpl, getSkillsCatalog as getSkillsCatalogImpl, getMySkillsList as getMySkillsListImpl, listSkillCategories as listSkillCategoriesImpl, @@ -677,6 +683,12 @@ export class RegistryBrokerClient { return searchImpl(this, params); } + async delegate( + request: DelegationPlanRequest, + ): Promise { + return delegateImpl(this, request); + } + async searchErc8004ByAgentId(params: { chainId: number; agentId: number | bigint | string; @@ -877,6 +889,12 @@ export class RegistryBrokerClient { return getSkillStatusImpl(this, params); } + async getSkillSecurityBreakdown( + params: SkillSecurityBreakdownRequest, + ): Promise { + return getSkillSecurityBreakdownImpl(this, params); + } + async getSkillStatusByRepo( params: SkillPreviewByRepoRequest, ): Promise { diff --git a/src/services/registry-broker/client/skills.ts b/src/services/registry-broker/client/skills.ts index c03aa77..3514e53 100644 --- a/src/services/registry-broker/client/skills.ts +++ b/src/services/registry-broker/client/skills.ts @@ -39,6 +39,8 @@ import type { SkillInstallCopyTelemetryResponse, SkillResolverManifestResponse, SkillRegistryVersionsResponse, + SkillSecurityBreakdownRequest, + SkillSecurityBreakdownResponse, SkillVerificationDomainProofChallengeRequest, SkillVerificationDomainProofChallengeResponse, SkillVerificationDomainProofVerifyRequest, @@ -71,6 +73,7 @@ import { skillPreviewRecordSchema, skillInstallResponseSchema, skillInstallCopyTelemetryResponseSchema, + skillSecurityBreakdownResponseSchema, skillResolverManifestResponseSchema, skillVerificationDomainProofChallengeResponseSchema, skillVerificationDomainProofVerifyResponseSchema, @@ -148,6 +151,27 @@ export async function listSkills( ); } +export async function getSkillSecurityBreakdown( + client: RegistryBrokerClient, + params: SkillSecurityBreakdownRequest, +): Promise { + const normalizedJobId = params.jobId.trim(); + if (!normalizedJobId) { + throw new Error('jobId is required'); + } + + const raw = await client.requestJson( + `/skills/${encodeURIComponent(normalizedJobId)}/security-breakdown`, + { method: 'GET' }, + ); + + return client.parseWithSchema( + raw, + skillSecurityBreakdownResponseSchema, + 'skill security breakdown response', + ); +} + export async function getSkillsCatalog( client: RegistryBrokerClient, params: SkillCatalogQueryOptions = {}, diff --git a/src/services/registry-broker/types.ts b/src/services/registry-broker/types.ts index 70d3aab..2f3e0cc 100644 --- a/src/services/registry-broker/types.ts +++ b/src/services/registry-broker/types.ts @@ -12,6 +12,7 @@ import { encryptionHandshakeResponseSchema, detectProtocolResponseSchema, dashboardStatsResponseSchema, + delegationPlanResponseSchema, metricsSummaryResponseSchema, popularResponseSchema, protocolsResponseSchema, @@ -89,6 +90,7 @@ import { skillResolverManifestResponseSchema, skillRegistryFileDescriptorSchema, skillRegistryJobStatusResponseSchema, + skillSecurityBreakdownResponseSchema, skillRegistryListResponseSchema, skillRegistryMineResponseSchema, skillRegistryMyListResponseSchema, @@ -291,6 +293,50 @@ export interface SearchParams { metadata?: Record>; } +export interface DelegationWorkspaceContext { + openFiles?: string[]; + modifiedFiles?: string[]; + relatedPaths?: string[]; + errors?: string[]; + commands?: string[]; + languages?: string[]; +} + +export interface DelegationPlanFilter { + capabilities?: string[]; + type?: 'ai-agents' | 'mcp-servers'; + registry?: string; + registries?: string[]; + protocols?: string[]; + adapters?: string[]; + verifiedOnly?: boolean; + onlineOnly?: boolean; + minTrust?: number; + minTrustSignals?: Record; + metadata?: Record>; + metadataRanges?: Record; +} + +export interface DelegationPlanRequest { + task: string; + context?: string; + workspace?: DelegationWorkspaceContext; + limit?: number; + filter?: DelegationPlanFilter; +} + +export type DelegationPlanCandidate = z.infer< + typeof delegationPlanResponseSchema +>['opportunities'][number]['candidates'][number]; + +export type DelegationOpportunity = z.infer< + typeof delegationPlanResponseSchema +>['opportunities'][number]; + +export type DelegationPlanResponse = z.infer< + typeof delegationPlanResponseSchema +>; + export type RegistryStatsResponse = z.infer; export type RegistriesResponse = z.infer; @@ -302,6 +348,9 @@ export type ResolvedAgentResponse = z.infer; export type CreateSessionResponse = z.infer; export type SendMessageResponse = z.infer; +export type SkillSecurityBreakdownResponse = z.infer< + typeof skillSecurityBreakdownResponseSchema +>; export type ChatHistorySnapshotResponse = z.infer< typeof chatHistorySnapshotResponseSchema >; @@ -539,6 +588,10 @@ export interface SkillRegistryVoteRequest { upvoted: boolean; } +export interface SkillSecurityBreakdownRequest { + jobId: string; +} + export interface SkillStatusRequest { name: string; version?: string; From b8e32bd2e1848b55fbaf9456331d87094790c208 Mon Sep 17 00:00:00 2001 From: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:15:33 -0400 Subject: [PATCH 3/4] fix: support browser-safe broker key generation Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> --- .../registry-broker/client/base-client.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/services/registry-broker/client/base-client.ts b/src/services/registry-broker/client/base-client.ts index 4dc916e..978ca7b 100644 --- a/src/services/registry-broker/client/base-client.ts +++ b/src/services/registry-broker/client/base-client.ts @@ -1546,9 +1546,21 @@ export class RegistryBrokerClient { return nodeCrypto; } + private getSecureRandomBytes(size: number, feature: string): Uint8Array { + const webCrypto = globalThis.crypto; + if (webCrypto && typeof webCrypto.getRandomValues === 'function') { + const bytes = new Uint8Array(size); + webCrypto.getRandomValues(bytes); + return bytes; + } + return this.getNodeCrypto(feature).randomBytes(size); + } + createEphemeralKeyPair(): EphemeralKeyPair { - const { randomBytes } = this.getNodeCrypto('generateEphemeralKeyPair'); - const privateKeyBytes = randomBytes(32); + const privateKeyBytes = this.getSecureRandomBytes( + 32, + 'generateEphemeralKeyPair', + ); const publicKey = secp256k1.getPublicKey(privateKeyBytes, true); return { privateKey: Buffer.from(privateKeyBytes).toString('hex'), From a9d540a9f0b2e54ddd667a5cfc8b656f5016d5ed Mon Sep 17 00:00:00 2001 From: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:28:40 -0400 Subject: [PATCH 4/4] feat: add broker guard client helpers Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> --- .../registry-broker-credits-client.test.ts | 137 ++++++++++ .../registry-broker-guard-client.test.ts | 250 +++++++++++++++++ .../registry-broker/client/base-client.ts | 72 +++++ .../registry-broker/client/credits.ts | 74 +++++ src/services/registry-broker/client/guard.ts | 133 +++++++++ src/services/registry-broker/schemas.ts | 255 ++++++++++++++++-- src/services/registry-broker/types.ts | 72 +++++ src/utils/dynamic-import.ts | 14 +- 8 files changed, 986 insertions(+), 21 deletions(-) create mode 100644 __tests__/services/registry-broker-credits-client.test.ts create mode 100644 __tests__/services/registry-broker-guard-client.test.ts create mode 100644 src/services/registry-broker/client/guard.ts diff --git a/__tests__/services/registry-broker-credits-client.test.ts b/__tests__/services/registry-broker-credits-client.test.ts new file mode 100644 index 0000000..051c2a8 --- /dev/null +++ b/__tests__/services/registry-broker-credits-client.test.ts @@ -0,0 +1,137 @@ +import { jest } from '@jest/globals'; +import { RegistryBrokerClient } from '../../src/services/registry-broker'; + +function createResponse(payload: { + status?: number; + json?: () => Promise; +}): Response { + return { + ok: (payload.status ?? 200) >= 200 && (payload.status ?? 200) < 300, + status: payload.status ?? 200, + statusText: 'OK', + headers: new Headers({ 'content-type': 'application/json' }), + json: payload.json ?? (async () => ({})), + text: async () => JSON.stringify(await (payload.json?.() ?? {})), + } as unknown as Response; +} + +describe('RegistryBrokerClient credit helpers', () => { + const fetchImplementation = jest.fn(); + + beforeEach(() => { + fetchImplementation.mockReset(); + }); + + it('retrieves credit balance for the authenticated account', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + accountId: '0.0.1234', + balance: 91, + timestamp: '2026-04-05T18:00:00.000Z', + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + apiKey: 'rb_test_key', + accountId: '0.0.1234', + fetchImplementation, + }); + + const balance = await client.getCreditsBalance({ accountId: '0.0.1234' }); + + expect(balance.accountId).toBe('0.0.1234'); + expect(balance.balance).toBe(91); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/credits/balance?accountId=0.0.1234', + expect.objectContaining({ + method: 'GET', + headers: expect.any(Headers), + }), + ); + }); + + it('retrieves available credit providers', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + providers: [ + { + name: 'stripe', + publishableKey: 'pk_test_123', + currency: 'usd', + centsPerHbar: 100, + }, + ], + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const providers = await client.getCreditProviders(); + + expect(providers.providers).toHaveLength(1); + expect(providers.providers[0]?.name).toBe('stripe'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/credits/providers', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('creates an HBAR purchase intent without exposing signing to the broker', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + transaction: 'AQID', + transactionId: '0.0.1234@1712332800.000000000', + network: 'mainnet', + accountId: '0.0.1234', + treasuryAccountId: '0.0.98', + hbarAmount: 3, + credits: 300, + tinybarAmount: 300000000, + memo: 'skill-publish funding', + centsPerHbar: 100, + validStart: '2026-04-05T18:00:00.000Z', + validDurationSeconds: 120, + requiresManualSubmit: true, + purchaseId: 'purchase_123', + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + apiKey: 'rb_test_key', + accountId: '0.0.1234', + fetchImplementation, + }); + + const intent = await client.createHbarPurchaseIntent({ + accountId: '0.0.1234', + hbarAmount: 3, + memo: 'skill-publish funding', + }); + + expect(intent.requiresManualSubmit).toBe(true); + expect(intent.purchaseId).toBe('purchase_123'); + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/credits/payments/hbar/intent', + expect.objectContaining({ + method: 'POST', + headers: expect.any(Headers), + body: JSON.stringify({ + accountId: '0.0.1234', + hbarAmount: 3, + memo: 'skill-publish funding', + }), + }), + ); + }); +}); diff --git a/__tests__/services/registry-broker-guard-client.test.ts b/__tests__/services/registry-broker-guard-client.test.ts new file mode 100644 index 0000000..d29235c --- /dev/null +++ b/__tests__/services/registry-broker-guard-client.test.ts @@ -0,0 +1,250 @@ +import { jest } from '@jest/globals'; +import { RegistryBrokerClient } from '../../src/services/registry-broker'; + +function createResponse(payload: { + status?: number; + json?: () => Promise; +}): Response { + return { + ok: (payload.status ?? 200) >= 200 && (payload.status ?? 200) < 300, + status: payload.status ?? 200, + statusText: 'OK', + headers: new Headers({ 'content-type': 'application/json' }), + json: payload.json ?? (async () => ({})), + text: async () => JSON.stringify(await (payload.json?.() ?? {})), + } as unknown as Response; +} + +describe('RegistryBrokerClient guard helpers', () => { + const fetchImplementation = jest.fn(); + + beforeEach(() => { + fetchImplementation.mockReset(); + }); + + it('retrieves the guard session and entitlements payload', async () => { + const sessionPayload = { + principal: { + signedIn: true, + userId: 'user_123', + email: 'guard@example.com', + accountId: '0.0.1234', + stripeCustomerId: 'cus_123', + roles: ['user'], + }, + entitlements: { + planId: 'pro', + includedMonthlyCredits: 500, + deviceLimit: 5, + retentionDays: 90, + syncEnabled: true, + premiumFeedsEnabled: true, + teamPolicyEnabled: false, + }, + balance: { + accountId: '0.0.1234', + availableCredits: 212, + }, + bucketingMode: 'product-bucketed', + buckets: [ + { + bucketId: 'guard_credits', + label: 'Guard credits', + availableCredits: 212, + includedMonthlyCredits: 500, + }, + ], + }; + fetchImplementation + .mockResolvedValueOnce( + createResponse({ json: async () => sessionPayload }), + ) + .mockResolvedValueOnce( + createResponse({ json: async () => sessionPayload }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + apiKey: 'rb_test_key', + fetchImplementation, + }); + + const session = await client.getGuardSession(); + const entitlements = await client.getGuardEntitlements(); + + expect(session.entitlements.planId).toBe('pro'); + expect(entitlements.principal.email).toBe('guard@example.com'); + expect(fetchImplementation).toHaveBeenNthCalledWith( + 1, + 'https://api.example.com/api/v1/guard/auth/session', + expect.objectContaining({ method: 'GET' }), + ); + expect(fetchImplementation).toHaveBeenNthCalledWith( + 2, + 'https://api.example.com/api/v1/guard/entitlements', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('retrieves billing balance, trust, and revocation data', async () => { + fetchImplementation + .mockResolvedValueOnce( + createResponse({ + json: async () => ({ + generatedAt: '2026-04-09T18:00:00.000Z', + bucketingMode: 'shared-ledger', + buckets: [ + { + bucketId: 'registry_credits', + label: 'Shared credits', + availableCredits: 98, + }, + ], + }), + }), + ) + .mockResolvedValueOnce( + createResponse({ + json: async () => ({ + generatedAt: '2026-04-09T18:00:00.000Z', + query: { + sha256: + '5f70bf18a086007016e948b04aed3b82103a36beab34d7d9f4b5d1f1c6ed7095', + }, + match: { + artifactId: 'skill_123', + artifactName: 'hashnet-mcp', + artifactType: 'skill', + artifactSlug: 'hashnet-mcp', + recommendation: 'review', + verified: true, + safetyScore: 94, + trustScore: 91, + href: 'https://hol.org/registry/skills/hashnet-mcp', + ecosystem: 'codex', + }, + evidence: ['attested', 'verified publisher'], + }), + }), + ) + .mockResolvedValueOnce( + createResponse({ + json: async () => ({ + generatedAt: '2026-04-09T18:00:00.000Z', + query: { + ecosystem: 'codex', + name: 'hashnet-mcp', + version: '1.0.0', + }, + items: [ + { + artifactId: 'skill_123', + artifactName: 'hashnet-mcp', + artifactType: 'skill', + artifactSlug: 'hashnet-mcp', + recommendation: 'monitor', + verified: true, + ecosystem: 'codex', + }, + ], + }), + }), + ) + .mockResolvedValueOnce( + createResponse({ + json: async () => ({ + generatedAt: '2026-04-09T18:00:00.000Z', + items: [ + { + id: 'rev_123', + artifactId: 'skill_999', + artifactName: 'bad-tool', + reason: 'publisher revoked', + severity: 'high', + publishedAt: '2026-04-08T18:00:00.000Z', + }, + ], + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + fetchImplementation, + }); + + const balance = await client.getGuardBillingBalance(); + const trustByHash = await client.getGuardTrustByHash( + '5f70bf18a086007016e948b04aed3b82103a36beab34d7d9f4b5d1f1c6ed7095', + ); + const resolved = await client.resolveGuardTrust({ + ecosystem: 'codex', + name: 'hashnet-mcp', + version: '1.0.0', + }); + const revocations = await client.getGuardRevocations(); + + expect(balance.buckets[0]?.availableCredits).toBe(98); + expect(trustByHash.match?.artifactName).toBe('hashnet-mcp'); + expect(resolved.items).toHaveLength(1); + expect(revocations.items[0]?.severity).toBe('high'); + }); + + it('syncs guard receipts to the broker', async () => { + fetchImplementation.mockResolvedValueOnce( + createResponse({ + json: async () => ({ + syncedAt: '2026-04-09T18:00:00.000Z', + receiptsStored: 1, + }), + }), + ); + + const client = new RegistryBrokerClient({ + baseUrl: 'https://api.example.com', + apiKey: 'rb_test_key', + fetchImplementation, + }); + + const synced = await client.syncGuardReceipts({ + receipts: [ + { + receiptId: 'receipt_123', + capturedAt: '2026-04-09T17:59:00.000Z', + harness: 'codex', + deviceId: 'device_123', + deviceName: 'MacBook Pro', + artifactId: 'skill_123', + artifactName: 'hashnet-mcp', + artifactType: 'skill', + artifactSlug: 'hashnet-mcp', + artifactHash: + '5f70bf18a086007016e948b04aed3b82103a36beab34d7d9f4b5d1f1c6ed7095', + policyDecision: 'allow', + recommendation: 'monitor', + changedSinceLastApproval: false, + publisher: 'hashgraph-online', + capabilities: ['mcp:list_tools'], + summary: 'First-party MCP canary approved.', + }, + ], + }); + + expect(synced.receiptsStored).toBe(1); + const request = fetchImplementation.mock.calls[0]?.[1] as RequestInit; + expect(fetchImplementation).toHaveBeenCalledWith( + 'https://api.example.com/api/v1/guard/receipts/sync', + expect.objectContaining({ + method: 'POST', + }), + ); + expect(JSON.parse(String(request.body))).toMatchObject({ + receipts: [ + expect.objectContaining({ + receiptId: 'receipt_123', + harness: 'codex', + }), + ], + }); + }); +}); diff --git a/src/services/registry-broker/client/base-client.ts b/src/services/registry-broker/client/base-client.ts index 978ca7b..f58a8fc 100644 --- a/src/services/registry-broker/client/base-client.ts +++ b/src/services/registry-broker/client/base-client.ts @@ -18,6 +18,8 @@ import type { EncryptCipherEnvelopeOptions, EphemeralKeyPair, HistoryAutoTopUpOptions, + HbarPurchaseIntentRequest, + HbarPurchaseIntentResponse, InitializeAgentClientOptions, JsonObject, JsonValue, @@ -48,6 +50,16 @@ import type { CompactHistoryRequestPayload, CreateAdapterRegistryCategoryRequest, CreateSessionResponse, + CreditBalanceResponse, + CreditProvidersResponse, + GuardBalanceResponse, + GuardReceiptSyncPayload, + GuardReceiptSyncResponse, + GuardRevocationResponse, + GuardSessionResponse, + GuardTrustByHashResponse, + GuardTrustResolveQuery, + GuardTrustResolveResponse, CreditPurchaseResponse, DashboardStatsResponse, DelegationPlanRequest, @@ -235,11 +247,23 @@ import type { X402PurchaseResult, } from './credits'; import { + createHbarPurchaseIntent as createHbarPurchaseIntentImpl, + getCreditProviders as getCreditProvidersImpl, + getCreditsBalance as getCreditsBalanceImpl, buyCreditsWithX402 as buyCreditsWithX402Impl, getX402Minimums as getX402MinimumsImpl, purchaseCreditsWithHbar as purchaseCreditsWithHbarImpl, purchaseCreditsWithX402 as purchaseCreditsWithX402Impl, } from './credits'; +import { + getGuardBillingBalance as getGuardBillingBalanceImpl, + getGuardEntitlements as getGuardEntitlementsImpl, + getGuardRevocations as getGuardRevocationsImpl, + getGuardSession as getGuardSessionImpl, + getGuardTrustByHash as getGuardTrustByHashImpl, + resolveGuardTrust as resolveGuardTrustImpl, + syncGuardReceipts as syncGuardReceiptsImpl, +} from './guard'; import { authenticateWithLedger as authenticateWithLedgerImpl, authenticateWithLedgerCredentials as authenticateWithLedgerCredentialsImpl, @@ -1216,6 +1240,54 @@ export class RegistryBrokerClient { return dashboardStatsImpl(this); } + async getCreditsBalance( + params: { accountId?: string } = {}, + ): Promise { + return getCreditsBalanceImpl(this, params); + } + + async getCreditProviders(): Promise { + return getCreditProvidersImpl(this); + } + + async getGuardSession(): Promise { + return getGuardSessionImpl(this); + } + + async getGuardEntitlements(): Promise { + return getGuardEntitlementsImpl(this); + } + + async getGuardBillingBalance(): Promise { + return getGuardBillingBalanceImpl(this); + } + + async getGuardTrustByHash(sha256: string): Promise { + return getGuardTrustByHashImpl(this, sha256); + } + + async resolveGuardTrust( + query: GuardTrustResolveQuery, + ): Promise { + return resolveGuardTrustImpl(this, query); + } + + async getGuardRevocations(): Promise { + return getGuardRevocationsImpl(this); + } + + async syncGuardReceipts( + payload: GuardReceiptSyncPayload, + ): Promise { + return syncGuardReceiptsImpl(this, payload); + } + + async createHbarPurchaseIntent( + payload: HbarPurchaseIntentRequest, + ): Promise { + return createHbarPurchaseIntentImpl(this, payload); + } + async purchaseCreditsWithHbar(params: { accountId: string; privateKey: string; diff --git a/src/services/registry-broker/client/credits.ts b/src/services/registry-broker/client/credits.ts index ebb7cb4..dbdc2d1 100644 --- a/src/services/registry-broker/client/credits.ts +++ b/src/services/registry-broker/client/credits.ts @@ -1,12 +1,19 @@ import type { + CreditBalanceResponse, + CreditProvidersResponse, CreditPurchaseResponse, + HbarPurchaseIntentRequest, + HbarPurchaseIntentResponse, JsonObject, JsonValue, X402CreditPurchaseResponse, X402MinimumsResponse, } from '../types'; import { + creditBalanceResponseSchema, + creditProvidersResponseSchema, creditPurchaseResponseSchema, + hbarPurchaseIntentResponseSchema, x402CreditPurchaseResponseSchema, x402MinimumsResponseSchema, } from '../schemas'; @@ -47,6 +54,73 @@ export type X402PurchaseResult = X402CreditPurchaseResponse & { paymentResponse?: unknown; }; +export async function getCreditsBalance( + client: RegistryBrokerClient, + params: { accountId?: string } = {}, +): Promise { + const query = new URLSearchParams(); + const normalizedAccountId = params.accountId?.trim(); + if (normalizedAccountId) { + query.set('accountId', normalizedAccountId); + } + const suffix = query.size > 0 ? `?${query.toString()}` : ''; + const raw = await client.requestJson(`/credits/balance${suffix}`, { + method: 'GET', + }); + return client.parseWithSchema( + raw, + creditBalanceResponseSchema, + 'credit balance response', + ); +} + +export async function getCreditProviders( + client: RegistryBrokerClient, +): Promise { + const raw = await client.requestJson('/credits/providers', { + method: 'GET', + }); + return client.parseWithSchema( + raw, + creditProvidersResponseSchema, + 'credit providers response', + ); +} + +export async function createHbarPurchaseIntent( + client: RegistryBrokerClient, + payload: HbarPurchaseIntentRequest, +): Promise { + const body: JsonObject = {}; + const normalizedAccountId = payload.accountId?.trim(); + if (normalizedAccountId) { + body.accountId = normalizedAccountId; + } + if (payload.credits !== undefined) { + body.credits = payload.credits; + } + if (payload.hbarAmount !== undefined) { + body.hbarAmount = payload.hbarAmount; + } + if (payload.memo?.trim()) { + body.memo = payload.memo.trim(); + } + + const raw = await client.requestJson( + '/credits/payments/hbar/intent', + { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body, + }, + ); + return client.parseWithSchema( + raw, + hbarPurchaseIntentResponseSchema, + 'hbar purchase intent response', + ); +} + type LoadX402DependenciesResult = { createPaymentClient: (walletClient: object) => PaymentClient; decodePaymentResponse: (value: string) => unknown; diff --git a/src/services/registry-broker/client/guard.ts b/src/services/registry-broker/client/guard.ts new file mode 100644 index 0000000..8800271 --- /dev/null +++ b/src/services/registry-broker/client/guard.ts @@ -0,0 +1,133 @@ +import type { + GuardBalanceResponse, + GuardReceiptSyncPayload, + GuardReceiptSyncResponse, + GuardRevocationResponse, + GuardSessionResponse, + GuardTrustByHashResponse, + GuardTrustResolveQuery, + GuardTrustResolveResponse, + JsonValue, +} from '../types'; +import { + guardBalanceResponseSchema, + guardReceiptSyncResponseSchema, + guardRevocationResponseSchema, + guardSessionResponseSchema, + guardTrustByHashResponseSchema, + guardTrustResolveResponseSchema, +} from '../schemas'; +import type { RegistryBrokerClient } from './base-client'; + +export async function getGuardSession( + client: RegistryBrokerClient, +): Promise { + const raw = await client.requestJson('/guard/auth/session', { + method: 'GET', + }); + return client.parseWithSchema( + raw, + guardSessionResponseSchema, + 'guard session response', + ); +} + +export async function getGuardEntitlements( + client: RegistryBrokerClient, +): Promise { + const raw = await client.requestJson('/guard/entitlements', { + method: 'GET', + }); + return client.parseWithSchema( + raw, + guardSessionResponseSchema, + 'guard entitlements response', + ); +} + +export async function getGuardBillingBalance( + client: RegistryBrokerClient, +): Promise { + const raw = await client.requestJson('/guard/billing/balance', { + method: 'GET', + }); + return client.parseWithSchema( + raw, + guardBalanceResponseSchema, + 'guard billing balance response', + ); +} + +export async function getGuardTrustByHash( + client: RegistryBrokerClient, + sha256: string, +): Promise { + const normalizedHash = sha256.trim(); + if (!normalizedHash) { + throw new Error('sha256 is required'); + } + const raw = await client.requestJson( + `/guard/trust/by-hash/${encodeURIComponent(normalizedHash)}`, + { method: 'GET' }, + ); + return client.parseWithSchema( + raw, + guardTrustByHashResponseSchema, + 'guard trust by hash response', + ); +} + +export async function resolveGuardTrust( + client: RegistryBrokerClient, + query: GuardTrustResolveQuery, +): Promise { + const params = new URLSearchParams(); + if (query.ecosystem?.trim()) { + params.set('ecosystem', query.ecosystem.trim()); + } + if (query.name?.trim()) { + params.set('name', query.name.trim()); + } + if (query.version?.trim()) { + params.set('version', query.version.trim()); + } + const suffix = params.size > 0 ? `?${params.toString()}` : ''; + const raw = await client.requestJson( + `/guard/trust/resolve${suffix}`, + { method: 'GET' }, + ); + return client.parseWithSchema( + raw, + guardTrustResolveResponseSchema, + 'guard trust resolve response', + ); +} + +export async function getGuardRevocations( + client: RegistryBrokerClient, +): Promise { + const raw = await client.requestJson('/guard/revocations', { + method: 'GET', + }); + return client.parseWithSchema( + raw, + guardRevocationResponseSchema, + 'guard revocations response', + ); +} + +export async function syncGuardReceipts( + client: RegistryBrokerClient, + payload: GuardReceiptSyncPayload, +): Promise { + const raw = await client.requestJson('/guard/receipts/sync', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: payload, + }); + return client.parseWithSchema( + raw, + guardReceiptSyncResponseSchema, + 'guard receipt sync response', + ); +} diff --git a/src/services/registry-broker/schemas.ts b/src/services/registry-broker/schemas.ts index a557163..b8a2135 100644 --- a/src/services/registry-broker/schemas.ts +++ b/src/services/registry-broker/schemas.ts @@ -218,6 +218,65 @@ export const resolveResponseSchema = z.object({ agent: searchHitSchema, }); +const delegationPlanCandidateSchema = z + .object({ + uaid: z.string(), + score: z.number(), + displayName: z.string().optional(), + summary: z.string().optional(), + protocols: z.array(z.string()).optional(), + surfaces: z.array(z.string()).optional(), + languages: z.array(z.string()).optional(), + artifacts: z.array(z.string()).optional(), + availability: z.boolean().optional(), + explanation: z.string().optional(), + }) + .passthrough(); + +const delegationOpportunitySchema = z + .object({ + id: z.string(), + title: z.string(), + reason: z.string(), + role: z.string(), + type: z.enum(['ai-agents', 'mcp-servers']), + suggestedMode: z.enum(['best-match', 'fallback', 'parallel']), + searchQueries: z.array(z.string()), + protocols: z.array(z.string()).optional(), + surfaces: z.array(z.string()).optional(), + languages: z.array(z.string()).optional(), + artifacts: z.array(z.string()).optional(), + candidates: z.array(delegationPlanCandidateSchema), + }) + .passthrough(); + +const delegationRecommendationSchema = z + .object({ + action: z.enum(['delegate-now', 'review-shortlist', 'handle-locally']), + confidence: z.number(), + reason: z.string(), + opportunityId: z.string().optional(), + candidate: delegationPlanCandidateSchema.optional(), + }) + .passthrough(); + +export const delegationPlanResponseSchema = z + .object({ + task: z.string(), + context: z.string().optional(), + summary: z.string().optional(), + intents: z.array(z.string()), + protocols: z.array(z.string()), + surfaces: z.array(z.string()), + languages: z.array(z.string()).optional(), + artifacts: z.array(z.string()).optional(), + shouldDelegate: z.boolean(), + localFirstReason: z.string().optional(), + recommendation: delegationRecommendationSchema.optional(), + opportunities: z.array(delegationOpportunitySchema), + }) + .passthrough(); + const agentFeedbackSummarySchema = z.object({ averageScore: z.number(), totalFeedbacks: z.number(), @@ -870,6 +929,170 @@ export const creditPurchaseResponseSchema = z.object({ consensusTimestamp: z.string().nullable().optional(), }); +export const creditProviderSummarySchema = z.object({ + name: z.string(), + publishableKey: z.string().optional(), + currency: z.string().optional(), + centsPerHbar: z.number().nullable().optional(), +}); + +export const creditProvidersResponseSchema = z.object({ + providers: z.array(creditProviderSummarySchema), +}); + +export const creditBalanceResponseSchema = z.object({ + accountId: z.string(), + balance: z.number(), + balanceRecord: jsonValueSchema.optional(), + timestamp: z.string().optional(), +}); + +export const guardPlanIdSchema = z.enum(['free', 'pro', 'team', 'enterprise']); + +export const guardBucketBalanceSchema = z.object({ + bucketId: z.enum([ + 'registry_credits', + 'chat_credits', + 'guard_credits', + 'org_policy_credits', + ]), + label: z.string(), + availableCredits: z.number(), + includedMonthlyCredits: z.number().nullable().optional(), +}); + +export const guardPrincipalSchema = z.object({ + signedIn: z.boolean(), + userId: z.string().optional(), + email: z.string().optional(), + accountId: z.string().optional(), + stripeCustomerId: z.string().optional(), + roles: z.array(z.string()), +}); + +export const guardEntitlementsSchema = z.object({ + planId: guardPlanIdSchema, + includedMonthlyCredits: z.number(), + deviceLimit: z.number(), + retentionDays: z.number(), + syncEnabled: z.boolean(), + premiumFeedsEnabled: z.boolean(), + teamPolicyEnabled: z.boolean(), +}); + +export const guardSessionResponseSchema = z.object({ + principal: guardPrincipalSchema, + entitlements: guardEntitlementsSchema, + balance: z + .object({ + accountId: z.string(), + availableCredits: z.number(), + }) + .nullable(), + bucketingMode: z.enum(['shared-ledger', 'product-bucketed']), + buckets: z.array(guardBucketBalanceSchema), +}); + +export const guardBalanceResponseSchema = z.object({ + generatedAt: z.string(), + bucketingMode: z.enum(['shared-ledger', 'product-bucketed']), + buckets: z.array(guardBucketBalanceSchema), +}); + +export const guardTrustMatchSchema = z.object({ + artifactId: z.string(), + artifactName: z.string(), + artifactType: z.enum(['skill', 'plugin']), + artifactSlug: z.string(), + recommendation: z.enum(['monitor', 'review', 'block']), + verified: z.boolean(), + safetyScore: z.number().nullable().optional(), + trustScore: z.number().nullable().optional(), + href: z.string().optional(), + ecosystem: z.string().optional(), +}); + +export const guardTrustByHashResponseSchema = z.object({ + generatedAt: z.string(), + query: z.object({ + sha256: z.string(), + }), + match: guardTrustMatchSchema.nullable(), + evidence: z.array(z.string()), +}); + +export const guardTrustResolveResponseSchema = z.object({ + generatedAt: z.string(), + query: z.object({ + ecosystem: z.string().optional(), + name: z.string().optional(), + version: z.string().optional(), + }), + items: z.array(guardTrustMatchSchema), +}); + +export const guardRevocationSchema = z.object({ + id: z.string(), + artifactId: z.string(), + artifactName: z.string(), + reason: z.string(), + severity: z.enum(['low', 'medium', 'high']), + publishedAt: z.string(), +}); + +export const guardRevocationResponseSchema = z.object({ + generatedAt: z.string(), + items: z.array(guardRevocationSchema), +}); + +export const guardReceiptSchema = z.object({ + receiptId: z.string(), + capturedAt: z.string(), + harness: z.string(), + deviceId: z.string(), + deviceName: z.string(), + artifactId: z.string(), + artifactName: z.string(), + artifactType: z.enum(['skill', 'plugin']), + artifactSlug: z.string(), + artifactHash: z.string(), + policyDecision: z.enum([ + 'allow', + 'warn', + 'block', + 'review', + 'require-reapproval', + 'sandbox-required', + ]), + recommendation: z.enum(['monitor', 'review', 'block']), + changedSinceLastApproval: z.boolean(), + publisher: z.string().optional(), + capabilities: z.array(z.string()), + summary: z.string(), +}); + +export const guardReceiptSyncResponseSchema = z.object({ + syncedAt: z.string(), + receiptsStored: z.number(), +}); + +export const hbarPurchaseIntentResponseSchema = z.object({ + transaction: z.string(), + transactionId: z.string(), + network: z.enum(['mainnet', 'testnet']), + accountId: z.string(), + treasuryAccountId: z.string(), + hbarAmount: z.number(), + credits: z.number(), + tinybarAmount: z.number(), + memo: z.string(), + centsPerHbar: z.number(), + validStart: z.string(), + validDurationSeconds: z.number(), + requiresManualSubmit: z.literal(true), + purchaseId: z.string(), +}); + const x402SettlementSchema = z .object({ success: z.boolean().optional(), @@ -1361,14 +1584,27 @@ export const skillDeprecationsResponseSchema = z }) .passthrough(); +export const skillSecurityBreakdownResponseSchema = z + .object({ + jobId: z.string(), + score: z.number().nullable().optional(), + findings: z.array(z.unknown()).optional(), + summary: z.unknown().optional(), + generatedAt: z.string().nullable().optional(), + scannerVersion: z.string().nullable().optional(), + }) + .passthrough(); + export const skillBadgeMetricSchema = z.enum([ 'version', + 'version_verification', 'status', 'verification', 'repo_commit', 'manifest', 'domain', 'trust', + 'tier', 'safety', 'upvotes', 'updated', @@ -1661,14 +1897,14 @@ export const skillPreviewRecordSchema = z generatedAt: z.string(), expiresAt: z.string(), statusUrl: z.string(), - authoritative: z.literal(false), + authoritative: z.boolean(), }) .passthrough(); export const skillPreviewLookupResponseSchema = z .object({ found: z.boolean(), - authoritative: z.literal(false), + authoritative: z.boolean(), preview: skillPreviewRecordSchema.nullable(), statusUrl: z.string().nullable(), expiresAt: z.string().nullable(), @@ -1725,20 +1961,7 @@ export const skillStatusResponseSchema = z published: z.boolean(), verifiedDomain: z.boolean(), trustTier: skillTrustTierSchema, - badgeMetric: z.enum([ - 'version', - 'version_verification', - 'status', - 'verification', - 'repo_commit', - 'manifest', - 'domain', - 'trust', - 'tier', - 'upvotes', - 'updated', - 'safety', - ]), + badgeMetric: skillBadgeMetricSchema, checks: skillStatusChecksSchema, nextSteps: z.array(skillStatusNextStepSchema), verificationSignals: skillStatusVerificationSignalsSchema, diff --git a/src/services/registry-broker/types.ts b/src/services/registry-broker/types.ts index 2f3e0cc..205e33c 100644 --- a/src/services/registry-broker/types.ts +++ b/src/services/registry-broker/types.ts @@ -18,7 +18,23 @@ import { protocolsResponseSchema, registerAgentResponseSchema, registrationQuoteResponseSchema, + creditProviderSummarySchema, + creditProvidersResponseSchema, + creditBalanceResponseSchema, + guardBalanceResponseSchema, + guardBucketBalanceSchema, + guardEntitlementsSchema, + guardPlanIdSchema, + guardPrincipalSchema, + guardReceiptSchema, + guardReceiptSyncResponseSchema, + guardRevocationResponseSchema, + guardSessionResponseSchema, + guardTrustByHashResponseSchema, + guardTrustMatchSchema, + guardTrustResolveResponseSchema, creditPurchaseResponseSchema, + hbarPurchaseIntentResponseSchema, x402CreditPurchaseResponseSchema, x402MinimumsResponseSchema, registriesResponseSchema, @@ -255,6 +271,48 @@ export interface HistoryAutoTopUpOptions extends AutoTopUpOptions { hbarAmount?: number; } +export type GuardPlanId = z.infer; + +export type GuardPrincipal = z.infer; + +export type GuardEntitlements = z.infer; + +export type GuardBucketBalance = z.infer; + +export type GuardSessionResponse = z.infer; + +export type GuardBalanceResponse = z.infer; + +export type GuardTrustMatch = z.infer; + +export type GuardTrustByHashResponse = z.infer< + typeof guardTrustByHashResponseSchema +>; + +export interface GuardTrustResolveQuery { + ecosystem?: string; + name?: string; + version?: string; +} + +export type GuardTrustResolveResponse = z.infer< + typeof guardTrustResolveResponseSchema +>; + +export type GuardRevocationResponse = z.infer< + typeof guardRevocationResponseSchema +>; + +export type GuardReceipt = z.infer; + +export interface GuardReceiptSyncPayload { + receipts: GuardReceipt[]; +} + +export type GuardReceiptSyncResponse = z.infer< + typeof guardReceiptSyncResponseSchema +>; + export interface RegisterAgentOptions { autoTopUp?: AutoTopUpOptions; } @@ -734,6 +792,20 @@ export type RegistrationProgressResponse = z.infer< export type RegisterAgentQuoteResponse = z.infer< typeof registrationQuoteResponseSchema >; +export type CreditProviderSummary = z.infer; +export type CreditProvidersResponse = z.infer< + typeof creditProvidersResponseSchema +>; +export type CreditBalanceResponse = z.infer; +export interface HbarPurchaseIntentRequest { + accountId?: string; + credits?: number; + hbarAmount?: number; + memo?: string; +} +export type HbarPurchaseIntentResponse = z.infer< + typeof hbarPurchaseIntentResponseSchema +>; export type CreditPurchaseResponse = z.infer< typeof creditPurchaseResponseSchema >; diff --git a/src/utils/dynamic-import.ts b/src/utils/dynamic-import.ts index 0971baa..16e080c 100644 --- a/src/utils/dynamic-import.ts +++ b/src/utils/dynamic-import.ts @@ -10,12 +10,16 @@ type NodeModuleNamespace = { }; function getNodeRequireSync(): NodeRequire | null { + const builtinModuleLoader = ( + process as typeof process & { + getBuiltinModule?: (name: string) => TModule | undefined; + } + ).getBuiltinModule; + try { - const moduleNamespace = ( - process as typeof process & { - getBuiltinModule?: (name: string) => unknown; - } - ).getBuiltinModule?.('module') as Partial | undefined; + const moduleNamespace = builtinModuleLoader?.('module') as + | Partial + | undefined; if (typeof moduleNamespace?.createRequire === 'function') { const requireFromModule = moduleNamespace.createRequire(import.meta.url); if (typeof requireFromModule.resolve === 'function') {