From dd04fe0fcae74f3541e6b034ce1e3b2066764969 Mon Sep 17 00:00:00 2001 From: "Panche I." Date: Thu, 18 Dec 2025 21:43:55 +0100 Subject: [PATCH 1/4] Add GitHub link to footers --- src/components/layout/Footer/Footer.tsx | 20 +++++++++++++++---- .../layout/PublicFooter/PublicFooter.tsx | 9 +++++++++ src/lib/utils/constants.ts | 1 + 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/components/layout/Footer/Footer.tsx b/src/components/layout/Footer/Footer.tsx index 49284b6..d36cab6 100644 --- a/src/components/layout/Footer/Footer.tsx +++ b/src/components/layout/Footer/Footer.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { ROUTES } from '@/lib/utils/constants'; +import { ROUTES, EXTERNAL_LINKS } from '@/lib/utils/constants'; import styles from './Footer.module.css'; const footerLinks = [ @@ -26,6 +26,12 @@ const footerLinks = [ { href: '#', label: 'Terms' }, ], }, + { + title: 'Open Source', + links: [ + { href: EXTERNAL_LINKS.GITHUB, label: 'GitHub', external: true }, + ], + }, ]; export function Footer() { @@ -46,9 +52,15 @@ export function Footer() { diff --git a/src/components/layout/PublicFooter/PublicFooter.tsx b/src/components/layout/PublicFooter/PublicFooter.tsx index 54cbe89..4725871 100644 --- a/src/components/layout/PublicFooter/PublicFooter.tsx +++ b/src/components/layout/PublicFooter/PublicFooter.tsx @@ -22,6 +22,12 @@ const TelegramIcon = () => ( ); +const GitHubIcon = () => ( + + + +); + export function PublicFooter() { const currentYear = new Date().getFullYear(); @@ -60,6 +66,9 @@ export function PublicFooter() { Telegram + + GitHub + diff --git a/src/lib/utils/constants.ts b/src/lib/utils/constants.ts index beeb3a6..b0c7641 100644 --- a/src/lib/utils/constants.ts +++ b/src/lib/utils/constants.ts @@ -29,6 +29,7 @@ export const EXTERNAL_LINKS = { TWITTER: 'https://x.com/agentokratia', DISCORD: 'https://discord.gg/agentokratia', TELEGRAM: 'https://t.me/agentokratia', + GITHUB: 'https://github.com/Agentokratia/agentokratia', } as const; export const API_CATEGORIES = [ From 725d1c36e6f7a280a2938693929cccbb4ec0ee6c Mon Sep 17 00:00:00 2001 From: "Panche I." Date: Thu, 18 Dec 2025 21:58:03 +0100 Subject: [PATCH 2/4] Change deploy workflow to manual trigger --- .github/workflows/deploy.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b654e52..591f756 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,10 +1,16 @@ name: Deploy to Vercel on: - push: - branches: [main] - pull_request: - branches: [main] + workflow_dispatch: + inputs: + environment: + description: 'Deploy environment' + required: true + default: 'production' + type: choice + options: + - production + - preview env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} @@ -31,10 +37,10 @@ jobs: - name: Build run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Preview (PR) - if: github.event_name == 'pull_request' + - name: Deploy Preview + if: ${{ inputs.environment == 'preview' }} run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Production (main) - if: github.ref == 'refs/heads/main' && github.event_name == 'push' + - name: Deploy Production + if: ${{ inputs.environment == 'production' }} run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} From bf246b14a189e9ff4685fc5ed4ba81aa671aed1c Mon Sep 17 00:00:00 2001 From: "Panche I." Date: Thu, 18 Dec 2025 22:04:33 +0100 Subject: [PATCH 3/4] chore: add prettier, husky, lint-staged, and commitlint --- .husky/commit-msg | 1 + .husky/pre-commit | 1 + .prettierignore | 9 + .prettierrc | 10 + CONTRIBUTING.md | 26 +- FRONTEND_ARCHITECTURE.md | 289 ++-- commitlint.config.js | 25 + docs/REVIEW_SYSTEM_DESIGN.md | 31 +- package-lock.json | 1458 ++++++++++++++++- package.json | 19 +- public/suppress-extension-errors.js | 34 +- src/app/[handle]/[slug]/page.module.css | 4 +- src/app/[handle]/[slug]/page.tsx | 172 +- .../api/agents/[id]/agentcard.json/route.ts | 10 +- src/app/api/agents/[id]/claim/route.ts | 48 +- .../api/agents/[id]/publish/confirm/route.ts | 45 +- src/app/api/agents/[id]/publish/route.ts | 10 +- .../agents/[id]/reviews/[reviewId]/route.ts | 5 +- .../api/agents/[id]/reviews/confirm/route.ts | 42 +- src/app/api/agents/[id]/reviews/route.ts | 53 +- src/app/api/agents/[id]/route.ts | 96 +- src/app/api/agents/[id]/secret/route.ts | 35 +- src/app/api/agents/dashboard/route.ts | 39 +- src/app/api/agents/route.ts | 25 +- src/app/api/agents/test-endpoint/route.ts | 15 +- src/app/api/auth/me/route.ts | 25 +- src/app/api/auth/nonce/route.ts | 20 +- src/app/api/auth/verify/route.ts | 46 +- src/app/api/creator/[handle]/route.ts | 15 +- src/app/api/feedback/route.ts | 20 +- .../[handle]/[slug]/reviews/route.ts | 69 +- .../api/marketplace/[handle]/[slug]/route.ts | 27 +- src/app/api/marketplace/route.ts | 18 +- src/app/api/network/route.ts | 7 +- src/app/api/payments/route.ts | 19 +- src/app/api/reviews/[id]/route.ts | 15 +- src/app/api/users/profile/route.ts | 45 +- src/app/api/v1/call/[handle]/[slug]/route.ts | 366 ++++- src/app/creator/[handle]/page.module.css | 4 +- src/app/creator/[handle]/page.tsx | 39 +- src/app/dashboard/agents/[id]/page.module.css | 44 +- src/app/dashboard/agents/[id]/page.tsx | 112 +- .../agents/[id]/tabs/ConnectionTab.tsx | 226 ++- .../dashboard/agents/[id]/tabs/PricingTab.tsx | 75 +- .../dashboard/agents/[id]/tabs/ReadmeTab.tsx | 4 +- .../agents/[id]/tabs/ReviewsTab.module.css | 8 +- .../dashboard/agents/[id]/tabs/ReviewsTab.tsx | 42 +- .../agents/[id]/tabs/SecurityTab.tsx | 461 ++++-- .../agents/[id]/tabs/tabs.module.css | 5 +- src/app/dashboard/agents/new/page.module.css | 8 +- src/app/dashboard/agents/new/page.tsx | 3 +- src/app/dashboard/agents/page.module.css | 17 +- src/app/dashboard/agents/page.tsx | 36 +- src/app/dashboard/layout.module.css | 4 +- src/app/dashboard/layout.tsx | 9 +- src/app/dashboard/page.module.css | 8 +- src/app/dashboard/page.tsx | 36 +- src/app/dashboard/payments/page.module.css | 8 +- src/app/dashboard/payments/page.tsx | 26 +- src/app/dashboard/settings/page.tsx | 21 +- src/app/global-error.tsx | 72 +- src/app/globals.css | 176 +- src/app/layout.tsx | 11 +- src/app/marketplace/page.tsx | 26 +- src/app/not-found.module.css | 4 +- src/app/not-found.tsx | 15 +- src/app/page.module.css | 18 +- src/app/page.tsx | 139 +- .../EnableReviewsModal.module.css | 8 +- .../EnableReviewsModal/EnableReviewsModal.tsx | 28 +- .../PublishModal/PublishModal.module.css | 17 +- .../agents/PublishModal/PublishModal.tsx | 76 +- .../TestPlayground/TestPlayground.tsx | 151 +- src/components/layout/Footer/Footer.tsx | 15 +- .../layout/PublicFooter/PublicFooter.tsx | 44 +- .../layout/Sidebar/Sidebar.module.css | 7 +- src/components/layout/Sidebar/Sidebar.tsx | 5 +- .../ApiPlayground/ApiPlayground.tsx | 153 +- .../marketplace/ReviewForm/ReviewForm.tsx | 31 +- .../ReviewsList/ReviewsList.module.css | 16 +- .../marketplace/ReviewsList/ReviewsList.tsx | 39 +- src/components/providers/Providers.tsx | 5 +- src/components/ui/Badge/Badge.tsx | 7 +- src/components/ui/Button/Button.tsx | 7 +- src/components/ui/Card/Card.tsx | 7 +- src/components/ui/Card/index.ts | 8 +- src/components/ui/FeedbackWidget.module.css | 4 +- src/components/ui/FeedbackWidget.tsx | 2 +- src/components/ui/Input/Input.module.css | 4 +- src/components/ui/Input/Input.tsx | 20 +- src/components/ui/Modal/Modal.module.css | 4 +- src/components/ui/Modal/Modal.tsx | 4 +- src/components/ui/index.ts | 8 +- src/lib/api/client.ts | 5 +- src/lib/auth/jwt.ts | 8 +- src/lib/auth/siwe.ts | 6 +- src/lib/db/supabase.ts | 56 +- src/lib/erc8004/hooks.ts | 69 +- src/lib/network/client.ts | 6 +- src/lib/network/index.ts | 2 +- src/lib/ownership/index.ts | 12 +- src/lib/store/useUiStore.ts | 3 +- src/lib/utils/constants.ts | 8 +- src/lib/utils/retry.ts | 2 +- src/lib/utils/slugify.ts | 13 +- src/lib/utils/syntax.ts | 24 +- src/lib/validations/schemas.ts | 72 +- src/lib/x402/README.md | 22 +- src/lib/x402/client.ts | 2 +- src/lib/x402/facilitator.ts | 32 +- src/lib/x402/useAgentCall.ts | 86 +- src/lib/x402/usePaymentSigner.ts | 63 +- tsconfig.json | 14 +- 113 files changed, 4221 insertions(+), 1805 deletions(-) create mode 100644 .husky/commit-msg create mode 100644 .husky/pre-commit create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 commitlint.config.js diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..0a4b97d --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..36a9487 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +node_modules +.next +out +build +dist +coverage +.vercel +*.min.js +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b6b0fde --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100, + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb65191..b0b28e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,17 +32,37 @@ Before submitting a bug report: ``` 3. Make your changes following our coding standards 4. Write or update tests if applicable -5. Run linting and type checking: +5. Run linting, formatting, and type checking: ```bash npm run lint + npm run format npm run type-check ``` -6. Commit with clear, descriptive messages +6. Commit with conventional commit messages (pre-commit hooks will lint and format) 7. Push and open a pull request ### Commit Messages -Use clear, descriptive commit messages: +We use [Conventional Commits](https://www.conventionalcommits.org/). Commits are validated by commitlint. + +**Format:** `type: description` + +**Types:** +| Type | Description | +|------|-------------| +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation only | +| `style` | Formatting, no code change | +| `refactor` | Code change without fix or feature | +| `perf` | Performance improvement | +| `test` | Adding tests | +| `chore` | Maintenance | +| `ci` | CI/CD changes | +| `build` | Build system changes | +| `revert` | Revert previous commit | + +**Examples:** - `feat: add user profile page` - `fix: resolve wallet connection issue on mobile` diff --git a/FRONTEND_ARCHITECTURE.md b/FRONTEND_ARCHITECTURE.md index 5729412..83468fc 100644 --- a/FRONTEND_ARCHITECTURE.md +++ b/FRONTEND_ARCHITECTURE.md @@ -3,6 +3,7 @@ > Reference document for frontend implementation. All agents and developers should follow these conventions. ## Table of Contents + 1. [Overview](#overview) 2. [Tech Stack](#tech-stack) 3. [Folder Structure](#folder-structure) @@ -25,6 +26,7 @@ Agentokratia is a marketplace for AI agents to discover, purchase, and consume A 2. **API Consumers** (AI Agents/Developers) - Discover and pay for API calls ### Core Principles + - **Simplicity over complexity** - MVP-focused, avoid over-engineering - **Existing design system** - Reuse styles from `public/styles.css` - **Headless components** - Use Radix UI only for complex interactions @@ -36,42 +38,48 @@ Agentokratia is a marketplace for AI agents to discover, purchase, and consume A ## Tech Stack ### Core -| Technology | Version | Purpose | -|------------|---------|---------| -| Next.js | 14.x | Framework (App Router) | -| TypeScript | 5.x | Type safety | -| React | 18.x | UI library | + +| Technology | Version | Purpose | +| ---------- | ------- | ---------------------- | +| Next.js | 14.x | Framework (App Router) | +| TypeScript | 5.x | Type safety | +| React | 18.x | UI library | ### Styling -| Technology | Purpose | -|------------|---------| -| CSS Modules | Component-scoped styles | + +| Technology | Purpose | +| ------------- | ------------------------------------ | +| CSS Modules | Component-scoped styles | | CSS Variables | Design tokens (from existing system) | ### State Management -| Technology | Purpose | -|------------|---------| -| Zustand | Global client state (wallet, user, UI) | -| TanStack Query | Server state, caching, mutations | + +| Technology | Purpose | +| -------------- | -------------------------------------- | +| Zustand | Global client state (wallet, user, UI) | +| TanStack Query | Server state, caching, mutations | ### Web3 -| Technology | Version | Purpose | -|------------|---------|---------| -| wagmi | 2.x | React hooks for Ethereum | -| viem | 2.x | TypeScript Ethereum library | -| @rainbow-me/rainbowkit | 2.x | Wallet connection UI | + +| Technology | Version | Purpose | +| ---------------------- | ------- | --------------------------- | +| wagmi | 2.x | React hooks for Ethereum | +| viem | 2.x | TypeScript Ethereum library | +| @rainbow-me/rainbowkit | 2.x | Wallet connection UI | ### UI Components -| Technology | Purpose | -|------------|---------| -| Radix UI | Headless primitives (Dialog, Dropdown, Toast) | -| Lucide React | Icons | + +| Technology | Purpose | +| ------------ | --------------------------------------------- | +| Radix UI | Headless primitives (Dialog, Dropdown, Toast) | +| Lucide React | Icons | ### Development -| Technology | Purpose | -|------------|---------| -| ESLint | Code linting | -| Prettier | Code formatting | + +| Technology | Purpose | +| ---------- | --------------- | +| ESLint | Code linting | +| Prettier | Code formatting | --- @@ -210,21 +218,21 @@ Port these to `globals.css`: ```css :root { /* Core Colors */ - --ink: #1A1A1A; - --stone: #6B6B6B; - --sand: #E5E5E2; - --cloud: #F2F2EF; - --paper: #FAFAF8; - --white: #FFFFFF; + --ink: #1a1a1a; + --stone: #6b6b6b; + --sand: #e5e5e2; + --cloud: #f2f2ef; + --paper: #fafaf8; + --white: #ffffff; /* Accent Colors */ - --success: #22C55E; + --success: #22c55e; --success-light: rgba(34, 197, 94, 0.1); - --error: #EF4444; + --error: #ef4444; --error-light: rgba(239, 68, 68, 0.1); - --warning: #F59E0B; + --warning: #f59e0b; --warning-light: rgba(245, 158, 11, 0.1); - --info: #3B82F6; + --info: #3b82f6; --info-light: rgba(59, 130, 246, 0.1); /* Typography */ @@ -251,20 +259,21 @@ Port these to `globals.css`: ### Typography Scale -| Element | Font | Size | Weight | Line Height | -|---------|------|------|--------|-------------| -| h1 | Newsreader | 48px | 400 | 1.2 | -| h2 | Newsreader | 32px | 400 | 1.3 | -| h3 | Newsreader | 24px | 400 | 1.4 | -| h4 | DM Sans | 18px | 600 | 1.4 | -| body | DM Sans | 16px | 400 | 1.6 | -| small | DM Sans | 14px | 400 | 1.5 | -| caption | DM Sans | 12px | 400 | 1.4 | -| code | Space Mono | 13px | 400 | 1.5 | +| Element | Font | Size | Weight | Line Height | +| ------- | ---------- | ---- | ------ | ----------- | +| h1 | Newsreader | 48px | 400 | 1.2 | +| h2 | Newsreader | 32px | 400 | 1.3 | +| h3 | Newsreader | 24px | 400 | 1.4 | +| h4 | DM Sans | 18px | 600 | 1.4 | +| body | DM Sans | 16px | 400 | 1.6 | +| small | DM Sans | 14px | 400 | 1.5 | +| caption | DM Sans | 12px | 400 | 1.4 | +| code | Space Mono | 13px | 400 | 1.5 | ### Component Variants #### Buttons + - `btn-primary` - Black background, white text (primary actions) - `btn-secondary` - White background, border (secondary actions) - `btn-success` - Green background (confirm/success actions) @@ -272,12 +281,14 @@ Port these to `globals.css`: - Sizes: `btn-sm`, default, `btn-lg` #### Badges + - `badge-success` - Green (active, online, verified) - `badge-warning` - Orange (pending, beta) - `badge-error` - Red (error, failed) - `badge-info` - Blue (info, new) #### Cards + - Default card with white background, sand border, radius-lg - Card header with flex layout for title + actions @@ -379,6 +390,7 @@ export function Modal({ open, onOpenChange, title, children }: ModalProps) { ### Zustand Stores #### Auth Store + ```tsx // lib/store/useAuthStore.ts import { create } from 'zustand'; @@ -409,6 +421,7 @@ export const useAuthStore = create()( ``` #### UI Store + ```tsx // lib/store/useUiStore.ts import { create } from 'zustand'; @@ -670,22 +683,24 @@ export function useWalletAuth() { ## Pages & Routing ### Public Pages -| Route | Page | Description | -|-------|------|-------------| -| `/` | Landing | Marketing page, hero, features | -| `/marketplace` | Browse | Search and filter APIs | -| `/marketplace/[id]` | Detail | API details, pricing, docs | + +| Route | Page | Description | +| ------------------- | ------- | ------------------------------ | +| `/` | Landing | Marketing page, hero, features | +| `/marketplace` | Browse | Search and filter APIs | +| `/marketplace/[id]` | Detail | API details, pricing, docs | ### Protected Pages (require wallet) -| Route | Page | Description | -|-------|------|-------------| -| `/dashboard` | Overview | Stats, recent activity | -| `/dashboard/apis` | My APIs | List of user's APIs | -| `/dashboard/apis/new` | Create API | API creation form | -| `/dashboard/apis/[id]/edit` | Edit API | Edit existing API | -| `/dashboard/usage` | Usage | API usage analytics | -| `/dashboard/earnings` | Earnings | Revenue, payouts | -| `/dashboard/settings` | Settings | Account settings | + +| Route | Page | Description | +| --------------------------- | ---------- | ---------------------- | +| `/dashboard` | Overview | Stats, recent activity | +| `/dashboard/apis` | My APIs | List of user's APIs | +| `/dashboard/apis/new` | Create API | API creation form | +| `/dashboard/apis/[id]/edit` | Edit API | Edit existing API | +| `/dashboard/usage` | Usage | API usage analytics | +| `/dashboard/earnings` | Earnings | Revenue, payouts | +| `/dashboard/settings` | Settings | Account settings | ### Route Protection @@ -767,18 +782,21 @@ export function AuthGuard({ children }: { children: React.ReactNode }) { ## Coding Conventions ### TypeScript + - Enable strict mode - Explicit return types on functions - Use interfaces for object shapes, types for unions - No `any` - use `unknown` if type is truly unknown ### React + - Functional components only - Use `forwardRef` for components accepting refs - Destructure props in function signature - Use early returns for conditional rendering ### Naming + - **Components**: PascalCase (`Button.tsx`, `ApiCard.tsx`) - **Hooks**: camelCase with `use` prefix (`useApi.ts`) - **Utilities**: camelCase (`formatCurrency.ts`) @@ -786,18 +804,21 @@ export function AuthGuard({ children }: { children: React.ReactNode }) { - **Constants**: SCREAMING_SNAKE_CASE ### File Organization + - One component per file - Co-locate styles with components - Index files for re-exports only - Group by feature in `components/features/` ### CSS + - Use CSS variables for all colors, spacing, fonts - Mobile-first responsive design - Use CSS Modules for component styles - Global styles only in `globals.css` ### Imports Order + ```tsx // 1. React import { useState, useEffect } from 'react'; @@ -826,27 +847,32 @@ import styles from './Component.module.css'; ## MVP Implementation Order ### Phase 1: Foundation + 1. Project setup (Next.js, TypeScript, dependencies) 2. Design system (globals.css, base components) 3. Layout components (Navbar, Footer, Sidebar) 4. Web3 provider setup ### Phase 2: Public Pages + 5. Landing page 6. Marketplace browse page 7. API detail page ### Phase 3: Dashboard + 8. Dashboard layout with sidebar 9. Dashboard overview 10. My APIs list + create/edit forms ### Phase 4: Core Features + 11. Usage analytics page 12. Earnings page 13. Settings page ### Phase 5: Polish + 14. Loading states, error handling 15. Mobile responsiveness 16. Performance optimization @@ -858,11 +884,13 @@ import styles from './Component.module.css'; ### Overview The frontend requires a backend API for: + 1. **Off-chain agent metadata** (name, description, logo, docs) 2. **Usage analytics** (call logs, earnings tracking) 3. **Marketplace search** (indexed agent data) On-chain data (via smart contracts): + - Agent ownership (ERC-721 token) - Agent ID, price, payment address - Total calls, total earned (updated by x402 facilitator) @@ -870,6 +898,7 @@ On-chain data (via smart contracts): ### API Endpoints Required #### Authentication + ``` POST /api/auth/nonce → { nonce: string } @@ -884,6 +913,7 @@ GET /api/auth/me ``` #### Agents (Provider Dashboard) + ``` GET /api/agents Headers: Authorization: Bearer @@ -913,6 +943,7 @@ GET /api/agents/:id/stats ``` #### Marketplace (Public) + ``` GET /api/marketplace Query: ?category=&search=&sortBy=calls|rating|price|newest&offset=0&limit=20 @@ -926,6 +957,7 @@ GET /api/marketplace/categories ``` #### Payments & Earnings + ``` GET /api/earnings Headers: Authorization: Bearer @@ -943,6 +975,7 @@ POST /api/payouts ``` #### Call Logs (for analytics) + ``` GET /api/calls Headers: Authorization: Bearer @@ -960,94 +993,97 @@ POST /api/calls/log ```typescript interface Agent { - id: string // Internal DB ID - tokenId: number // On-chain ERC-721 token ID - owner: string // Wallet address - name: string - description: string - endpoint: string - pricePerCall: string // USDC amount (e.g., "0.05") - category: string - active: boolean - logoUrl?: string - documentationUrl?: string - apiSchema?: object // OpenAPI schema + id: string; // Internal DB ID + tokenId: number; // On-chain ERC-721 token ID + owner: string; // Wallet address + name: string; + description: string; + endpoint: string; + pricePerCall: string; // USDC amount (e.g., "0.05") + category: string; + active: boolean; + logoUrl?: string; + documentationUrl?: string; + apiSchema?: object; // OpenAPI schema stats: { - totalCalls: number - totalEarned: string // USDC amount - avgResponseTime: number // ms - rating?: number // 1-5 (post-MVP) - } - createdAt: string - updatedAt: string + totalCalls: number; + totalEarned: string; // USDC amount + avgResponseTime: number; // ms + rating?: number; // 1-5 (post-MVP) + }; + createdAt: string; + updatedAt: string; } interface MarketplaceAgent { - id: string - tokenId: number - name: string - description: string - pricePerCall: string - category: string - owner: string - logoUrl?: string + id: string; + tokenId: number; + name: string; + description: string; + pricePerCall: string; + category: string; + owner: string; + logoUrl?: string; stats: { - totalCalls: number - rating?: number - } + totalCalls: number; + rating?: number; + }; } interface CallLog { - id: string - agentId: string - callerAddress: string - amount: string - txHash: string - responseTimeMs: number - success: boolean - createdAt: string + id: string; + agentId: string; + callerAddress: string; + amount: string; + txHash: string; + responseTimeMs: number; + success: boolean; + createdAt: string; } interface Earnings { - total: string // Total earned (USDC) - pending: string // Pending payout - paid: string // Already paid out + total: string; // Total earned (USDC) + pending: string; // Pending payout + paid: string; // Already paid out history: { - date: string - amount: string - type: 'earning' | 'payout' - txHash?: string - }[] + date: string; + amount: string; + type: 'earning' | 'payout'; + txHash?: string; + }[]; } ``` ### Data Storage Strategy (Hybrid) -| Data | Storage | Reason | -|------|---------|--------| -| Agent ownership | On-chain (ERC-721) | Proof of ownership | -| Agent ID | On-chain | Unique identifier | -| Price per call | On-chain | x402 reads this | -| Payment address | On-chain | x402 sends payments here | -| Name, description | Off-chain (DB) | Frequently updated, searchable | -| Logo, docs URL | Off-chain (DB) | Large data | -| API schema | Off-chain (DB) | Complex object | -| Call logs | Off-chain (DB) | High volume, analytics | -| Total calls/earned | Both | On-chain = source of truth, DB = cache | +| Data | Storage | Reason | +| ------------------ | ------------------ | -------------------------------------- | +| Agent ownership | On-chain (ERC-721) | Proof of ownership | +| Agent ID | On-chain | Unique identifier | +| Price per call | On-chain | x402 reads this | +| Payment address | On-chain | x402 sends payments here | +| Name, description | Off-chain (DB) | Frequently updated, searchable | +| Logo, docs URL | Off-chain (DB) | Large data | +| API schema | Off-chain (DB) | Complex object | +| Call logs | Off-chain (DB) | High volume, analytics | +| Total calls/earned | Both | On-chain = source of truth, DB = cache | ### Backend Tech Stack Options **Option A: Next.js API Routes (Simplest)** + - Use existing Next.js app - API routes in `/app/api/` - Good for MVP, may need to split later **Option B: Separate Express/Fastify Backend** + - Dedicated API server - Better separation of concerns - Required if x402 middleware needs to call back **Option C: Supabase + Edge Functions** + - Managed PostgreSQL - Built-in auth (can integrate with wallet) - Edge functions for custom logic @@ -1060,22 +1096,22 @@ For data that lives on-chain, use viem directly: ```typescript // lib/contracts/registry.ts -import { createPublicClient, http } from 'viem' -import { base } from 'viem/chains' -import { REGISTRY_ABI, REGISTRY_ADDRESS } from './abi' +import { createPublicClient, http } from 'viem'; +import { base } from 'viem/chains'; +import { REGISTRY_ABI, REGISTRY_ADDRESS } from './abi'; const client = createPublicClient({ chain: base, - transport: http() -}) + transport: http(), +}); export async function getAgentOnChain(tokenId: number) { return client.readContract({ address: REGISTRY_ADDRESS, abi: REGISTRY_ABI, functionName: 'agents', - args: [BigInt(tokenId)] - }) + args: [BigInt(tokenId)], + }); } export async function getAgentsByOwner(ownerAddress: string) { @@ -1083,24 +1119,27 @@ export async function getAgentsByOwner(ownerAddress: string) { address: REGISTRY_ADDRESS, abi: REGISTRY_ABI, functionName: 'getAgentsByOwner', - args: [ownerAddress as `0x${string}`] - }) + args: [ownerAddress as `0x${string}`], + }); } ``` ### Syncing On-Chain & Off-Chain Data **Option 1: Event Indexing (Recommended)** + - Listen to `AgentRegistered`, `AgentUpdated`, `CallRecorded` events - Update DB when events occur - Use services like Alchemy, The Graph, or custom indexer **Option 2: On-Demand Reads** + - Read from chain when needed - Cache in DB - Simpler but slower **Option 3: Hybrid** + - Read from DB for list views (fast) - Read from chain for detail views (accurate) - Sync periodically diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..f44012b --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,25 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', // New feature + 'fix', // Bug fix + 'docs', // Documentation + 'style', // Formatting, no code change + 'refactor', // Code change without fix or feature + 'perf', // Performance improvement + 'test', // Adding tests + 'chore', // Maintenance + 'ci', // CI/CD changes + 'build', // Build system changes + 'revert', // Revert previous commit + ], + ], + 'subject-case': [2, 'always', 'lower-case'], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + }, +}; diff --git a/docs/REVIEW_SYSTEM_DESIGN.md b/docs/REVIEW_SYSTEM_DESIGN.md index 3cd79dc..ef87079 100644 --- a/docs/REVIEW_SYSTEM_DESIGN.md +++ b/docs/REVIEW_SYSTEM_DESIGN.md @@ -10,10 +10,10 @@ The review system uses the **existing on-chain IReputationRegistry** contract fo ## Deployed Contracts -| Network | Chain ID | Identity Registry | Reputation Registry | -|---------|----------|-------------------|---------------------| -| Base Sepolia | 84532 | `0xYourIdentityRegistry` | `0xB5048e3ef1DA4E04deB6f7d0423D06F63869e322` | -| Base Mainnet | 8453 | TBD | TBD | +| Network | Chain ID | Identity Registry | Reputation Registry | +| ------------ | -------- | ------------------------ | -------------------------------------------- | +| Base Sepolia | 84532 | `0xYourIdentityRegistry` | `0xB5048e3ef1DA4E04deB6f7d0423D06F63869e322` | +| Base Mainnet | 8453 | TBD | TBD | --- @@ -505,7 +505,7 @@ GROUP BY agent_id; ## Score Mapping | Stars (UI) | Score (On-Chain) | -|------------|------------------| +| ---------- | ---------------- | | 1 star | 0-20 | | 2 stars | 21-40 | | 3 stars | 41-60 | @@ -519,9 +519,11 @@ GROUP BY agent_id; ### Agent Publish Flow #### 1. Generate Feedback Signer + ``` POST /api/agents/[id]/publish ``` + **Generates** a new keypair and stores it encrypted. Returns the signer address. ```json @@ -532,15 +534,18 @@ POST /api/agents/[id]/publish ``` #### 2. Confirm Operator Setup + ``` POST /api/agents/[id]/publish/confirm-operator Body: { "txHash": "0x..." } ``` + **Called after** owner submits setOperator() transaction. ### Review Submission Flow #### 1. Submit Review + ``` POST /api/marketplace/[id]/reviews Body: { @@ -554,6 +559,7 @@ Body: { ``` #### 2. Confirm On-Chain + ``` POST /api/marketplace/[id]/reviews/[reviewId]/confirm Body: { "txHash": "0x...", "feedbackIndex": 1 } @@ -562,11 +568,13 @@ Body: { "txHash": "0x...", "feedbackIndex": 1 } ### Read Reviews #### Get Agent Reviews + ``` GET /api/marketplace/[id]/reviews?page=1&limit=10&sort=recent ``` #### Get Review Content (fileuri) + ``` GET /api/reviews/[reviewId] ``` @@ -680,14 +688,14 @@ async function signFeedbackAuth( data.expiry, data.chainId, data.identityRegistry, - data.signerAddress + data.signerAddress, ] ) ); // Sign the hash const signature = await signer.signMessage({ - message: { raw: messageHash } + message: { raw: messageHash }, }); // Encode full feedbackAuth (data + signature) @@ -700,7 +708,7 @@ async function signFeedbackAuth( { name: 'chainId', type: 'uint256' }, { name: 'identityRegistry', type: 'address' }, { name: 'signerAddress', type: 'address' }, - { name: 'signature', type: 'bytes' } + { name: 'signature', type: 'bytes' }, ], [ data.agentId, @@ -710,7 +718,7 @@ async function signFeedbackAuth( data.chainId, data.identityRegistry, data.signerAddress, - signature + signature, ] ) as `0x${string}`; } @@ -721,6 +729,7 @@ async function signFeedbackAuth( ## Implementation Checklist ### Phase 1: Database & API Foundation + - [x] Add feedback signer columns to agents table (`20241216000001_agent_feedback_signer.sql`) - [x] Create agent_reviews table (`20241216000000_agent_reviews.sql`) - [x] Create feedback_auth_tokens table @@ -728,24 +737,28 @@ async function signFeedbackAuth( - [x] Add review types to supabase.ts ### Phase 2: Publish Flow Integration (Simplified - Same 2 API Calls) + - [x] Update /api/agents/[id]/publish to generate keypair (returns `feedbackSignerAddress`) - [x] Update /api/agents/[id]/publish/confirm to accept optional `operatorTxHash` - [ ] Add SetOperator UI step in publish modal (after ERC-8004 registration) - [ ] Store operator tx confirmation in confirm endpoint ### Phase 3: Payment Flow Integration + - [x] Add signFeedbackAuth utility function (`src/lib/erc8004/feedbackAuth.ts`) - [x] Update payment proxy (`/api/v1/call/[agentId]`) to generate feedbackAuth - [x] Add X-Feedback-Auth and X-Feedback-Expires headers to payment responses - [x] Store feedbackAuth tokens in database ### Phase 4: Review Submission + - [x] POST /api/marketplace/[id]/reviews endpoint (validates feedbackAuth) - [ ] Create POST /api/marketplace/[id]/reviews/[reviewId]/confirm for on-chain tx - [ ] Add ReviewForm component - [ ] Integrate on-chain giveFeedback() call in frontend ### Phase 5: Review Display + - [x] Update marketplace detail page reviews tab (using real data) - [ ] Add individual review display with on-chain verification badge - [ ] Add owner response display diff --git a/package-lock.json b/package-lock.json index 1a93603..13b2276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "agentokratia-frontend", "version": "0.1.0", + "license": "AGPL-3.0-or-later", "dependencies": { "@coinbase/x402": "^2.0.0", "@radix-ui/react-dialog": "^1.1.1", @@ -32,12 +33,17 @@ "zustand": "^4.5.5" }, "devDependencies": { + "@commitlint/cli": "^20.2.0", + "@commitlint/config-conventional": "^20.2.0", "@types/node": "^20.16.10", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "eslint": "^9.0.0", "eslint-config-next": "^16.0.8", + "husky": "^9.1.7", + "lint-staged": "^16.2.7", "null-loader": "^4.0.1", + "prettier": "^3.7.4", "supabase": "^2.65.8", "typescript": "^5.6.2" } @@ -663,6 +669,519 @@ "zod": "^3.24.2" } }, + "node_modules/@commitlint/cli": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.2.0.tgz", + "integrity": "sha512-l37HkrPZ2DZy26rKiTUvdq/LZtlMcxz+PeLv9dzK9NzoFGuJdOQyYU7IEkEQj0pO++uYue89wzOpZ0hcTtoqUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^20.2.0", + "@commitlint/lint": "^20.2.0", + "@commitlint/load": "^20.2.0", + "@commitlint/read": "^20.2.0", + "@commitlint/types": "^20.2.0", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@commitlint/cli/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@commitlint/cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@commitlint/cli/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@commitlint/cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@commitlint/cli/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.2.0.tgz", + "integrity": "sha512-MsRac+yNIbTB4Q/psstKK4/ciVzACHicSwz+04Sxve+4DW+PiJeTjU0JnS4m/oOnulrXYN+yBPlKaBSGemRfgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.2.0", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.2.0.tgz", + "integrity": "sha512-SQCBGsL9MFk8utWNSthdxd9iOD1pIVZSHxGBwYIGfd67RTjxqzFOSAYeQVXOu3IxRC3YrTOH37ThnTLjUlyF2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.2.0", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@commitlint/ensure": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.2.0.tgz", + "integrity": "sha512-+8TgIGv89rOWyt3eC6lcR1H7hqChAKkpawytlq9P1i/HYugFRVqgoKJ8dhd89fMnlrQTLjA5E97/4sF09QwdoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", + "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.2.0.tgz", + "integrity": "sha512-PhNoLNhxpfIBlW/i90uZ3yG3hwSSYx7n4d9Yc+2FAorAHS0D9btYRK4ZZXX+Gm3W5tDtu911ow/eWRfcRVgNWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.2.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.2.0.tgz", + "integrity": "sha512-Lz0OGeZCo/QHUDLx5LmZc0EocwanneYJUM8z0bfWexArk62HKMLfLIodwXuKTO5y0s6ddXaTexrYHs7v96EOmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.2.0", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/lint": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.2.0.tgz", + "integrity": "sha512-cQEEB+jlmyQbyiji/kmh8pUJSDeUmPiWq23kFV0EtW3eM+uAaMLMuoTMajbrtWYWQpPzOMDjYltQ8jxHeHgITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^20.2.0", + "@commitlint/parse": "^20.2.0", + "@commitlint/rules": "^20.2.0", + "@commitlint/types": "^20.2.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.2.0.tgz", + "integrity": "sha512-iAK2GaBM8sPFTSwtagI67HrLKHIUxQc2BgpgNc/UMNme6LfmtHpIxQoN1TbP+X1iz58jq32HL1GbrFTCzcMi6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^20.2.0", + "@commitlint/execute-rule": "^20.0.0", + "@commitlint/resolve-extends": "^20.2.0", + "@commitlint/types": "^20.2.0", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/message": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.0.0.tgz", + "integrity": "sha512-gLX4YmKnZqSwkmSB9OckQUrI5VyXEYiv3J5JKZRxIp8jOQsWjZgHSG/OgEfMQBK9ibdclEdAyIPYggwXoFGXjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.2.0.tgz", + "integrity": "sha512-LXStagGU1ivh07X7sM+hnEr4BvzFYn1iBJ6DRg2QsIN8lBfSzyvkUcVCDwok9Ia4PWiEgei5HQjju6xfJ1YaSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.2.0", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.2.0.tgz", + "integrity": "sha512-+SjF9mxm5JCbe+8grOpXCXMMRzAnE0WWijhhtasdrpJoAFJYd5UgRTj/oCq5W3HJTwbvTOsijEJ0SUGImECD7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^20.0.0", + "@commitlint/types": "^20.2.0", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.2.0.tgz", + "integrity": "sha512-KVoLDi9BEuqeq+G0wRABn4azLRiCC22/YHR2aCquwx6bzCHAIN8hMt3Nuf1VFxq/c8ai6s8qBxE8+ZD4HeFTlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^20.2.0", + "@commitlint/types": "^20.2.0", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/rules": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.2.0.tgz", + "integrity": "sha512-27rHGpeAjnYl/A+qUUiYDa7Yn1WIjof/dFJjYW4gA1Ug+LUGa1P0AexzGZ5NBxTbAlmDgaxSZkLLxtLVqtg8PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^20.2.0", + "@commitlint/message": "^20.0.0", + "@commitlint/to-lines": "^20.0.0", + "@commitlint/types": "^20.2.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", + "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.0.0.tgz", + "integrity": "sha512-drXaPSP2EcopukrUXvUXmsQMu3Ey/FuJDc/5oiW4heoCfoE5BdLQyuc7veGeE3aoQaTVqZnh4D5WTWe2vefYKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level/node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@commitlint/top-level/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/types": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.2.0.tgz", + "integrity": "sha512-KTy0OqRDLR5y/zZMnizyx09z/rPlPC/zKhYgH8o/q6PuAjoQAKlRfY4zzv0M64yybQ//6//4H1n14pxaLZfUnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@ecies/ciphers": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.5.tgz", @@ -5662,6 +6181,16 @@ "@types/node": "*" } }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.2.tgz", + "integrity": "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -8112,6 +8641,22 @@ "ajv": "^6.9.1" } }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -8213,6 +8758,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, "node_modules/array-includes": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", @@ -8901,6 +9453,39 @@ "node": ">=6.0" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -8989,6 +9574,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -9020,6 +9612,17 @@ "node": ">=20" } }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -9027,6 +9630,51 @@ "dev": true, "license": "MIT" }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -9040,11 +9688,56 @@ "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", "license": "MIT" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "^2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } }, "node_modules/crc-32": { "version": "1.2.2", @@ -9162,6 +9855,19 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -9459,6 +10165,19 @@ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "license": "MIT" }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -9622,6 +10341,39 @@ "node": ">=10.13.0" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -10704,8 +11456,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fastestsmallesttextencoderdecoder": { "version": "1.0.22", @@ -10971,6 +11722,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -11048,6 +11812,24 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -11069,6 +11851,22 @@ "license": "BSD-2-Clause", "peer": true }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -11325,6 +12123,22 @@ "ms": "^2.0.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iceberg-js": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", @@ -11387,6 +12201,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -11403,6 +12228,16 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -11491,6 +12326,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -11763,6 +12605,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -11881,6 +12733,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -12089,6 +12954,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -12143,8 +13018,7 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-rpc-engine": { "version": "6.1.0", @@ -12204,6 +13078,33 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -12285,6 +13186,66 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.2", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/lit": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", @@ -12391,6 +13352,27 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12398,6 +13380,90 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -12757,6 +13823,19 @@ "@babel/runtime": "^7.12.5" } }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -13392,6 +14471,19 @@ "node": ">= 0.6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -13476,6 +14568,19 @@ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", "license": "(Apache-2.0 AND MIT)" }, + "node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -13890,6 +14995,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openapi-fetch": { "version": "0.13.8", "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.13.8.tgz", @@ -14083,6 +15204,25 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -14128,6 +15268,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -14250,6 +15403,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -14709,7 +15878,6 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -14761,6 +15929,23 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -14772,6 +15957,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rpc-websockets": { "version": "9.3.2", "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.2.tgz", @@ -15214,6 +16406,52 @@ "ethers": "^5.6.8 || ^6.0.8" } }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/socket.io-client": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", @@ -15405,6 +16643,62 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -15854,6 +17148,19 @@ "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/thread-stream": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz", @@ -15863,6 +17170,23 @@ "real-require": "^0.1.0" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -16164,6 +17488,19 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -16905,6 +18242,91 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -16976,6 +18398,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", diff --git a/package.json b/package.json index 4b32682..4adcccc 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,17 @@ "build": "next build", "start": "next start", "lint": "next lint", + "lint:fix": "next lint --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", "type-check": "tsc --noEmit", "db:start": "npx supabase start", "db:stop": "npx supabase stop", "db:reset": "npx supabase db reset", "db:status": "npx supabase status", "db:push": "npx supabase db push", - "db:migrate": "npx supabase migration up" + "db:migrate": "npx supabase migration up", + "prepare": "husky" }, "dependencies": { "@coinbase/x402": "^2.0.0", @@ -59,13 +63,26 @@ "zustand": "^4.5.5" }, "devDependencies": { + "@commitlint/cli": "^20.2.0", + "@commitlint/config-conventional": "^20.2.0", "@types/node": "^20.16.10", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "eslint": "^9.0.0", "eslint-config-next": "^16.0.8", + "husky": "^9.1.7", + "lint-staged": "^16.2.7", "null-loader": "^4.0.1", + "prettier": "^3.7.4", "supabase": "^2.65.8", "typescript": "^5.6.2" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "prettier --write" + ], + "*.{json,md,css}": [ + "prettier --write" + ] } } diff --git a/public/suppress-extension-errors.js b/public/suppress-extension-errors.js index 7f6f9d1..b06b599 100644 --- a/public/suppress-extension-errors.js +++ b/public/suppress-extension-errors.js @@ -1,7 +1,7 @@ // Suppress wallet extension errors -(function() { +(function () { var originalError = window.onerror; - window.onerror = function(message, source, lineno, colno, error) { + window.onerror = function (message, source, lineno, colno, error) { if ( (message && message.toString().includes('chrome.runtime.sendMessage')) || (message && message.toString().includes('Extension ID')) || @@ -16,20 +16,24 @@ return false; }; - window.addEventListener('error', function(event) { - if ( - (event.message && event.message.includes('chrome.runtime.sendMessage')) || - (event.message && event.message.includes('Extension ID')) || - (event.filename && event.filename.includes('chrome-extension')) || - (event.filename && event.filename.includes('inpage.js')) - ) { - event.preventDefault(); - event.stopPropagation(); - return true; - } - }, true); + window.addEventListener( + 'error', + function (event) { + if ( + (event.message && event.message.includes('chrome.runtime.sendMessage')) || + (event.message && event.message.includes('Extension ID')) || + (event.filename && event.filename.includes('chrome-extension')) || + (event.filename && event.filename.includes('inpage.js')) + ) { + event.preventDefault(); + event.stopPropagation(); + return true; + } + }, + true + ); - window.addEventListener('unhandledrejection', function(event) { + window.addEventListener('unhandledrejection', function (event) { var message = event.reason && (event.reason.message || event.reason.toString()); if ( message && diff --git a/src/app/[handle]/[slug]/page.module.css b/src/app/[handle]/[slug]/page.module.css index 7ff2537..043cb20 100644 --- a/src/app/[handle]/[slug]/page.module.css +++ b/src/app/[handle]/[slug]/page.module.css @@ -31,7 +31,9 @@ } @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } /* Two Column Container - Apify Style (Full Width) */ diff --git a/src/app/[handle]/[slug]/page.tsx b/src/app/[handle]/[slug]/page.tsx index e4e2902..5fc502c 100644 --- a/src/app/[handle]/[slug]/page.tsx +++ b/src/app/[handle]/[slug]/page.tsx @@ -2,7 +2,28 @@ import { useParams } from 'next/navigation'; import Link from 'next/link'; -import { ArrowLeft, Loader2, Shield, ShieldCheck, Star, X, Zap, Clock, Activity, AlertCircle, Play, Check, Copy, ExternalLink, Code, FileText, MessageSquare, Share2, User, Download } from 'lucide-react'; +import { + ArrowLeft, + Loader2, + Shield, + ShieldCheck, + Star, + X, + Zap, + Clock, + Activity, + AlertCircle, + Play, + Check, + Copy, + ExternalLink, + Code, + FileText, + MessageSquare, + Share2, + User, + Download, +} from 'lucide-react'; import { useState, useEffect, useCallback } from 'react'; import { useQuery } from '@tanstack/react-query'; import ReactMarkdown from 'react-markdown'; @@ -20,7 +41,7 @@ interface ReviewStats { avgScore: number; avgRating: number; reviewCount: number; - distribution: { 5: number; 4: number; 3: number; 2: number; 1: number; }; + distribution: { 5: number; 4: number; 3: number; 2: number; 1: number }; } interface MarketplaceAgentDetail { @@ -44,14 +65,17 @@ interface MarketplaceAgentDetail { erc8004TxHash: string | null; erc8004ChainId: number | null; reviewsEnabled: boolean; - stats?: { uptime: number; avgResponseMs: number; errorRate: number; }; + stats?: { uptime: number; avgResponseMs: number; errorRate: number }; reviewStats?: ReviewStats; } type CodeLang = 'js' | 'py' | 'curl'; type TabId = 'readme' | 'api' | 'reviews'; -async function fetchMarketplaceAgent(handle: string, slug: string): Promise { +async function fetchMarketplaceAgent( + handle: string, + slug: string +): Promise { const res = await fetch(`/api/marketplace/${handle}/${slug}`); if (!res.ok) { if (res.status === 404) throw new Error('Agent not found'); @@ -75,14 +99,18 @@ export default function AgentDetailPage() { const [copiedInput, setCopiedInput] = useState(false); const [copiedOutput, setCopiedOutput] = useState(false); - const { data: agent, isLoading, error, refetch: refetchAgent } = useQuery({ + const { + data: agent, + isLoading, + error, + refetch: refetchAgent, + } = useQuery({ queryKey: ['agent', handle, slug], queryFn: () => fetchMarketplaceAgent(handle, slug), enabled: !!handle && !!slug, staleTime: 30_000, }); - useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape' && isPlaygroundOpen) setIsPlaygroundOpen(false); @@ -93,7 +121,9 @@ export default function AgentDetailPage() { useEffect(() => { document.body.style.overflow = isPlaygroundOpen ? 'hidden' : ''; - return () => { document.body.style.overflow = ''; }; + return () => { + document.body.style.overflow = ''; + }; }, [isPlaygroundOpen]); const handleReviewSubmitted = useCallback(() => { @@ -154,22 +184,26 @@ export default function AgentDetailPage() { post: { summary: agent.name, description: agent.description || '', - requestBody: agent.inputSchema ? { - required: true, - content: { - 'application/json': { - schema: agent.inputSchema, - }, - }, - } : undefined, + requestBody: agent.inputSchema + ? { + required: true, + content: { + 'application/json': { + schema: agent.inputSchema, + }, + }, + } + : undefined, responses: { '200': { description: 'Successful response', - content: agent.outputSchema ? { - 'application/json': { - schema: agent.outputSchema, - }, - } : undefined, + content: agent.outputSchema + ? { + 'application/json': { + schema: agent.outputSchema, + }, + } + : undefined, }, '402': { description: 'Payment required - see x402 protocol', @@ -213,7 +247,9 @@ export default function AgentDetailPage() {

Agent Not Found

{error instanceof Error ? error.message : 'This agent does not exist.'}

- + + + @@ -255,13 +291,17 @@ curl -X POST "${endpoint}" \\ -d '{"your": "params"}' # Without a valid payment, you'll receive a 402 response -# with payment details in the "payment-required" header` +# with payment details in the "payment-required" header`, }; const tabs: { id: TabId; label: string; icon: React.ReactNode }[] = [ { id: 'readme', label: 'Readme', icon: }, { id: 'api', label: 'API', icon: }, - { id: 'reviews', label: `Reviews${reviewCount > 0 ? ` (${reviewCount})` : ''}`, icon: }, + { + id: 'reviews', + label: `Reviews${reviewCount > 0 ? ` (${reviewCount})` : ''}`, + icon: , + }, ]; return ( @@ -278,17 +318,11 @@ curl -X POST "${endpoint}" \\ Marketplace

{agent.name}

- {agent.description && ( -

{agent.description}

- )} + {agent.description &&

{agent.description}

} {/* Try Button */} - @@ -305,10 +339,7 @@ curl -X POST "${endpoint}" \\ {/* Author */}
Developer - +
@@ -327,7 +358,9 @@ curl -X POST "${endpoint}" \\ {reviewCount > 0 && (
- {avgRating.toFixed(1)} ({reviewCount} reviews) + + {avgRating.toFixed(1)} ({reviewCount} reviews) +
)} {agent.stats?.avgResponseMs && ( @@ -352,7 +385,10 @@ curl -X POST "${endpoint}" \\ {/* On-chain Verification */} {agent.erc8004TokenId ? ( Topics
{agent.tags.map((tag) => ( - {tag} + + {tag} + ))}
@@ -479,9 +517,17 @@ curl -X POST "${endpoint}" \\

Integrate in 2 minutes

    -
  1. Install an HTTP client with x402 support (see Node.js and Python examples below)
  2. +
  3. + Install an HTTP client with{' '} + + x402 + {' '} + support (see Node.js and Python examples below) +
  4. Add your wallet private key (needs USDC on Base)
  5. -
  6. Send a POST request to the endpoint below - you only pay when it succeeds
  7. +
  8. + Send a POST request to the endpoint below - you only pay when it succeeds +
@@ -512,7 +558,9 @@ curl -X POST "${endpoint}" \\ ))}
-
{fullCodeExamples[codeLang]}
+
+                        {fullCodeExamples[codeLang]}
+                      
@@ -520,9 +568,10 @@ curl -X POST "${endpoint}" \\ {codeLang !== 'curl' && (
@@ -564,7 +621,15 @@ curl -X POST "${endpoint}" \\
Response
@@ -599,8 +664,15 @@ curl -X POST "${endpoint}" \\
{([5, 4, 3, 2, 1] as const).map((r) => { - const dist = agent.reviewStats?.distribution ?? { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }; - const pct = reviewCount === 0 ? 0 : Math.round((dist[r] / reviewCount) * 100); + const dist = agent.reviewStats?.distribution ?? { + 5: 0, + 4: 0, + 3: 0, + 2: 0, + 1: 0, + }; + const pct = + reviewCount === 0 ? 0 : Math.round((dist[r] / reviewCount) * 100); return (
{r} @@ -645,7 +717,9 @@ curl -X POST "${endpoint}" \\

API Playground

- +
} -) { +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; // Get agent (public endpoint - no auth needed) @@ -22,10 +19,7 @@ export async function GET( } if (!agent.agentcard_json) { - return NextResponse.json( - { error: 'AgentCard not available' }, - { status: 404 } - ); + return NextResponse.json({ error: 'AgentCard not available' }, { status: 404 }); } // Parse and return the stored AgentCard diff --git a/src/app/api/agents/[id]/claim/route.ts b/src/app/api/agents/[id]/claim/route.ts index 93e829d..c109bc4 100644 --- a/src/app/api/agents/[id]/claim/route.ts +++ b/src/app/api/agents/[id]/claim/route.ts @@ -26,29 +26,25 @@ function generateAgentSecret(): string { return `agk_${randomBytes(32).toString('hex')}`; } -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id: agentId } = await params; const auth = await getAuthenticatedUser(request); if (!auth?.userId || !auth?.address) { - return NextResponse.json( - { error: 'Wallet not connected' }, - { status: 401 } - ); + return NextResponse.json({ error: 'Wallet not connected' }, { status: 401 }); } const walletAddress = auth.address; // Get the agent with current owner info const { data: agent, error: agentError } = await supabaseAdmin .from('agents') - .select(` + .select( + ` id, name, owner_id, erc8004_token_id, erc8004_chain_id, users!agents_owner_id_fkey(id, wallet_address) - `) + ` + ) .eq('id', agentId) .single(); @@ -58,10 +54,7 @@ export async function POST( // Agent must be published on-chain if (!agent.erc8004_token_id || !agent.erc8004_chain_id) { - return NextResponse.json( - { error: 'Agent is not published on-chain' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Agent is not published on-chain' }, { status: 400 }); } // Verify caller owns the NFT on-chain @@ -72,18 +65,12 @@ export async function POST( ); if (!ownsNft) { - return NextResponse.json( - { error: 'You do not own this agent NFT' }, - { status: 403 } - ); + return NextResponse.json({ error: 'You do not own this agent NFT' }, { status: 403 }); } // Check if already the owner if (agent.owner_id === auth.userId) { - return NextResponse.json( - { error: 'You already own this agent' }, - { status: 400 } - ); + return NextResponse.json({ error: 'You already own this agent' }, { status: 400 }); } // Get previous owner info for logging @@ -113,23 +100,17 @@ export async function POST( feedback_operator_set_at: null, }) .eq('id', agentId) - .eq('owner_id', previousOwnerId) // Atomic: only update if owner hasn't changed + .eq('owner_id', previousOwnerId) // Atomic: only update if owner hasn't changed .select('id'); if (updateError) { console.error('[Claim] Update failed:', updateError); - return NextResponse.json( - { error: 'Failed to claim agent' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to claim agent' }, { status: 500 }); } // Check if update actually happened (race condition protection) if (!updateResult || updateResult.length === 0) { - return NextResponse.json( - { error: 'Agent already claimed by another user' }, - { status: 409 } - ); + return NextResponse.json({ error: 'Agent already claimed by another user' }, { status: 409 }); } // Log the claim @@ -162,9 +143,6 @@ export async function POST( }); } catch (error) { console.error('[Claim] Error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/agents/[id]/publish/confirm/route.ts b/src/app/api/agents/[id]/publish/confirm/route.ts index 34c167b..35b30b5 100644 --- a/src/app/api/agents/[id]/publish/confirm/route.ts +++ b/src/app/api/agents/[id]/publish/confirm/route.ts @@ -22,7 +22,9 @@ const ERC721_TRANSFER_ABI = [ ] as const; // Server-side log parsing - tries multiple methods -function parseTokenIdFromLogs(logs: readonly { topics: readonly `0x${string}`[]; data: `0x${string}` }[]): bigint | null { +function parseTokenIdFromLogs( + logs: readonly { topics: readonly `0x${string}`[]; data: `0x${string}` }[] +): bigint | null { // Method 1: Try AgentRegistered event try { const parsed = parseEventLogs({ @@ -46,9 +48,8 @@ function parseTokenIdFromLogs(logs: readonly { topics: readonly `0x${string}`[]; eventName: 'Transfer', }); - const mintEvent = parsed.find(p => - 'from' in p.args && - p.args.from === '0x0000000000000000000000000000000000000000' + const mintEvent = parsed.find( + (p) => 'from' in p.args && p.args.from === '0x0000000000000000000000000000000000000000' ); if (mintEvent && 'tokenId' in mintEvent.args) { @@ -61,7 +62,8 @@ function parseTokenIdFromLogs(logs: readonly { topics: readonly `0x${string}`[]; // Method 3: Try to parse tokenId from raw log topics try { const TRANSFER_SIG = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; - const AGENT_REGISTERED_SIG = '0xca52e62c367d81bb2e328eb795f7c7ba24afb478408a26c0e201d155c449bc4a'; + const AGENT_REGISTERED_SIG = + '0xca52e62c367d81bb2e328eb795f7c7ba24afb478408a26c0e201d155c449bc4a'; for (const log of logs) { const eventSig = log.topics[0]?.toLowerCase(); @@ -90,10 +92,7 @@ function parseTokenIdFromLogs(logs: readonly { topics: readonly `0x${string}`[]; // POST /api/agents/[id]/publish/confirm // Step 2: After user signs the transaction, verify and update DB -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; const auth = await getAuthenticatedUser(request); @@ -116,10 +115,7 @@ export async function POST( try { networkConfig = await getNetworkConfig(chainId); } catch { - return NextResponse.json( - { error: 'Unsupported chain' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Unsupported chain' }, { status: 400 }); } if (!networkConfig.identityRegistryAddress) { @@ -173,19 +169,20 @@ export async function POST( // Try to get transaction receipt with retries try { - receipt = await withRetry(async () => { - return await publicClient.waitForTransactionReceipt({ - hash: txHash as `0x${string}`, - timeout: 30_000, - confirmations: 1, - }); - }, 5, 3000); + receipt = await withRetry( + async () => { + return await publicClient.waitForTransactionReceipt({ + hash: txHash as `0x${string}`, + timeout: 30_000, + confirmations: 1, + }); + }, + 5, + 3000 + ); if (receipt.status !== 'success') { - return NextResponse.json( - { error: 'Transaction failed on-chain' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Transaction failed on-chain' }, { status: 400 }); } // SECURITY: Verify transaction was sent by the authenticated user diff --git a/src/app/api/agents/[id]/publish/route.ts b/src/app/api/agents/[id]/publish/route.ts index 5fb0078..a411621 100644 --- a/src/app/api/agents/[id]/publish/route.ts +++ b/src/app/api/agents/[id]/publish/route.ts @@ -7,10 +7,7 @@ import { buildAgentCard } from '@/lib/erc8004/agentcard'; // Step 1: Validate agent and prepare AgentCard for on-chain registration // Returns the tokenURI that the user will pass to the contract -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; const auth = await getAuthenticatedUser(request); @@ -89,9 +86,6 @@ export async function POST( }); } catch (err) { console.error('Publish prepare failed:', err); - return NextResponse.json( - { error: 'Failed to prepare agent for publishing' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to prepare agent for publishing' }, { status: 500 }); } } diff --git a/src/app/api/agents/[id]/reviews/[reviewId]/route.ts b/src/app/api/agents/[id]/reviews/[reviewId]/route.ts index 1f8a1c7..4ba11d6 100644 --- a/src/app/api/agents/[id]/reviews/[reviewId]/route.ts +++ b/src/app/api/agents/[id]/reviews/[reviewId]/route.ts @@ -30,7 +30,10 @@ export async function POST( } if (response.length > 1000) { - return NextResponse.json({ error: 'Response must be under 1000 characters' }, { status: 400 }); + return NextResponse.json( + { error: 'Response must be under 1000 characters' }, + { status: 400 } + ); } // Verify the user owns this agent diff --git a/src/app/api/agents/[id]/reviews/confirm/route.ts b/src/app/api/agents/[id]/reviews/confirm/route.ts index 98a340e..358d08f 100644 --- a/src/app/api/agents/[id]/reviews/confirm/route.ts +++ b/src/app/api/agents/[id]/reviews/confirm/route.ts @@ -9,10 +9,7 @@ import { withRetry } from '@/lib/utils/retry'; // POST /api/agents/[id]/reviews/confirm // Confirms the setApprovalForAll transaction for enabling reviews -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id: agentId } = await params; const auth = await getAuthenticatedUser(request); @@ -35,16 +32,15 @@ export async function POST( try { networkConfig = await getNetworkConfig(chainId); } catch { - return NextResponse.json( - { error: 'Unsupported chain' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Unsupported chain' }, { status: 400 }); } // Get agent with ownership check const { data: agent, error: agentError } = await supabaseAdmin .from('agents') - .select('id, owner_id, erc8004_token_id, erc8004_chain_id, feedback_signer_address, feedback_operator_tx_hash') + .select( + 'id, owner_id, erc8004_token_id, erc8004_chain_id, feedback_signer_address, feedback_operator_tx_hash' + ) .eq('id', agentId) .single(); @@ -58,10 +54,7 @@ export async function POST( // Agent must be published if (!agent.erc8004_token_id) { - return NextResponse.json( - { error: 'Agent not published' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Agent not published' }, { status: 400 }); } // Must have feedback signer address @@ -98,19 +91,20 @@ export async function POST( }); // Wait for transaction confirmation with retry - const receipt = await withRetry(async () => { - return await publicClient.waitForTransactionReceipt({ - hash: txHash as `0x${string}`, - timeout: 30_000, - confirmations: 1, - }); - }, 5, 3000); + const receipt = await withRetry( + async () => { + return await publicClient.waitForTransactionReceipt({ + hash: txHash as `0x${string}`, + timeout: 30_000, + confirmations: 1, + }); + }, + 5, + 3000 + ); if (receipt.status !== 'success') { - return NextResponse.json( - { error: 'Transaction failed on-chain' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Transaction failed on-chain' }, { status: 400 }); } // SECURITY: Verify transaction was sent by the authenticated user diff --git a/src/app/api/agents/[id]/reviews/route.ts b/src/app/api/agents/[id]/reviews/route.ts index 878beeb..9123eda 100644 --- a/src/app/api/agents/[id]/reviews/route.ts +++ b/src/app/api/agents/[id]/reviews/route.ts @@ -2,10 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getAuthenticatedUser } from '@/lib/auth/session'; import { supabaseAdmin, DbAgentReview } from '@/lib/db/supabase'; import { shortenAddress } from '@/lib/utils/format'; -import { - generateFeedbackSignerKeypair, - encryptPrivateKey, -} from '@/lib/erc8004/feedbackAuth'; +import { generateFeedbackSignerKeypair, encryptPrivateKey } from '@/lib/erc8004/feedbackAuth'; // Convert score (0-100) to stars (1-5) function scoreToStars(score: number): number { @@ -35,10 +32,7 @@ interface ReviewResponse { } // GET /api/agents/[id]/reviews - Get reviews for agent owner -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const auth = await getAuthenticatedUser(request); if (!auth) { @@ -100,24 +94,27 @@ export async function GET( if (reviewsError) { console.error('Error fetching reviews:', reviewsError); - return NextResponse.json( - { error: 'Failed to fetch reviews' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to fetch reviews' }, { status: 500 }); } // Fetch user handles for reviewer addresses - const reviewerAddresses = [...new Set((reviewsData || []).map((r: DbAgentReview) => r.reviewer_address))]; - const { data: usersData } = reviewerAddresses.length > 0 - ? await supabaseAdmin - .from('users') - .select('wallet_address, handle') - .in('wallet_address', reviewerAddresses) - : { data: [] }; - - const handleMap = new Map((usersData || []).map((u: { wallet_address: string; handle: string | null }) => - [u.wallet_address.toLowerCase(), u.handle] - )); + const reviewerAddresses = [ + ...new Set((reviewsData || []).map((r: DbAgentReview) => r.reviewer_address)), + ]; + const { data: usersData } = + reviewerAddresses.length > 0 + ? await supabaseAdmin + .from('users') + .select('wallet_address, handle') + .in('wallet_address', reviewerAddresses) + : { data: [] }; + + const handleMap = new Map( + (usersData || []).map((u: { wallet_address: string; handle: string | null }) => [ + u.wallet_address.toLowerCase(), + u.handle, + ]) + ); // Fetch stats from view const { data: statsData } = await supabaseAdmin @@ -185,10 +182,7 @@ export async function GET( // POST /api/agents/[id]/reviews - Prepare to enable reviews // Generates feedback signer keypair if not already present // Returns feedbackSignerAddress for setApprovalForAll call -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const auth = await getAuthenticatedUser(request); if (!auth) { @@ -238,10 +232,7 @@ export async function POST( if (updateError) { console.error('Failed to store feedback signer:', updateError); - return NextResponse.json( - { error: 'Failed to prepare reviews' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to prepare reviews' }, { status: 500 }); } } diff --git a/src/app/api/agents/[id]/route.ts b/src/app/api/agents/[id]/route.ts index 685e550..d705de9 100644 --- a/src/app/api/agents/[id]/route.ts +++ b/src/app/api/agents/[id]/route.ts @@ -38,10 +38,7 @@ function formatAgent(agent: DbAgent & { users?: { handle: string | null } }, own } // GET /api/agents/[id] - Get a single agent -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params; const auth = await getAuthenticatedUser(request); @@ -60,21 +57,17 @@ export async function GET( return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); } - return NextResponse.json({ agent: formatAgent(data as DbAgent & { users?: { handle: string | null } }) }); + return NextResponse.json({ + agent: formatAgent(data as DbAgent & { users?: { handle: string | null } }), + }); } catch (error) { console.error('Get agent error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } // PUT /api/agents/[id] - Update an agent -export async function PUT( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params; const auth = await getAuthenticatedUser(request); @@ -110,17 +103,25 @@ export async function PUT( } const body = await request.json(); - const { name, description, category, endpointUrl, timeoutMs, pricePerCall, status, inputSchema, outputSchema, readme } = body; + const { + name, + description, + category, + endpointUrl, + timeoutMs, + pricePerCall, + status, + inputSchema, + outputSchema, + readme, + } = body; // Build update object const updates: Record = {}; if (name !== undefined) { if (typeof name !== 'string' || name.length < 2 || name.length > 100) { - return NextResponse.json( - { error: 'Name must be 2-100 characters' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Name must be 2-100 characters' }, { status: 400 }); } updates.name = name.trim(); } @@ -132,10 +133,7 @@ export async function PUT( if (category !== undefined) { const validCategories: AgentCategory[] = ['ai', 'data', 'content', 'tools', 'other']; if (!validCategories.includes(category)) { - return NextResponse.json( - { error: 'Invalid category' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Invalid category' }, { status: 400 }); } updates.category = category; } @@ -144,10 +142,7 @@ export async function PUT( try { new URL(endpointUrl); } catch { - return NextResponse.json( - { error: 'Invalid endpoint URL format' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Invalid endpoint URL format' }, { status: 400 }); } updates.endpoint_url = endpointUrl.trim(); } @@ -176,10 +171,7 @@ export async function PUT( if (status !== undefined) { const validStatuses: AgentStatus[] = ['draft', 'pending', 'live', 'paused', 'rejected']; if (!validStatuses.includes(status)) { - return NextResponse.json( - { error: 'Invalid status' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Invalid status' }, { status: 400 }); } updates.status = status; @@ -195,10 +187,7 @@ export async function PUT( try { updates.input_schema = JSON.parse(inputSchema); } catch { - return NextResponse.json( - { error: 'Invalid input schema JSON' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Invalid input schema JSON' }, { status: 400 }); } } else { updates.input_schema = inputSchema; @@ -211,10 +200,7 @@ export async function PUT( try { updates.output_schema = JSON.parse(outputSchema); } catch { - return NextResponse.json( - { error: 'Invalid output schema JSON' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Invalid output schema JSON' }, { status: 400 }); } } else { updates.output_schema = outputSchema; @@ -226,10 +212,7 @@ export async function PUT( } if (Object.keys(updates).length === 0) { - return NextResponse.json( - { error: 'No fields to update' }, - { status: 400 } - ); + return NextResponse.json({ error: 'No fields to update' }, { status: 400 }); } const { data: updatedAgent, error } = await supabaseAdmin @@ -241,19 +224,15 @@ export async function PUT( if (error) { console.error('Error updating agent:', error); - return NextResponse.json( - { error: 'Failed to update agent' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to update agent' }, { status: 500 }); } - return NextResponse.json({ agent: formatAgent(updatedAgent as DbAgent & { users?: { handle: string | null } }) }); + return NextResponse.json({ + agent: formatAgent(updatedAgent as DbAgent & { users?: { handle: string | null } }), + }); } catch (error) { console.error('Update agent error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } @@ -296,25 +275,16 @@ export async function DELETE( } } - const { error } = await supabaseAdmin - .from('agents') - .delete() - .eq('id', id); + const { error } = await supabaseAdmin.from('agents').delete().eq('id', id); if (error) { console.error('Error deleting agent:', error); - return NextResponse.json( - { error: 'Failed to delete agent' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to delete agent' }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error('Delete agent error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/agents/[id]/secret/route.ts b/src/app/api/agents/[id]/secret/route.ts index b4643c9..0e6d5d7 100644 --- a/src/app/api/agents/[id]/secret/route.ts +++ b/src/app/api/agents/[id]/secret/route.ts @@ -4,10 +4,7 @@ import { getAuthenticatedUser } from '@/lib/auth/session'; import { generateAgentSecret } from '@/lib/crypto'; // GET /api/agents/[id]/secret - Get agent's secret info -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params; const auth = await getAuthenticatedUser(request); @@ -35,18 +32,12 @@ export async function GET( }); } catch (error) { console.error('Get secret error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } // POST /api/agents/[id]/secret - Generate a new secret -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params; const auth = await getAuthenticatedUser(request); @@ -80,10 +71,7 @@ export async function POST( if (error) { console.error('Error storing secret:', error); - return NextResponse.json( - { error: 'Failed to generate secret' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to generate secret' }, { status: 500 }); } return NextResponse.json({ @@ -92,10 +80,7 @@ export async function POST( }); } catch (error) { console.error('Generate secret error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } @@ -134,18 +119,12 @@ export async function DELETE( if (error) { console.error('Error deleting secret:', error); - return NextResponse.json( - { error: 'Failed to delete secret' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to delete secret' }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error('Delete secret error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/agents/dashboard/route.ts b/src/app/api/agents/dashboard/route.ts index b04755d..42f24a6 100644 --- a/src/app/api/agents/dashboard/route.ts +++ b/src/app/api/agents/dashboard/route.ts @@ -1,11 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { supabaseAdmin } from '@/lib/db/supabase'; import { getAuthenticatedUser } from '@/lib/auth/session'; -import { - getOwnedTokenIds, - batchVerifyOwnership, - type OwnershipStatus, -} from '@/lib/ownership'; +import { getOwnedTokenIds, batchVerifyOwnership, type OwnershipStatus } from '@/lib/ownership'; /** * GET /api/agents/dashboard @@ -67,12 +63,14 @@ export async function GET(request: NextRequest) { // 1. Get agents I control in DB const { data: dbAgents, error: dbError } = await supabaseAdmin .from('agents') - .select(` + .select( + ` id, name, description, category, status, price_per_call, total_calls, total_earned_cents, icon_url, erc8004_token_id, erc8004_chain_id, created_at, updated_at - `) + ` + ) .eq('owner_id', auth.userId) .order('created_at', { ascending: false }); @@ -87,13 +85,13 @@ export async function GET(request: NextRequest) { // 3. Batch verify ownership for on-chain agents const onChainAgents = (dbAgents || []).filter( - a => a.erc8004_token_id && a.erc8004_chain_id === chainId + (a) => a.erc8004_token_id && a.erc8004_chain_id === chainId ); - const tokenIdsToVerify = onChainAgents.map(a => a.erc8004_token_id!); + const tokenIdsToVerify = onChainAgents.map((a) => a.erc8004_token_id!); const ownershipMap = await batchVerifyOwnership(tokenIdsToVerify, chainId); // 4. Build controlled list with ownership status - const controlled: ControlledAgent[] = (dbAgents || []).map(agent => { + const controlled: ControlledAgent[] = (dbAgents || []).map((agent) => { let ownershipStatus: OwnershipStatus = 'draft'; let onChainOwner: string | undefined; @@ -132,14 +130,10 @@ export async function GET(request: NextRequest) { // 5. Find claimable agents (I own NFT but DB says someone else owns it) const myDbTokenIds = new Set( - (dbAgents || []) - .filter(a => a.erc8004_token_id) - .map(a => a.erc8004_token_id) + (dbAgents || []).filter((a) => a.erc8004_token_id).map((a) => a.erc8004_token_id) ); - const claimableTokenIds = ownedTokenIds.filter( - tid => !myDbTokenIds.has(tid) - ); + const claimableTokenIds = ownedTokenIds.filter((tid) => !myDbTokenIds.has(tid)); let claimable: ClaimableAgent[] = []; @@ -147,15 +141,17 @@ export async function GET(request: NextRequest) { // Look up these agents in DB const { data: claimableAgents } = await supabaseAdmin .from('agents') - .select(` + .select( + ` id, name, description, category, icon_url, erc8004_token_id, erc8004_chain_id, users!agents_owner_id_fkey(wallet_address) - `) + ` + ) .in('erc8004_token_id', claimableTokenIds) .eq('erc8004_chain_id', chainId); - claimable = (claimableAgents || []).map(agent => { + claimable = (claimableAgents || []).map((agent) => { const userData = agent.users as | { wallet_address: string } | { wallet_address: string }[] @@ -182,9 +178,6 @@ export async function GET(request: NextRequest) { return NextResponse.json(response); } catch (error) { console.error('[Dashboard] Error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/agents/route.ts b/src/app/api/agents/route.ts index ccce104..3bdb926 100644 --- a/src/app/api/agents/route.ts +++ b/src/app/api/agents/route.ts @@ -42,10 +42,7 @@ export async function GET(request: NextRequest) { if (error) { console.error('Error fetching agents:', error); - return NextResponse.json( - { error: 'Failed to fetch agents' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to fetch agents' }, { status: 500 }); } const agents = (data as DbAgent[]).map(formatAgent); @@ -53,10 +50,7 @@ export async function GET(request: NextRequest) { return NextResponse.json({ agents }); } catch (error) { console.error('Get agents error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } @@ -114,21 +108,12 @@ export async function POST(request: NextRequest) { if (error) { console.error('Error creating agent:', error); - return NextResponse.json( - { error: 'Failed to create agent' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to create agent' }, { status: 500 }); } - return NextResponse.json( - { agent: formatAgent(newAgent as DbAgent) }, - { status: 201 } - ); + return NextResponse.json({ agent: formatAgent(newAgent as DbAgent) }, { status: 201 }); } catch (error) { console.error('Create agent error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/agents/test-endpoint/route.ts b/src/app/api/agents/test-endpoint/route.ts index b1e8ab3..5b4ac25 100644 --- a/src/app/api/agents/test-endpoint/route.ts +++ b/src/app/api/agents/test-endpoint/route.ts @@ -21,13 +21,13 @@ function isInternalUrl(url: string): boolean { /^0\./, ]; - if (privateRanges.some(r => r.test(hostname))) { + if (privateRanges.some((r) => r.test(hostname))) { return true; } // Block internal hostnames const blockedHosts = ['metadata', 'metadata.google', 'instance-data']; - if (blockedHosts.some(h => hostname.includes(h))) { + if (blockedHosts.some((h) => hostname.includes(h))) { return true; } @@ -59,7 +59,7 @@ export async function POST(request: NextRequest) { } catch { return NextResponse.json({ success: false, - error: 'Invalid URL format' + error: 'Invalid URL format', }); } @@ -67,7 +67,7 @@ export async function POST(request: NextRequest) { if (!['http:', 'https:'].includes(parsedUrl.protocol)) { return NextResponse.json({ success: false, - error: 'Only HTTP/HTTPS URLs are supported' + error: 'Only HTTP/HTTPS URLs are supported', }); } @@ -75,7 +75,7 @@ export async function POST(request: NextRequest) { if (process.env.NODE_ENV !== 'development' && isInternalUrl(url)) { return NextResponse.json({ success: false, - error: 'Internal URLs are not allowed' + error: 'Internal URLs are not allowed', }); } @@ -155,9 +155,6 @@ export async function POST(request: NextRequest) { } } catch (error) { console.error('Test endpoint error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts index 4421068..eb21c65 100644 --- a/src/app/api/auth/me/route.ts +++ b/src/app/api/auth/me/route.ts @@ -7,20 +7,14 @@ export async function GET(request: NextRequest) { // Get token from Authorization header const authHeader = request.headers.get('authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { - return NextResponse.json( - { error: 'No token provided' }, - { status: 401 } - ); + return NextResponse.json({ error: 'No token provided' }, { status: 401 }); } const token = authHeader.replace('Bearer ', ''); const payload = await verifyToken(token); if (!payload) { - return NextResponse.json( - { error: 'Invalid token' }, - { status: 401 } - ); + return NextResponse.json({ error: 'Invalid token' }, { status: 401 }); } // Verify session is still valid (not revoked) @@ -34,10 +28,7 @@ export async function GET(request: NextRequest) { .single(); if (!sessionData) { - return NextResponse.json( - { error: 'Session expired or revoked' }, - { status: 401 } - ); + return NextResponse.json({ error: 'Session expired or revoked' }, { status: 401 }); } // Get user @@ -48,10 +39,7 @@ export async function GET(request: NextRequest) { .single(); if (error || !userData) { - return NextResponse.json( - { error: 'User not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'User not found' }, { status: 404 }); } const user = userData as DbUser; @@ -72,9 +60,6 @@ export async function GET(request: NextRequest) { }); } catch (error) { console.error('Auth check error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/auth/nonce/route.ts b/src/app/api/auth/nonce/route.ts index d686758..5108245 100644 --- a/src/app/api/auth/nonce/route.ts +++ b/src/app/api/auth/nonce/route.ts @@ -12,27 +12,19 @@ export async function GET() { expiresAt.setMinutes(expiresAt.getMinutes() + 5); // Store nonce in database - const { error } = await supabaseAdmin - .from('auth_nonces') - .insert({ - nonce, - expires_at: expiresAt.toISOString(), - }); + const { error } = await supabaseAdmin.from('auth_nonces').insert({ + nonce, + expires_at: expiresAt.toISOString(), + }); if (error) { console.error('Failed to store nonce:', error); - return NextResponse.json( - { error: 'Failed to generate nonce' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to generate nonce' }, { status: 500 }); } return NextResponse.json({ nonce }); } catch (error) { console.error('Nonce generation error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/auth/verify/route.ts b/src/app/api/auth/verify/route.ts index f9ab94e..4a05a2e 100644 --- a/src/app/api/auth/verify/route.ts +++ b/src/app/api/auth/verify/route.ts @@ -23,10 +23,7 @@ export async function POST(request: NextRequest) { const { message, signature, inviteCode, handle } = await request.json(); if (!message || !signature) { - return NextResponse.json( - { error: 'Message and signature are required' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Message and signature are required' }, { status: 400 }); } // Parse the SIWE message @@ -62,10 +59,7 @@ export async function POST(request: NextRequest) { } if (!isValid) { - return NextResponse.json( - { error: 'Invalid signature' }, - { status: 401 } - ); + return NextResponse.json({ error: 'Invalid signature' }, { status: 401 }); } const normalizedAddress = address.toLowerCase(); @@ -79,10 +73,7 @@ export async function POST(request: NextRequest) { .single(); if (nonceError || !nonceRecord) { - return NextResponse.json( - { error: 'Invalid or expired nonce' }, - { status: 401 } - ); + return NextResponse.json({ error: 'Invalid or expired nonce' }, { status: 401 }); } const nonceData = nonceRecord as DbAuthNonce; @@ -135,10 +126,7 @@ export async function POST(request: NextRequest) { if (rpcError || !result) { console.error('Registration RPC error:', rpcError); - return NextResponse.json( - { error: 'Failed to create user' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to create user' }, { status: 500 }); } // Check for business logic errors from the function @@ -159,10 +147,7 @@ export async function POST(request: NextRequest) { if (fetchError || !newUser) { console.error('Failed to fetch created user:', fetchError); - return NextResponse.json( - { error: 'Failed to create user' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to create user' }, { status: 500 }); } user = newUser as DbUser; @@ -187,15 +172,13 @@ export async function POST(request: NextRequest) { const expiresAt = getTokenExpiration(); // Store session - const { error: sessionError } = await supabaseAdmin - .from('user_sessions') - .insert({ - user_id: user.id, - token_hash: tokenHash, - expires_at: expiresAt.toISOString(), - ip_address: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'), - user_agent: request.headers.get('user-agent'), - }); + const { error: sessionError } = await supabaseAdmin.from('user_sessions').insert({ + user_id: user.id, + token_hash: tokenHash, + expires_at: expiresAt.toISOString(), + ip_address: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'), + user_agent: request.headers.get('user-agent'), + }); if (sessionError) { console.error('Failed to create session:', sessionError); @@ -217,9 +200,6 @@ export async function POST(request: NextRequest) { }); } catch (error) { console.error('Verification error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/creator/[handle]/route.ts b/src/app/api/creator/[handle]/route.ts index 75f3694..7dbf60f 100644 --- a/src/app/api/creator/[handle]/route.ts +++ b/src/app/api/creator/[handle]/route.ts @@ -70,10 +70,7 @@ export async function GET( } if (!userData) { - return NextResponse.json( - { error: 'Creator not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'Creator not found' }, { status: 404 }); } const user = userData; @@ -88,10 +85,7 @@ export async function GET( if (agentsError) { console.error('Error fetching creator agents:', agentsError); - return NextResponse.json( - { error: 'Failed to fetch agents' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to fetch agents' }, { status: 500 }); } const agents = (agentsData || []) as DbAgent[]; @@ -130,9 +124,6 @@ export async function GET( return NextResponse.json({ profile }); } catch (error) { console.error('Creator profile error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/feedback/route.ts b/src/app/api/feedback/route.ts index 93843b4..58c8a14 100644 --- a/src/app/api/feedback/route.ts +++ b/src/app/api/feedback/route.ts @@ -18,17 +18,11 @@ export async function POST(request: NextRequest) { } if (!message?.trim()) { - return NextResponse.json( - { error: 'Message is required' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Message is required' }, { status: 400 }); } if (!category || !['bug', 'feature', 'other'].includes(category)) { - return NextResponse.json( - { error: 'Invalid category' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Invalid category' }, { status: 400 }); } const { error } = await supabaseAdmin.from('feedback').insert({ @@ -42,18 +36,12 @@ export async function POST(request: NextRequest) { if (error) { console.error('Failed to save feedback:', error); - return NextResponse.json( - { error: 'Failed to save feedback' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to save feedback' }, { status: 500 }); } return NextResponse.json({ success: true }); } catch (error) { console.error('Feedback error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/marketplace/[handle]/[slug]/reviews/route.ts b/src/app/api/marketplace/[handle]/[slug]/reviews/route.ts index f0d9266..150375a 100644 --- a/src/app/api/marketplace/[handle]/[slug]/reviews/route.ts +++ b/src/app/api/marketplace/[handle]/[slug]/reviews/route.ts @@ -113,17 +113,23 @@ export async function GET( } // Fetch user handles for reviewer addresses - const reviewerAddresses = [...new Set((reviewsData || []).map((r: DbAgentReview) => r.reviewer_address))]; - const { data: usersData } = reviewerAddresses.length > 0 - ? await supabaseAdmin - .from('users') - .select('wallet_address, handle') - .in('wallet_address', reviewerAddresses) - : { data: [] }; - - const handleMap = new Map((usersData || []).map((u: { wallet_address: string; handle: string | null }) => - [u.wallet_address.toLowerCase(), u.handle] - )); + const reviewerAddresses = [ + ...new Set((reviewsData || []).map((r: DbAgentReview) => r.reviewer_address)), + ]; + const { data: usersData } = + reviewerAddresses.length > 0 + ? await supabaseAdmin + .from('users') + .select('wallet_address, handle') + .in('wallet_address', reviewerAddresses) + : { data: [] }; + + const handleMap = new Map( + (usersData || []).map((u: { wallet_address: string; handle: string | null }) => [ + u.wallet_address.toLowerCase(), + u.handle, + ]) + ); // Fetch stats from view const { data: statsData } = await supabaseAdmin @@ -191,7 +197,6 @@ export async function POST( const { handle, slug } = await params; const body = await request.json(); - // Resolve handle/slug to agentId const resolved = await resolveAgentByHandleSlug(handle, slug); if (!resolved) { @@ -206,21 +211,36 @@ export async function POST( } if (typeof score !== 'number' || score < 0 || score > 100) { - return NextResponse.json({ error: 'score must be a number between 0 and 100' }, { status: 400 }); + return NextResponse.json( + { error: 'score must be a number between 0 and 100' }, + { status: 400 } + ); } if (tag1 && !VALID_TAGS.includes(tag1)) { - return NextResponse.json({ error: `Invalid tag1. Must be one of: ${VALID_TAGS.join(', ')}` }, { status: 400 }); + return NextResponse.json( + { error: `Invalid tag1. Must be one of: ${VALID_TAGS.join(', ')}` }, + { status: 400 } + ); } if (tag2 && !VALID_TAGS.includes(tag2)) { - return NextResponse.json({ error: `Invalid tag2. Must be one of: ${VALID_TAGS.join(', ')}` }, { status: 400 }); + return NextResponse.json( + { error: `Invalid tag2. Must be one of: ${VALID_TAGS.join(', ')}` }, + { status: 400 } + ); } if (title && (typeof title !== 'string' || title.length > 200)) { - return NextResponse.json({ error: 'title must be a string with max 200 characters' }, { status: 400 }); + return NextResponse.json( + { error: 'title must be a string with max 200 characters' }, + { status: 400 } + ); } if (content && (typeof content !== 'string' || content.length > 2000)) { - return NextResponse.json({ error: 'content must be a string with max 2000 characters' }, { status: 400 }); + return NextResponse.json( + { error: 'content must be a string with max 2000 characters' }, + { status: 400 } + ); } // Verify agent exists and is live @@ -273,7 +293,10 @@ export async function POST( .single(); if (existingReview) { - return NextResponse.json({ error: 'Review already submitted for this payment' }, { status: 409 }); + return NextResponse.json( + { error: 'Review already submitted for this payment' }, + { status: 409 } + ); } const reviewContent = { @@ -341,8 +364,14 @@ export async function POST( onchain: { agentId: agent.erc8004_token_id, score: review.score, - tag1: tag1 && VALID_TAGS.includes(tag1) ? FEEDBACK_TAGS[tag1 as keyof typeof FEEDBACK_TAGS] : EMPTY_BYTES32, - tag2: tag2 && VALID_TAGS.includes(tag2) ? FEEDBACK_TAGS[tag2 as keyof typeof FEEDBACK_TAGS] : EMPTY_BYTES32, + tag1: + tag1 && VALID_TAGS.includes(tag1) + ? FEEDBACK_TAGS[tag1 as keyof typeof FEEDBACK_TAGS] + : EMPTY_BYTES32, + tag2: + tag2 && VALID_TAGS.includes(tag2) + ? FEEDBACK_TAGS[tag2 as keyof typeof FEEDBACK_TAGS] + : EMPTY_BYTES32, fileuri, filehash: contentHash, feedbackAuth, diff --git a/src/app/api/marketplace/[handle]/[slug]/route.ts b/src/app/api/marketplace/[handle]/[slug]/route.ts index 7f32ff6..37acd4a 100644 --- a/src/app/api/marketplace/[handle]/[slug]/route.ts +++ b/src/app/api/marketplace/[handle]/[slug]/route.ts @@ -65,10 +65,7 @@ export async function GET( .single(); if (userError || !user) { - return NextResponse.json( - { error: 'Creator not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'Creator not found' }, { status: 404 }); } // Fetch agent by owner_id and slug @@ -81,10 +78,7 @@ export async function GET( .single(); if (error || !data) { - return NextResponse.json( - { error: 'Agent not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); } const agent = data as DbAgent; @@ -128,11 +122,13 @@ export async function GET( // Reviews enabled (feedbackSigner is set up and operator approved) reviewsEnabled: !!(agent.feedback_signer_address && agent.feedback_operator_set_at), // Performance stats - stats: statsData ? { - uptime: statsData.uptime_pct, - avgResponseMs: statsData.avg_response_ms, - errorRate: statsData.error_rate_pct, - } : undefined, + stats: statsData + ? { + uptime: statsData.uptime_pct, + avgResponseMs: statsData.avg_response_ms, + errorRate: statsData.error_rate_pct, + } + : undefined, // Review stats reviewStats: reviewStatsData ? { @@ -153,9 +149,6 @@ export async function GET( return NextResponse.json({ agent: result }); } catch (error) { console.error('Marketplace detail error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/marketplace/route.ts b/src/app/api/marketplace/route.ts index 3e5a0aa..2f55454 100644 --- a/src/app/api/marketplace/route.ts +++ b/src/app/api/marketplace/route.ts @@ -18,7 +18,9 @@ interface MarketplaceAgent { ownerHandle: string | null; } -function formatMarketplaceAgent(agent: DbAgent & { users?: { handle: string | null } }): MarketplaceAgent { +function formatMarketplaceAgent( + agent: DbAgent & { users?: { handle: string | null } } +): MarketplaceAgent { return { id: agent.id, name: agent.name, @@ -50,8 +52,8 @@ export async function GET(request: NextRequest) { if (category && category !== 'All') { const categoryMap: Record = { 'AI / ML': 'ai', - 'Data': 'data', - 'Content': 'content', + Data: 'data', + Content: 'content', 'Dev Tools': 'tools', }; const dbCategory = categoryMap[category] || category.toLowerCase(); @@ -81,10 +83,7 @@ export async function GET(request: NextRequest) { if (error) { console.error('Error fetching marketplace agents:', error); - return NextResponse.json( - { error: 'Failed to fetch agents' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to fetch agents' }, { status: 500 }); } const agents = (data || []).map(formatMarketplaceAgent); @@ -92,9 +91,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ agents }); } catch (error) { console.error('Marketplace error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/network/route.ts b/src/app/api/network/route.ts index 1a9ddf6..bb4d224 100644 --- a/src/app/api/network/route.ts +++ b/src/app/api/network/route.ts @@ -6,7 +6,7 @@ export async function GET() { try { const networks = await getAllNetworks(); return NextResponse.json( - networks.map(config => ({ + networks.map((config) => ({ chainId: config.chainId, network: config.network, name: config.name, @@ -20,7 +20,10 @@ export async function GET() { } catch (error) { console.error('[Network API] Failed to load networks:', error); return NextResponse.json( - { error: 'Failed to load networks', details: error instanceof Error ? error.message : 'Unknown error' }, + { + error: 'Failed to load networks', + details: error instanceof Error ? error.message : 'Unknown error', + }, { status: 503 } ); } diff --git a/src/app/api/payments/route.ts b/src/app/api/payments/route.ts index 8be1060..e39ea4e 100644 --- a/src/app/api/payments/route.ts +++ b/src/app/api/payments/route.ts @@ -36,12 +36,13 @@ export async function GET(request: NextRequest) { return NextResponse.json({ payments: [] }); } - const agentIds = userAgents.map(a => a.id); + const agentIds = userAgents.map((a) => a.id); // Get payment history for user's agents const { data: payments, error: paymentsError } = await supabaseAdmin .from('agent_payments') - .select(` + .select( + ` id, agent_id, caller_address, @@ -52,7 +53,8 @@ export async function GET(request: NextRequest) { request_id, created_at, agents!inner(name) - `) + ` + ) .in('agent_id', agentIds) .order('created_at', { ascending: false }) .limit(limit); @@ -63,12 +65,10 @@ export async function GET(request: NextRequest) { } // Transform for client - const transformedPayments = (payments || []).map(p => { + const transformedPayments = (payments || []).map((p) => { // Handle joined agent - could be object or array depending on Supabase response const agentData = p.agents as { name: string } | { name: string }[] | null; - const agentName = Array.isArray(agentData) - ? agentData[0]?.name - : agentData?.name; + const agentName = Array.isArray(agentData) ? agentData[0]?.name : agentData?.name; return { id: p.id, @@ -87,9 +87,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ payments: transformedPayments }); } catch (error) { console.error('[Payments API] Error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/reviews/[id]/route.ts b/src/app/api/reviews/[id]/route.ts index 2c7ff87..256d3a5 100644 --- a/src/app/api/reviews/[id]/route.ts +++ b/src/app/api/reviews/[id]/route.ts @@ -19,10 +19,7 @@ interface ReviewContent { } // GET /api/reviews/[id] - Get review content (fileuri endpoint) -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { const { id } = await params; @@ -34,10 +31,7 @@ export async function GET( .single(); if (error || !review) { - return NextResponse.json( - { error: 'Review not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'Review not found' }, { status: 404 }); } // Return canonical JSON for on-chain filehash verification @@ -61,9 +55,6 @@ export async function GET( }); } catch (error) { console.error('Review content error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/users/profile/route.ts b/src/app/api/users/profile/route.ts index 89ac5f9..7ebb47d 100644 --- a/src/app/api/users/profile/route.ts +++ b/src/app/api/users/profile/route.ts @@ -36,10 +36,7 @@ export async function GET(request: NextRequest) { try { const auth = await getAuthenticatedUser(request); if (!auth) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const { data: userData, error } = await supabaseAdmin @@ -49,10 +46,7 @@ export async function GET(request: NextRequest) { .single(); if (error || !userData) { - return NextResponse.json( - { error: 'User not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'User not found' }, { status: 404 }); } const user = userData as DbUser; @@ -71,10 +65,7 @@ export async function GET(request: NextRequest) { }); } catch (error) { console.error('Get profile error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } @@ -82,10 +73,7 @@ export async function PUT(request: NextRequest) { try { const auth = await getAuthenticatedUser(request); if (!auth) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const body = await request.json(); @@ -110,10 +98,7 @@ export async function PUT(request: NextRequest) { .single(); if (existingUser) { - return NextResponse.json( - { error: 'Handle is already taken' }, - { status: 409 } - ); + return NextResponse.json({ error: 'Handle is already taken' }, { status: 409 }); } } } @@ -122,10 +107,7 @@ export async function PUT(request: NextRequest) { if (email !== undefined && email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { - return NextResponse.json( - { error: 'Invalid email format' }, - { status: 400 } - ); + return NextResponse.json({ error: 'Invalid email format' }, { status: 400 }); } } @@ -137,10 +119,7 @@ export async function PUT(request: NextRequest) { if (bio !== undefined) updates.bio = bio || null; if (Object.keys(updates).length === 0) { - return NextResponse.json( - { error: 'No fields to update' }, - { status: 400 } - ); + return NextResponse.json({ error: 'No fields to update' }, { status: 400 }); } const { data: updatedUserData, error } = await supabaseAdmin @@ -152,10 +131,7 @@ export async function PUT(request: NextRequest) { if (error || !updatedUserData) { console.error('Update profile error:', error); - return NextResponse.json( - { error: 'Failed to update profile' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to update profile' }, { status: 500 }); } const updatedUser = updatedUserData as DbUser; @@ -174,9 +150,6 @@ export async function PUT(request: NextRequest) { }); } catch (error) { console.error('Update profile error:', error); - return NextResponse.json( - { error: 'Internal server error' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } diff --git a/src/app/api/v1/call/[handle]/[slug]/route.ts b/src/app/api/v1/call/[handle]/[slug]/route.ts index 28f650f..6423174 100644 --- a/src/app/api/v1/call/[handle]/[slug]/route.ts +++ b/src/app/api/v1/call/[handle]/[slug]/route.ts @@ -1,8 +1,12 @@ import { NextRequest, NextResponse } from 'next/server'; import { supabaseAdmin } from '@/lib/db/supabase'; import { - verifyPayment, settlePayment, simulatePayment, decodePaymentSignatureHeader, - encodePaymentRequiredHeader, encodePaymentResponseHeader, + verifyPayment, + settlePayment, + simulatePayment, + decodePaymentSignatureHeader, + encodePaymentRequiredHeader, + encodePaymentResponseHeader, } from '@/lib/x402/facilitator'; import { X402_HEADERS, X402_VERSION } from '@/lib/x402/types'; import { getNetworkConfig, getDefaultNetworkConfig, type NetworkConfig } from '@/lib/network'; @@ -19,7 +23,10 @@ const MAX_REQUEST_SIZE = 10 * 1024 * 1024; // SSRF protection (bypassed in local dev) function isInternalUrl(url: string): boolean { // Allow localhost in development - if (process.env.NODE_ENV === 'development' || process.env.NEXT_PUBLIC_APP_URL?.includes('localhost')) { + if ( + process.env.NODE_ENV === 'development' || + process.env.NEXT_PUBLIC_APP_URL?.includes('localhost') + ) { return false; } try { @@ -28,33 +35,56 @@ function isInternalUrl(url: string): boolean { if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.)/.test(h)) return true; if (h.endsWith('.internal') || h.endsWith('.local')) return true; return false; - } catch { return true; } + } catch { + return true; + } } // Headers to skip when forwarding const SKIP_HEADERS = new Set([ - 'host', 'connection', 'content-length', 'transfer-encoding', 'authorization', 'cookie', - 'x-payment', 'x-payment-required', 'x-payment-response', - 'x-agentokratia-request-id', 'x-agentokratia-agent-id', 'x-agentokratia-caller', - 'x-agentokratia-timestamp', 'x-agentokratia-secret', + 'host', + 'connection', + 'content-length', + 'transfer-encoding', + 'authorization', + 'cookie', + 'x-payment', + 'x-payment-required', + 'x-payment-response', + 'x-agentokratia-request-id', + 'x-agentokratia-agent-id', + 'x-agentokratia-caller', + 'x-agentokratia-timestamp', + 'x-agentokratia-secret', ]); const CORS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Payment-Signature, Accept', - 'Access-Control-Expose-Headers': 'Payment-Required, Payment-Response, X-Agentokratia-Request-Id, X-Feedback-Auth, X-Feedback-Expires', + 'Access-Control-Expose-Headers': + 'Payment-Required, Payment-Response, X-Agentokratia-Request-Id, X-Feedback-Auth, X-Feedback-Expires', }; // Helpers const jsonResponse = (body: object, status: number, headers: Record = {}) => - new NextResponse(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json', ...CORS, ...headers } }); + new NextResponse(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json', ...CORS, ...headers }, + }); const errorResponse = (message: string, status: number, requestId?: string) => - jsonResponse({ error: message, ...(requestId && { requestId }) }, status, requestId ? { 'X-Agentokratia-Request-Id': requestId } : {}); + jsonResponse( + { error: message, ...(requestId && { requestId }) }, + status, + requestId ? { 'X-Agentokratia-Request-Id': requestId } : {} + ); // Look up agent by handle and slug -async function getAgentByHandleSlug(handle: string, slug: string): Promise<{ +async function getAgentByHandleSlug( + handle: string, + slug: string +): Promise<{ agent: { id: string; name: string; @@ -86,9 +116,11 @@ async function getAgentByHandleSlug(handle: string, slug: string): Promise<{ // Then find the agent by owner and slug const { data, error } = await supabaseAdmin .from('agents') - .select(`id, name, slug, endpoint_url, price_per_call, timeout_ms, status, owner_id, + .select( + `id, name, slug, endpoint_url, price_per_call, timeout_ms, status, owner_id, agent_secret, erc8004_chain_id, erc8004_token_id, - feedback_signer_address, feedback_signer_private_key, feedback_operator_set_at`) + feedback_signer_address, feedback_signer_private_key, feedback_operator_set_at` + ) .eq('owner_id', user.id) .eq('slug', slug.toLowerCase()) .eq('status', 'live') @@ -97,7 +129,11 @@ async function getAgentByHandleSlug(handle: string, slug: string): Promise<{ if (error || !data) return null; // Validate endpoint - if (!data.endpoint_url || data.endpoint_url === 'https://placeholder.example.com' || isInternalUrl(data.endpoint_url)) { + if ( + !data.endpoint_url || + data.endpoint_url === 'https://placeholder.example.com' || + isInternalUrl(data.endpoint_url) + ) { return null; } @@ -133,21 +169,25 @@ interface PaymentRecord { async function recordPayment(record: PaymentRecord): Promise { try { - const { data } = await supabaseAdmin.from('agent_payments').insert({ - agent_id: record.agentId, - caller_address: record.caller, - recipient_address: record.recipient, - amount_cents: record.cents, - tx_hash: record.txHash, - network: record.network, - status: record.status, - request_id: record.requestId, - started_at: record.startedAt ? new Date(record.startedAt).toISOString() : null, - response_time_ms: record.responseTimeMs, - success: record.success, - http_status: record.httpStatus, - error_code: record.errorCode, - }).select('id').single(); + const { data } = await supabaseAdmin + .from('agent_payments') + .insert({ + agent_id: record.agentId, + caller_address: record.caller, + recipient_address: record.recipient, + amount_cents: record.cents, + tx_hash: record.txHash, + network: record.network, + status: record.status, + request_id: record.requestId, + started_at: record.startedAt ? new Date(record.startedAt).toISOString() : null, + response_time_ms: record.responseTimeMs, + success: record.success, + http_status: record.httpStatus, + error_code: record.errorCode, + }) + .select('id') + .single(); return data?.id || null; } catch (e) { console.error('[Proxy] Record payment failed:', e); @@ -171,12 +211,14 @@ async function generateFeedbackAuthForPayment( paymentId: string ): Promise<{ feedbackAuth: string; expiry: string } | null> { try { - if (!agent.feedback_signer_address || - !agent.feedback_signer_private_key || - !agent.feedback_operator_set_at || - !agent.erc8004_token_id || - !agent.erc8004_chain_id || - !networkConfig.identityRegistryAddress) { + if ( + !agent.feedback_signer_address || + !agent.feedback_signer_private_key || + !agent.feedback_operator_set_at || + !agent.erc8004_token_id || + !agent.erc8004_chain_id || + !networkConfig.identityRegistryAddress + ) { return null; } @@ -222,19 +264,33 @@ async function generateFeedbackAuthForPayment( } } -function buildPaymentRequired(agent: { name: string; price_per_call: number }, ownerWallet: string, resource: string, networkConfig: NetworkConfig): PaymentRequired { +function buildPaymentRequired( + agent: { name: string; price_per_call: number }, + ownerWallet: string, + resource: string, + networkConfig: NetworkConfig +): PaymentRequired { return { x402Version: X402_VERSION, - resource: { url: resource, description: `API call to ${agent.name}`, mimeType: 'application/json' }, - accepts: [{ - scheme: 'exact', network: networkConfig.network, asset: networkConfig.usdcAddress, - amount: centsToUsdcUnits(agent.price_per_call).toString(), - payTo: ownerWallet, maxTimeoutSeconds: 300, - extra: { - name: networkConfig.usdcEip712Domain.name, - version: networkConfig.usdcEip712Domain.version, + resource: { + url: resource, + description: `API call to ${agent.name}`, + mimeType: 'application/json', + }, + accepts: [ + { + scheme: 'exact', + network: networkConfig.network, + asset: networkConfig.usdcAddress, + amount: centsToUsdcUnits(agent.price_per_call).toString(), + payTo: ownerWallet, + maxTimeoutSeconds: 300, + extra: { + name: networkConfig.usdcEip712Domain.name, + version: networkConfig.usdcEip712Domain.version, + }, }, - }], + ], }; } @@ -244,11 +300,15 @@ export async function OPTIONS() { } // Main proxy endpoint - handle/slug version -export async function POST(request: NextRequest, { params }: { params: Promise<{ handle: string; slug: string }> }) { +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ handle: string; slug: string }> } +) { const { handle, slug } = await params; const requestId = crypto.randomUUID(); const timestamp = Date.now(); - const baseUrl = request.headers.get('x-forwarded-host') || request.headers.get('host') || 'localhost'; + const baseUrl = + request.headers.get('x-forwarded-host') || request.headers.get('host') || 'localhost'; const resource = `${request.headers.get('x-forwarded-proto') || 'https'}://${baseUrl}/api/v1/call/${handle}/${slug}`; // 1. Get agent by handle/slug with on-chain owner verification @@ -257,7 +317,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ result = await getAgentByHandleSlug(handle, slug); } catch (e) { if (e instanceof Error && e.message === 'ONCHAIN_VERIFICATION_FAILED') { - return errorResponse('Blockchain verification temporarily unavailable. Please retry.', 503, requestId); + return errorResponse( + 'Blockchain verification temporarily unavailable. Please retry.', + 503, + requestId + ); } throw e; } @@ -272,7 +336,9 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ } else { networkConfig = await getDefaultNetworkConfig(); } - } catch { return errorResponse('Unsupported network for this agent', 503, requestId); } + } catch { + return errorResponse('Unsupported network for this agent', 503, requestId); + } const paymentRequired = buildPaymentRequired(agent, ownerWallet, resource, networkConfig); @@ -281,37 +347,70 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ if (!paymentHeader) { const priceUsdc = centsToUsdcString(agent.price_per_call); return jsonResponse( - { error: 'Payment required', message: `This API costs ${priceUsdc} USDC per call`, agentId: agent.id, agentName: agent.name, priceUsdc }, - 402, { [X402_HEADERS.PAYMENT_REQUIRED]: encodePaymentRequiredHeader(paymentRequired), 'X-Agentokratia-Request-Id': requestId } + { + error: 'Payment required', + message: `This API costs ${priceUsdc} USDC per call`, + agentId: agent.id, + agentName: agent.name, + priceUsdc, + }, + 402, + { + [X402_HEADERS.PAYMENT_REQUIRED]: encodePaymentRequiredHeader(paymentRequired), + 'X-Agentokratia-Request-Id': requestId, + } ); } // 4. Parse & validate payment let paymentPayload: PaymentPayload; - try { paymentPayload = decodePaymentSignatureHeader(paymentHeader); } - catch { return errorResponse('Invalid payment header format', 400, requestId); } + try { + paymentPayload = decodePaymentSignatureHeader(paymentHeader); + } catch { + return errorResponse('Invalid payment header format', 400, requestId); + } const paymentReqs = paymentPayload.accepted; const expected = paymentRequired.accepts[0]; - if (paymentReqs.amount !== expected.amount || paymentReqs.payTo.toLowerCase() !== expected.payTo.toLowerCase() || - paymentReqs.network !== expected.network || paymentReqs.asset.toLowerCase() !== expected.asset.toLowerCase()) { + if ( + paymentReqs.amount !== expected.amount || + paymentReqs.payTo.toLowerCase() !== expected.payTo.toLowerCase() || + paymentReqs.network !== expected.network || + paymentReqs.asset.toLowerCase() !== expected.asset.toLowerCase() + ) { return errorResponse('Payment requirements mismatch', 400, requestId); } // 5. Verify with facilitator const verifyResult = await verifyPayment(paymentPayload, paymentReqs); if (!verifyResult.isValid) { - return jsonResponse({ error: 'Payment verification failed', reason: verifyResult.invalidReason }, 402, - { [X402_HEADERS.PAYMENT_REQUIRED]: encodePaymentRequiredHeader(paymentRequired), 'X-Agentokratia-Request-Id': requestId }); + return jsonResponse( + { error: 'Payment verification failed', reason: verifyResult.invalidReason }, + 402, + { + [X402_HEADERS.PAYMENT_REQUIRED]: encodePaymentRequiredHeader(paymentRequired), + 'X-Agentokratia-Request-Id': requestId, + } + ); } const caller = verifyResult.payer || 'unknown'; // 5b. Simulate payment const simResult = await simulatePayment(paymentPayload, paymentReqs, networkConfig.rpcUrl); if (!simResult.success) { - console.error('[Proxy] SIMULATION FAILED:', { requestId, agentId: agent.id, caller, error: simResult.error, reason: simResult.errorReason }); + console.error('[Proxy] SIMULATION FAILED:', { + requestId, + agentId: agent.id, + caller, + error: simResult.error, + reason: simResult.errorReason, + }); return jsonResponse( - { error: 'Payment simulation failed', reason: simResult.errorReason, details: simResult.error }, + { + error: 'Payment simulation failed', + reason: simResult.errorReason, + details: simResult.error, + }, 400, { 'X-Agentokratia-Request-Id': requestId } ); @@ -321,13 +420,18 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ let body: string; try { body = await request.text(); - if (body.length > MAX_REQUEST_SIZE) return errorResponse('Request body too large', 413, requestId); - } catch { return errorResponse('Failed to read request body', 400, requestId); } + if (body.length > MAX_REQUEST_SIZE) + return errorResponse('Request body too large', 413, requestId); + } catch { + return errorResponse('Failed to read request body', 400, requestId); + } // 7. Build target headers const targetHeaders: Record = { - 'X-Agentokratia-Request-Id': requestId, 'X-Agentokratia-Agent-Id': agent.id, - 'X-Agentokratia-Caller': caller, 'X-Agentokratia-Timestamp': timestamp.toString(), + 'X-Agentokratia-Request-Id': requestId, + 'X-Agentokratia-Agent-Id': agent.id, + 'X-Agentokratia-Caller': caller, + 'X-Agentokratia-Timestamp': timestamp.toString(), }; if (agent.agent_secret) { @@ -342,10 +446,20 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ let targetResponse: Response, targetBody: string, targetContentType: string; try { const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), Math.min(agent.timeout_ms || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS)); + const timeoutId = setTimeout( + () => controller.abort(), + Math.min(agent.timeout_ms || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS) + ); try { - targetResponse = await fetch(agent.endpoint_url, { method: 'POST', headers: targetHeaders, body: body || undefined, signal: controller.signal }); - } finally { clearTimeout(timeoutId); } + targetResponse = await fetch(agent.endpoint_url, { + method: 'POST', + headers: targetHeaders, + body: body || undefined, + signal: controller.signal, + }); + } finally { + clearTimeout(timeoutId); + } targetBody = await targetResponse.text(); targetContentType = targetResponse.headers.get('content-type') || 'application/json'; } catch (e) { @@ -353,10 +467,24 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const responseTimeMs = Date.now() - timestamp; const isTimeout = e instanceof Error && e.name === 'AbortError'; await recordPayment({ - agentId: agent.id, caller, recipient: ownerWallet, cents: agent.price_per_call, txHash: null, requestId, status: 'failed', network: networkConfig.network, - startedAt: timestamp, responseTimeMs, success: false, errorCode: isTimeout ? 'TIMEOUT' : 'TARGET_ERROR', + agentId: agent.id, + caller, + recipient: ownerWallet, + cents: agent.price_per_call, + txHash: null, + requestId, + status: 'failed', + network: networkConfig.network, + startedAt: timestamp, + responseTimeMs, + success: false, + errorCode: isTimeout ? 'TIMEOUT' : 'TARGET_ERROR', }); - return errorResponse(isTimeout ? 'Target API timeout' : 'Target API unavailable', 502, requestId); + return errorResponse( + isTimeout ? 'Target API timeout' : 'Target API unavailable', + 502, + requestId + ); } const responseTimeMs = Date.now() - timestamp; @@ -364,10 +492,28 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ // 9. Target error = no charge if (!targetResponse.ok) { await recordPayment({ - agentId: agent.id, caller, recipient: ownerWallet, cents: agent.price_per_call, txHash: null, requestId, status: 'failed', network: networkConfig.network, - startedAt: timestamp, responseTimeMs, success: false, httpStatus: targetResponse.status, errorCode: 'TARGET_ERROR', + agentId: agent.id, + caller, + recipient: ownerWallet, + cents: agent.price_per_call, + txHash: null, + requestId, + status: 'failed', + network: networkConfig.network, + startedAt: timestamp, + responseTimeMs, + success: false, + httpStatus: targetResponse.status, + errorCode: 'TARGET_ERROR', + }); + return new NextResponse(targetBody, { + status: targetResponse.status, + headers: { + 'Content-Type': targetContentType, + ...CORS, + 'X-Agentokratia-Request-Id': requestId, + }, }); - return new NextResponse(targetBody, { status: targetResponse.status, headers: { 'Content-Type': targetContentType, ...CORS, 'X-Agentokratia-Request-Id': requestId } }); } // 10. Re-verify ownership before settlement @@ -375,42 +521,87 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const currentOwner = await getOnChainOwner(agent.erc8004_token_id, agent.erc8004_chain_id); if (!currentOwner || currentOwner.toLowerCase() !== ownerWallet.toLowerCase()) { console.error('[Proxy] OWNER_CHANGED: NFT transferred during payment', { - requestId, agentId: agent.id, caller, + requestId, + agentId: agent.id, + caller, expectedOwner: ownerWallet, currentOwner: currentOwner || 'lookup_failed', }); await recordPayment({ - agentId: agent.id, caller, recipient: ownerWallet, cents: agent.price_per_call, txHash: null, requestId, status: 'failed', network: networkConfig.network, - startedAt: timestamp, responseTimeMs, success: false, errorCode: 'OWNER_CHANGED', + agentId: agent.id, + caller, + recipient: ownerWallet, + cents: agent.price_per_call, + txHash: null, + requestId, + status: 'failed', + network: networkConfig.network, + startedAt: timestamp, + responseTimeMs, + success: false, + errorCode: 'OWNER_CHANGED', }); return errorResponse('Agent ownership changed during payment. Please retry.', 409, requestId); } } // 11. Settle with retry - let settleResult: Awaited> | null = null, lastError: string | undefined; + let settleResult: Awaited> | null = null, + lastError: string | undefined; for (let i = 1; i <= 3; i++) { settleResult = await settlePayment(paymentPayload, paymentReqs); if (settleResult.success) break; lastError = settleResult.errorReason; - if (i < 3) await new Promise(r => setTimeout(r, 500 * i)); + if (i < 3) await new Promise((r) => setTimeout(r, 500 * i)); } let paymentId: string | null = null; let feedbackAuthResult: { feedbackAuth: string; expiry: string } | null = null; if (!settleResult?.success) { - console.error('[Proxy] SETTLEMENT FAILED:', { requestId, agentId: agent.id, caller, ownerWallet, amount: agent.price_per_call, error: lastError }); + console.error('[Proxy] SETTLEMENT FAILED:', { + requestId, + agentId: agent.id, + caller, + ownerWallet, + amount: agent.price_per_call, + error: lastError, + }); paymentId = await recordPayment({ - agentId: agent.id, caller, recipient: ownerWallet, cents: agent.price_per_call, txHash: null, requestId, status: 'verified', network: networkConfig.network, - startedAt: timestamp, responseTimeMs, success: true, httpStatus: targetResponse.status, + agentId: agent.id, + caller, + recipient: ownerWallet, + cents: agent.price_per_call, + txHash: null, + requestId, + status: 'verified', + network: networkConfig.network, + startedAt: timestamp, + responseTimeMs, + success: true, + httpStatus: targetResponse.status, }); } else { paymentId = await recordPayment({ - agentId: agent.id, caller, recipient: ownerWallet, cents: agent.price_per_call, txHash: settleResult.transaction || null, requestId, status: 'settled', network: networkConfig.network, - startedAt: timestamp, responseTimeMs, success: true, httpStatus: targetResponse.status, + agentId: agent.id, + caller, + recipient: ownerWallet, + cents: agent.price_per_call, + txHash: settleResult.transaction || null, + requestId, + status: 'settled', + network: networkConfig.network, + startedAt: timestamp, + responseTimeMs, + success: true, + httpStatus: targetResponse.status, }); - try { await supabaseAdmin.rpc('increment_agent_stats', { p_agent_id: agent.id, p_amount_cents: agent.price_per_call }); } catch {} + try { + await supabaseAdmin.rpc('increment_agent_stats', { + p_agent_id: agent.id, + p_amount_cents: agent.price_per_call, + }); + } catch {} if (paymentId) { feedbackAuthResult = await generateFeedbackAuthForPayment( @@ -450,4 +641,9 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ // Reject other methods const methodNotAllowed = () => errorResponse('Method not allowed. Use POST.', 405); -export { methodNotAllowed as GET, methodNotAllowed as PUT, methodNotAllowed as DELETE, methodNotAllowed as PATCH }; +export { + methodNotAllowed as GET, + methodNotAllowed as PUT, + methodNotAllowed as DELETE, + methodNotAllowed as PATCH, +}; diff --git a/src/app/creator/[handle]/page.module.css b/src/app/creator/[handle]/page.module.css index 9285735..d078cce 100644 --- a/src/app/creator/[handle]/page.module.css +++ b/src/app/creator/[handle]/page.module.css @@ -378,7 +378,9 @@ } @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } /* Responsive */ diff --git a/src/app/creator/[handle]/page.tsx b/src/app/creator/[handle]/page.tsx index 9360b57..0d70f22 100644 --- a/src/app/creator/[handle]/page.tsx +++ b/src/app/creator/[handle]/page.tsx @@ -3,7 +3,18 @@ import { useState } from 'react'; import { useParams } from 'next/navigation'; import Link from 'next/link'; -import { ArrowLeft, Loader2, Zap, Star, Share2, Check, Shield, Clock, Package, ExternalLink } from 'lucide-react'; +import { + ArrowLeft, + Loader2, + Zap, + Star, + Share2, + Check, + Shield, + Clock, + Package, + ExternalLink, +} from 'lucide-react'; import { useQuery } from '@tanstack/react-query'; import { Button } from '@/components/ui'; import { PublicHeader, PublicFooter } from '@/components/layout'; @@ -53,7 +64,11 @@ export default function CreatorProfilePage() { const handle = params.handle as string; const [copied, setCopied] = useState(false); - const { data: profile, isLoading, error } = useQuery({ + const { + data: profile, + isLoading, + error, + } = useQuery({ queryKey: ['creator-profile', handle], queryFn: () => fetchCreatorProfile(handle), enabled: !!handle, @@ -86,8 +101,14 @@ export default function CreatorProfilePage() {

Profile not found

-

{error instanceof Error ? error.message : 'This profile doesn\'t exist or may have been removed.'}

- +

+ {error instanceof Error + ? error.message + : "This profile doesn't exist or may have been removed."} +

+ + +
@@ -129,9 +150,7 @@ export default function CreatorProfilePage() {

{displayName}

- {profile.bio && ( -

{profile.bio}

- )} + {profile.bio &&

{profile.bio}

} {/* Stats Row */}
@@ -198,11 +217,7 @@ export default function CreatorProfilePage() { ) : (
{profile.agents.map((agent) => ( - +

{agent.name}

{formatUsdc(agent.pricePerCall)}/call diff --git a/src/app/dashboard/agents/[id]/page.module.css b/src/app/dashboard/agents/[id]/page.module.css index e7137cf..4d35609 100644 --- a/src/app/dashboard/agents/[id]/page.module.css +++ b/src/app/dashboard/agents/[id]/page.module.css @@ -233,8 +233,14 @@ } @keyframes fadeIn { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } } .panelHeader { @@ -737,12 +743,24 @@ } /* Syntax highlighting */ -.codeBlockContent .keyword { color: #569cd6; } -.codeBlockContent .string { color: #ce9178; } -.codeBlockContent .comment { color: #6a9955; } -.codeBlockContent .function { color: #dcdcaa; } -.codeBlockContent .variable { color: #9cdcfe; } -.codeBlockContent .property { color: #9cdcfe; } +.codeBlockContent .keyword { + color: #569cd6; +} +.codeBlockContent .string { + color: #ce9178; +} +.codeBlockContent .comment { + color: #6a9955; +} +.codeBlockContent .function { + color: #dcdcaa; +} +.codeBlockContent .variable { + color: #9cdcfe; +} +.codeBlockContent .property { + color: #9cdcfe; +} /* Language Tabs */ .langTabs { @@ -958,7 +976,7 @@ .toggleSlider::before { position: absolute; - content: ""; + content: ''; height: 18px; width: 18px; left: 3px; @@ -993,8 +1011,12 @@ } @keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } /* Toast */ diff --git a/src/app/dashboard/agents/[id]/page.tsx b/src/app/dashboard/agents/[id]/page.tsx index 1bfc605..5c0565a 100644 --- a/src/app/dashboard/agents/[id]/page.tsx +++ b/src/app/dashboard/agents/[id]/page.tsx @@ -73,7 +73,10 @@ interface SecretKeyInfo { createdAt: string | null; } -const statusConfig: Record = { +const statusConfig: Record< + string, + { label: string; variant: 'success' | 'warning' | 'error' | 'default' } +> = { live: { label: 'Live', variant: 'success' }, draft: { label: 'Draft', variant: 'warning' }, pending: { label: 'Pending', variant: 'default' }, @@ -110,7 +113,11 @@ async function fetchSecretKey(id: string, token: string): Promise return res.json(); } -async function updateAgentApi(id: string, token: string, updates: Record): Promise { +async function updateAgentApi( + id: string, + token: string, + updates: Record +): Promise { const res = await fetch(`/api/agents/${id}`, { method: 'PUT', headers: { @@ -141,7 +148,12 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri const [showEnableReviewsModal, setShowEnableReviewsModal] = useState(false); // Fetch agent data - const { data: agent, isLoading, error, refetch: refetchAgent } = useQuery({ + const { + data: agent, + isLoading, + error, + refetch: refetchAgent, + } = useQuery({ queryKey: ['agent', id, token], queryFn: () => fetchAgent(id, token!), enabled: !!token, @@ -169,17 +181,16 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri }, }); - // Check on-chain if reviews are enabled (via isApprovedForAll) - const { reviewsEnabled, isLoading: isCheckingReviews, refetch: refetchReviews } = useReviewsEnabled( - address, - agent?.feedbackSignerAddress, - agent?.erc8004ChainId - ); + const { + reviewsEnabled, + isLoading: isCheckingReviews, + refetch: refetchReviews, + } = useReviewsEnabled(address, agent?.feedbackSignerAddress, agent?.erc8004ChainId); useEffect(() => { const tab = searchParams.get('tab') as Tab | null; - if (tab && tabs.some(t => t.id === tab)) { + if (tab && tabs.some((t) => t.id === tab)) { setActiveTab(tab); } }, [searchParams]); @@ -200,14 +211,18 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri const handlePublished = async (tokenId: string, txHash: string, chainId: number) => { // Update cache optimistically with the new on-chain data - queryClient.setQueryData(['agent', id, token], (prev: Agent | undefined) => prev ? { - ...prev, - status: 'live', - publishedAt: new Date().toISOString(), - erc8004TokenId: tokenId, - erc8004TxHash: txHash, - erc8004ChainId: chainId, - } : undefined); + queryClient.setQueryData(['agent', id, token], (prev: Agent | undefined) => + prev + ? { + ...prev, + status: 'live', + publishedAt: new Date().toISOString(), + erc8004TokenId: tokenId, + erc8004TxHash: txHash, + erc8004ChainId: chainId, + } + : undefined + ); queryClient.invalidateQueries({ queryKey: ['agents'] }); showToast('Agent published successfully!'); // Refetch to get the complete updated state from server @@ -227,7 +242,9 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri const copyAgentUrl = async () => { if (agent?.ownerHandle && agent?.slug) { - await navigator.clipboard.writeText(`${window.location.origin}/api/v1/call/${agent.ownerHandle}/${agent.slug}`); + await navigator.clipboard.writeText( + `${window.location.origin}/api/v1/call/${agent.ownerHandle}/${agent.slug}` + ); showToast('URL copied!'); } }; @@ -243,7 +260,9 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri case 'profile': return agent.name ? 'done' : 'needs-setup'; case 'connection': - return agent.endpointUrl && agent.endpointUrl !== 'https://placeholder.example.com' ? 'done' : 'needs-setup'; + return agent.endpointUrl && agent.endpointUrl !== 'https://placeholder.example.com' + ? 'done' + : 'needs-setup'; case 'pricing': return 'done'; case 'readme': @@ -252,7 +271,12 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri return secretKey?.hasKey ? 'done' : 'needs-setup'; case 'reviews': // Show highlight indicator when published but reviews not enabled - if (agent.status === 'live' && agent.erc8004TokenId && !reviewsEnabled && !isCheckingReviews) { + if ( + agent.status === 'live' && + agent.erc8004TokenId && + !reviewsEnabled && + !isCheckingReviews + ) { return 'highlight'; } return reviewsEnabled ? 'done' : ''; @@ -304,7 +328,14 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri
- @@ -331,11 +362,16 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri -
+
@@ -395,7 +435,6 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri
)} - {/* Tabs */} @@ -429,7 +468,12 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri {/* Connection Tab */} {activeTab === 'connection' && ( - + )} {/* Pricing Tab */} @@ -438,20 +482,18 @@ export default function AgentDetailPage({ params }: { params: Promise<{ id: stri )} {/* README Tab */} - {activeTab === 'readme' && ( - - )} + {activeTab === 'readme' && } {/* Security Tab */} - {activeTab === 'security' && ( - - )} + {activeTab === 'security' && } {/* Reviews Tab */} {activeTab === 'reviews' && ( n.chainId === agent.erc8004ChainId)?.blockExplorerUrl} + blockExplorerUrl={ + networks?.find((n) => n.chainId === agent.erc8004ChainId)?.blockExplorerUrl + } reviewsEnabled={reviewsEnabled} isCheckingReviews={isCheckingReviews} onEnableReviews={() => setShowEnableReviewsModal(true)} diff --git a/src/app/dashboard/agents/[id]/tabs/ConnectionTab.tsx b/src/app/dashboard/agents/[id]/tabs/ConnectionTab.tsx index 4339d00..53b3e3d 100644 --- a/src/app/dashboard/agents/[id]/tabs/ConnectionTab.tsx +++ b/src/app/dashboard/agents/[id]/tabs/ConnectionTab.tsx @@ -1,7 +1,15 @@ 'use client'; import { useState } from 'react'; -import { Plus, Trash2, GripVertical, AlertCircle, CheckCircle, Download, Upload } from 'lucide-react'; +import { + Plus, + Trash2, + GripVertical, + AlertCircle, + CheckCircle, + Download, + Upload, +} from 'lucide-react'; import { Button, Input, Select } from '@/components/ui'; import { PLACEHOLDER_ENDPOINT } from '@/lib/utils/constants'; import { TestPlayground } from '@/components/dashboard/TestPlayground'; @@ -46,7 +54,10 @@ const validateJsonSchema = (json: string): { valid: boolean; error?: string; sch } // Check for valid type - if (parsed.type && !['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'].includes(parsed.type)) { + if ( + parsed.type && + !['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'].includes(parsed.type) + ) { return { valid: false, error: `Invalid type: ${parsed.type}` }; } @@ -76,7 +87,9 @@ const validateJsonSchema = (json: string): { valid: boolean; error?: string; sch }; // Validate and parse OpenAPI spec -const validateOpenApiSpec = (json: string): { valid: boolean; error?: string; inputSchema?: object; outputSchema?: object } => { +const validateOpenApiSpec = ( + json: string +): { valid: boolean; error?: string; inputSchema?: object; outputSchema?: object } => { if (!json.trim()) return { valid: true }; try { @@ -110,7 +123,9 @@ const validateOpenApiSpec = (json: string): { valid: boolean; error?: string; in // Output from responses const responses = postOp.responses as Record | undefined; if (responses) { - const successResponse = (responses['200'] || responses['201']) as Record | undefined; + const successResponse = (responses['200'] || responses['201']) as + | Record + | undefined; if (successResponse?.content) { const content = successResponse.content as Record; const jsonContent = content['application/json']; @@ -170,7 +185,12 @@ const resolveRef = (schema: object, root: object, depth = 0): object => { }; // Generate OpenAPI spec from JSON schemas -const generateOpenApiSpec = (inputSchema: object | null, outputSchema: object | null, agentName: string, endpointUrl: string): string => { +const generateOpenApiSpec = ( + inputSchema: object | null, + outputSchema: object | null, + agentName: string, + endpointUrl: string +): string => { const spec = { openapi: '3.0.3', info: { @@ -178,9 +198,7 @@ const generateOpenApiSpec = (inputSchema: object | null, outputSchema: object | version: '1.0.0', description: `API specification for ${agentName}`, }, - servers: [ - { url: endpointUrl || 'https://api.example.com' }, - ], + servers: [{ url: endpointUrl || 'https://api.example.com' }], paths: { '/': { post: { @@ -214,7 +232,7 @@ const generateOpenApiSpec = (inputSchema: object | null, outputSchema: object | const normalizeSchemaType = (type: unknown): SchemaField['type'] => { if (Array.isArray(type)) { // Find first non-null type - const nonNullType = type.find(t => t !== 'null'); + const nonNullType = type.find((t) => t !== 'null'); return (nonNullType as SchemaField['type']) || 'string'; } if (typeof type === 'string') { @@ -227,7 +245,10 @@ const jsonSchemaToFields = (schema: object | null): SchemaField[] => { if (!schema || typeof schema !== 'object') { return [{ id: '1', name: '', type: 'string', description: '', required: false }]; } - const s = schema as { properties?: Record; required?: string[] }; + const s = schema as { + properties?: Record; + required?: string[]; + }; if (!s.properties || Object.keys(s.properties).length === 0) { return [{ id: '1', name: '', type: 'string', description: '', required: false }]; } @@ -246,7 +267,9 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop agent.endpointUrl === PLACEHOLDER_ENDPOINT ? '' : agent.endpointUrl ); // Convert ms to seconds for UI display - const [timeoutVal, setTimeoutVal] = useState(String(Math.floor((agent.timeoutMs || 30000) / 1000))); + const [timeoutVal, setTimeoutVal] = useState( + String(Math.floor((agent.timeoutMs || 30000) / 1000)) + ); const [schemaMode, setSchemaMode] = useState<'visual' | 'json' | 'openapi'>('visual'); // Visual schema builder state - initialize from agent's saved schemas @@ -336,7 +359,9 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop const result = validateOpenApiSpec(openApiSpec); if (result.valid) { setInputSchemaJson(result.inputSchema ? JSON.stringify(result.inputSchema, null, 2) : ''); - setOutputSchemaJson(result.outputSchema ? JSON.stringify(result.outputSchema, null, 2) : ''); + setOutputSchemaJson( + result.outputSchema ? JSON.stringify(result.outputSchema, null, 2) : '' + ); } } else { // From visual mode @@ -357,8 +382,12 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop inputSchema = fieldsToJsonSchema(inputFields); outputSchema = fieldsToJsonSchema(outputFields); } else if (schemaMode === 'json') { - try { inputSchema = inputSchemaJson ? JSON.parse(inputSchemaJson) : null; } catch {} - try { outputSchema = outputSchemaJson ? JSON.parse(outputSchemaJson) : null; } catch {} + try { + inputSchema = inputSchemaJson ? JSON.parse(inputSchemaJson) : null; + } catch {} + try { + outputSchema = outputSchemaJson ? JSON.parse(outputSchemaJson) : null; + } catch {} } setOpenApiSpec(generateOpenApiSpec(inputSchema, outputSchema, agent.name, endpointUrl)); @@ -384,20 +413,20 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop const updateField = (isInput: boolean, id: string, updates: Partial) => { const setter = isInput ? setInputFields : setOutputFields; const fields = isInput ? inputFields : outputFields; - setter(fields.map(f => f.id === id ? { ...f, ...updates } : f)); + setter(fields.map((f) => (f.id === id ? { ...f, ...updates } : f))); }; const removeField = (isInput: boolean, id: string) => { const setter = isInput ? setInputFields : setOutputFields; const fields = isInput ? inputFields : outputFields; - setter(fields.filter(f => f.id !== id)); + setter(fields.filter((f) => f.id !== id)); }; const fieldsToJsonSchema = (fields: SchemaField[]) => { const properties: Record = {}; const required: string[] = []; - fields.forEach(field => { + fields.forEach((field) => { if (!field.name) return; properties[field.name] = { type: field.type, @@ -430,10 +459,14 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop // Parse JSON mode schemas try { inputSchema = inputSchemaJson ? JSON.parse(inputSchemaJson) : null; - } catch { /* invalid JSON, skip */ } + } catch { + /* invalid JSON, skip */ + } try { outputSchema = outputSchemaJson ? JSON.parse(outputSchemaJson) : null; - } catch { /* invalid JSON, skip */ } + } catch { + /* invalid JSON, skip */ + } } else if (schemaMode === 'openapi') { // Extract from OpenAPI spec const result = validateOpenApiSpec(openApiSpec); @@ -455,7 +488,9 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop

Connect Your Agent

-

Define your endpoint, describe what your agent accepts and returns.

+

+ Define your endpoint, describe what your agent accepts and returns. +

{/* Endpoint Section - Two column layout */} @@ -471,7 +506,9 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop onChange={(e) => setEndpointUrl(e.target.value)} placeholder="https://your-server.com/api/agent" /> -

Where should we forward requests? Must accept POST and return JSON.

+

+ Where should we forward requests? Must accept POST and return JSON. +

@@ -533,30 +570,44 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop type="text" placeholder="field_name" value={field.name} - onChange={(e) => updateField(true, field.id, { name: e.target.value.replace(/\s/g, '_').toLowerCase() })} + onChange={(e) => + updateField(true, field.id, { + name: e.target.value.replace(/\s/g, '_').toLowerCase(), + }) + } className={styles.fieldName} /> updateField(true, field.id, { description: e.target.value })} + onChange={(e) => + updateField(true, field.id, { description: e.target.value }) + } className={styles.fieldDesc} /> @@ -594,30 +645,44 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop type="text" placeholder="field_name" value={field.name} - onChange={(e) => updateField(false, field.id, { name: e.target.value.replace(/\s/g, '_').toLowerCase() })} + onChange={(e) => + updateField(false, field.id, { + name: e.target.value.replace(/\s/g, '_').toLowerCase(), + }) + } className={styles.fieldName} /> updateField(false, field.id, { description: e.target.value })} + onChange={(e) => + updateField(false, field.id, { description: e.target.value }) + } className={styles.fieldDesc} /> @@ -643,17 +708,32 @@ export default function ConnectionTab({ agent, onSave, saving, secretKey }: Prop
Input Schema - {inputSchemaJson && ( - inputSchemaError ? ( - + {inputSchemaJson && + (inputSchemaError ? ( + {inputSchemaError} ) : ( - + Valid - ) - )} + ))}