feat(phase-4): WS2 + WS3 — Developer Portal (Next.js 14) and CLI tool (sentryagent)
WS2: Developer Portal (portal/) - Standalone Next.js 14 + Tailwind CSS app — independent deployment - Home page: hero, feature grid, CTA to /get-started - /pricing: free tier limits table (10 agents, 1k calls/day) + paid tier CTA - /sdks: all 4 SDKs (Node.js, Python, Go, Java) with install + code examples - /api-explorer: Swagger UI from NEXT_PUBLIC_API_URL/openapi.json, persistAuthorization - /get-started: 4-step wizard (setup → register agent → credentials → SDK snippet) - Shared Nav component with active-link highlighting - Build: 8/8 static pages, zero TypeScript errors WS3: CLI Tool (cli/ — npm package: sentryagent) - configure, register-agent, list-agents, issue-token, rotate-credentials, tail-audit-log - Auto OAuth2 token fetch + 30s-buffer cache via client_credentials flow - chalk-formatted table output, confirmation prompts, bounded audit log dedup - bash + zsh shell completion scripts - README with installation, all commands, and completion setup - Build: tsc clean, node dist/index.js --help verified Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
38
portal/app/api-explorer/page.tsx
Normal file
38
portal/app/api-explorer/page.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type React from 'react';
|
||||
import { SwaggerExplorer } from '@/components/SwaggerExplorer';
|
||||
|
||||
export const metadata = {
|
||||
title: 'API Explorer — SentryAgent AgentIdP',
|
||||
description:
|
||||
'Interactively explore and test the SentryAgent AgentIdP REST API.',
|
||||
};
|
||||
|
||||
export default function ApiExplorerPage(): React.ReactElement {
|
||||
const apiUrl =
|
||||
process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
return (
|
||||
<div className="px-4 py-8">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-extrabold text-slate-900">
|
||||
API Explorer
|
||||
</h1>
|
||||
<p className="mt-2 text-slate-600">
|
||||
Explore, authenticate, and test every AgentIdP endpoint directly
|
||||
from your browser. Use the Authorize button to set your Bearer
|
||||
token.
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-slate-400">
|
||||
Spec loaded from:{' '}
|
||||
<code className="rounded bg-slate-100 px-1.5 py-0.5 text-xs">
|
||||
{apiUrl}/openapi.json
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<SwaggerExplorer apiUrl={apiUrl} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
portal/app/get-started/page.tsx
Normal file
29
portal/app/get-started/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type React from 'react';
|
||||
import { GetStartedWizard } from '@/components/GetStartedWizard';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Get Started — SentryAgent AgentIdP',
|
||||
description:
|
||||
'Set up your first AI agent with SentryAgent AgentIdP in four steps.',
|
||||
};
|
||||
|
||||
export default function GetStartedPage(): React.ReactElement {
|
||||
const apiUrl =
|
||||
process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
return (
|
||||
<div className="px-6 py-16">
|
||||
<div className="mb-12 text-center">
|
||||
<h1 className="mb-4 text-4xl font-extrabold text-slate-900">
|
||||
Get Started
|
||||
</h1>
|
||||
<p className="text-xl text-slate-600">
|
||||
Register your first agent and get production-ready credentials in
|
||||
minutes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<GetStartedWizard apiUrl={apiUrl} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
portal/app/globals.css
Normal file
13
portal/app/globals.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 15, 23, 42;
|
||||
--background-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: rgb(var(--background-rgb));
|
||||
}
|
||||
31
portal/app/layout.tsx
Normal file
31
portal/app/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { Metadata } from 'next';
|
||||
import type React from 'react';
|
||||
import './globals.css';
|
||||
import { Nav } from '@/components/Nav';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'SentryAgent.ai Developer Portal',
|
||||
description:
|
||||
'Identity and access management for AI agents. Register, authenticate, and secure your agents with SentryAgent AgentIdP.',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="min-h-screen bg-slate-50 text-slate-900 antialiased">
|
||||
<Nav />
|
||||
<main>{children}</main>
|
||||
<footer className="border-t border-slate-200 py-8 text-center text-sm text-slate-500">
|
||||
<p>
|
||||
© {new Date().getFullYear()} SentryAgent.ai — All rights
|
||||
reserved.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
112
portal/app/page.tsx
Normal file
112
portal/app/page.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import type React from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: '🔑',
|
||||
title: 'Agent Identity',
|
||||
description:
|
||||
'Assign every AI agent a cryptographic identity backed by W3C DIDs. No more shared secrets or hard-coded API keys.',
|
||||
},
|
||||
{
|
||||
icon: '🛡️',
|
||||
title: 'OAuth 2.0 & OIDC',
|
||||
description:
|
||||
'Issue short-lived access tokens with fine-grained scopes. Integrate with any standards-compliant authorization server.',
|
||||
},
|
||||
{
|
||||
icon: '📋',
|
||||
title: 'Full Audit Trail',
|
||||
description:
|
||||
'Every agent action is logged with tamper-evident entries. Meet SOC 2, ISO 27001, and enterprise compliance requirements.',
|
||||
},
|
||||
{
|
||||
icon: '⚡',
|
||||
title: 'High Performance',
|
||||
description:
|
||||
'Token issuance p95 under 500 ms at 1,000 concurrent agents. Redis-backed rate limiting scales horizontally.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function HomePage(): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
{/* Hero */}
|
||||
<section className="bg-gradient-to-b from-brand-50 to-white px-6 py-24 text-center">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<span className="mb-4 inline-block rounded-full bg-brand-100 px-4 py-1 text-sm font-semibold text-brand-700">
|
||||
Identity for AI Agents
|
||||
</span>
|
||||
<h1 className="mb-6 text-5xl font-extrabold leading-tight tracking-tight text-slate-900">
|
||||
Secure Every Agent.{' '}
|
||||
<span className="text-brand-600">Trust Every Call.</span>
|
||||
</h1>
|
||||
<p className="mb-10 text-xl leading-relaxed text-slate-600">
|
||||
SentryAgent AgentIdP is the identity and access management platform
|
||||
built for AI agents. Register agents, issue OAuth 2.0 tokens,
|
||||
enforce policies, and audit every interaction — all from a single
|
||||
API.
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<Link
|
||||
href="/get-started"
|
||||
className="rounded-xl bg-brand-600 px-8 py-3 text-base font-semibold text-white shadow-md transition-colors hover:bg-brand-700"
|
||||
>
|
||||
Get Started Free
|
||||
</Link>
|
||||
<Link
|
||||
href="/api-explorer"
|
||||
className="rounded-xl border border-slate-300 bg-white px-8 py-3 text-base font-semibold text-slate-700 shadow-sm transition-colors hover:bg-slate-50"
|
||||
>
|
||||
Explore the API
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features */}
|
||||
<section className="px-6 py-20">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<h2 className="mb-12 text-center text-3xl font-bold text-slate-900">
|
||||
Everything your agents need to stay secure
|
||||
</h2>
|
||||
<div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{features.map(({ icon, title, description }) => (
|
||||
<div
|
||||
key={title}
|
||||
className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm"
|
||||
>
|
||||
<div className="mb-3 text-3xl">{icon}</div>
|
||||
<h3 className="mb-2 text-lg font-semibold text-slate-900">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-slate-600">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Banner */}
|
||||
<section className="bg-brand-600 px-6 py-16 text-center text-white">
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<h2 className="mb-4 text-3xl font-bold">
|
||||
Ready to secure your AI agents?
|
||||
</h2>
|
||||
<p className="mb-8 text-lg text-brand-100">
|
||||
Get started in minutes. Free tier includes 10 agents and 1,000 API
|
||||
calls per day — no credit card required.
|
||||
</p>
|
||||
<Link
|
||||
href="/get-started"
|
||||
className="inline-block rounded-xl bg-white px-8 py-3 text-base font-semibold text-brand-700 shadow-md transition-colors hover:bg-brand-50"
|
||||
>
|
||||
Start Building Now
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
160
portal/app/pricing/page.tsx
Normal file
160
portal/app/pricing/page.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import type React from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface FreeTierLimit {
|
||||
feature: string;
|
||||
limit: string;
|
||||
}
|
||||
|
||||
const freeTierLimits: FreeTierLimit[] = [
|
||||
{ feature: 'Registered agents', limit: '10' },
|
||||
{ feature: 'API calls per day', limit: '1,000' },
|
||||
{ feature: 'OAuth 2.0 token issuance', limit: 'Included' },
|
||||
{ feature: 'W3C DID documents', limit: 'Included' },
|
||||
{ feature: 'Audit log retention', limit: '7 days' },
|
||||
{ feature: 'Webhook events', limit: 'Not included' },
|
||||
{ feature: 'OIDC provider', limit: 'Not included' },
|
||||
{ feature: 'AGNTCY federation', limit: 'Not included' },
|
||||
{ feature: 'SOC 2 compliance reports', limit: 'Not included' },
|
||||
{ feature: 'Priority support', limit: 'Not included' },
|
||||
];
|
||||
|
||||
const paidFeatures: string[] = [
|
||||
'Unlimited agents',
|
||||
'Unlimited API calls',
|
||||
'90-day audit log retention',
|
||||
'Webhook event streaming',
|
||||
'OIDC provider integration',
|
||||
'AGNTCY federation',
|
||||
'SOC 2 Type II compliance reports',
|
||||
'Custom rate limit policies',
|
||||
'Dedicated SLA & priority support',
|
||||
'SSO / SAML for team access',
|
||||
];
|
||||
|
||||
export default function PricingPage(): React.ReactElement {
|
||||
return (
|
||||
<div className="px-6 py-20">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="mb-12 text-center">
|
||||
<h1 className="mb-4 text-4xl font-extrabold text-slate-900">
|
||||
Simple, Transparent Pricing
|
||||
</h1>
|
||||
<p className="text-xl text-slate-600">
|
||||
Start free. Upgrade when you grow.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
{/* Free Tier */}
|
||||
<div className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-2xl font-bold text-slate-900">Free</h2>
|
||||
<p className="mt-1 text-slate-500">
|
||||
Perfect for development and testing
|
||||
</p>
|
||||
<div className="mt-4 flex items-baseline gap-1">
|
||||
<span className="text-5xl font-extrabold text-slate-900">
|
||||
$0
|
||||
</span>
|
||||
<span className="text-slate-500">/ month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/get-started"
|
||||
className="mb-8 block w-full rounded-lg border border-brand-600 py-3 text-center font-semibold text-brand-600 transition-colors hover:bg-brand-50"
|
||||
>
|
||||
Get Started Free
|
||||
</Link>
|
||||
|
||||
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-slate-500">
|
||||
Free tier limits
|
||||
</h3>
|
||||
<div className="overflow-hidden rounded-xl border border-slate-200">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-slate-50">
|
||||
<th className="px-4 py-3 text-left font-semibold text-slate-700">
|
||||
Feature
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right font-semibold text-slate-700">
|
||||
Limit
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{freeTierLimits.map(({ feature, limit }, i) => (
|
||||
<tr
|
||||
key={feature}
|
||||
className={i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}
|
||||
>
|
||||
<td className="px-4 py-3 text-slate-700">{feature}</td>
|
||||
<td className="px-4 py-3 text-right font-medium text-slate-900">
|
||||
{limit}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Paid Tier */}
|
||||
<div className="relative rounded-2xl border-2 border-brand-500 bg-gradient-to-b from-brand-50 to-white p-8 shadow-lg">
|
||||
<span className="absolute -top-3.5 left-1/2 -translate-x-1/2 rounded-full bg-brand-600 px-4 py-1 text-xs font-bold uppercase tracking-wider text-white">
|
||||
Most Popular
|
||||
</span>
|
||||
|
||||
<div className="mb-6">
|
||||
<h2 className="text-2xl font-bold text-slate-900">Pro</h2>
|
||||
<p className="mt-1 text-slate-500">
|
||||
For production workloads and teams
|
||||
</p>
|
||||
<div className="mt-4 flex items-baseline gap-1">
|
||||
<span className="text-5xl font-extrabold text-slate-900">
|
||||
Custom
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Contact us for volume pricing
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="mailto:sales@sentryagent.ai"
|
||||
className="mb-8 block w-full rounded-lg bg-brand-600 py-3 text-center font-semibold text-white shadow-md transition-colors hover:bg-brand-700"
|
||||
>
|
||||
Contact Sales
|
||||
</a>
|
||||
|
||||
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-slate-500">
|
||||
Everything in Free, plus
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{paidFeatures.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-3 text-sm">
|
||||
<span className="mt-0.5 flex-shrink-0 text-brand-600">
|
||||
✓
|
||||
</span>
|
||||
<span className="text-slate-700">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* FAQ nudge */}
|
||||
<p className="mt-12 text-center text-slate-500">
|
||||
Questions about pricing?{' '}
|
||||
<a
|
||||
href="mailto:sales@sentryagent.ai"
|
||||
className="font-medium text-brand-600 hover:underline"
|
||||
>
|
||||
Talk to our team
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
194
portal/app/sdks/page.tsx
Normal file
194
portal/app/sdks/page.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
import type React from 'react';
|
||||
|
||||
interface Sdk {
|
||||
name: string;
|
||||
language: string;
|
||||
badge: string;
|
||||
installCmd: string;
|
||||
codeExample: string;
|
||||
repoUrl: string;
|
||||
}
|
||||
|
||||
const sdks: Sdk[] = [
|
||||
{
|
||||
name: 'Node.js SDK',
|
||||
language: 'typescript',
|
||||
badge: 'TypeScript',
|
||||
installCmd: 'npm install @sentryagent/idp-sdk',
|
||||
codeExample: `import { AgentIdPClient } from '@sentryagent/idp-sdk';
|
||||
|
||||
const client = new AgentIdPClient({
|
||||
apiUrl: process.env.AGENTIDP_URL!,
|
||||
clientId: process.env.AGENTIDP_CLIENT_ID!,
|
||||
clientSecret: process.env.AGENTIDP_CLIENT_SECRET!,
|
||||
});
|
||||
|
||||
// Register a new agent
|
||||
const agent = await client.agents.register({
|
||||
name: 'my-ai-agent',
|
||||
description: 'Production summarisation agent',
|
||||
});
|
||||
|
||||
console.log('Agent ID:', agent.agentId);
|
||||
|
||||
// Issue an access token
|
||||
const { accessToken } = await client.tokens.issue(agent.agentId);
|
||||
console.log('Token:', accessToken);`,
|
||||
repoUrl: 'https://github.com/sentryagent/sdk-node',
|
||||
},
|
||||
{
|
||||
name: 'Python SDK',
|
||||
language: 'python',
|
||||
badge: 'Python',
|
||||
installCmd: 'pip install sentryagent-idp',
|
||||
codeExample: `from sentryagent_idp import AgentIdPClient
|
||||
|
||||
client = AgentIdPClient(
|
||||
api_url=os.environ["AGENTIDP_URL"],
|
||||
client_id=os.environ["AGENTIDP_CLIENT_ID"],
|
||||
client_secret=os.environ["AGENTIDP_CLIENT_SECRET"],
|
||||
)
|
||||
|
||||
# Register a new agent
|
||||
agent = client.agents.register(
|
||||
name="my-ai-agent",
|
||||
description="Production summarisation agent",
|
||||
)
|
||||
|
||||
print("Agent ID:", agent.agent_id)
|
||||
|
||||
# Issue an access token
|
||||
token_response = client.tokens.issue(agent.agent_id)
|
||||
print("Token:", token_response.access_token)`,
|
||||
repoUrl: 'https://github.com/sentryagent/sdk-python',
|
||||
},
|
||||
{
|
||||
name: 'Go SDK',
|
||||
language: 'go',
|
||||
badge: 'Go',
|
||||
installCmd: 'go get github.com/sentryagent/idp-sdk-go',
|
||||
codeExample: `import (
|
||||
"fmt"
|
||||
"os"
|
||||
idp "github.com/sentryagent/idp-sdk-go"
|
||||
)
|
||||
|
||||
client := idp.NewClient(idp.Config{
|
||||
APIURL: os.Getenv("AGENTIDP_URL"),
|
||||
ClientID: os.Getenv("AGENTIDP_CLIENT_ID"),
|
||||
ClientSecret: os.Getenv("AGENTIDP_CLIENT_SECRET"),
|
||||
})
|
||||
|
||||
// Register a new agent
|
||||
agent, err := client.Agents.Register(ctx, &idp.RegisterAgentInput{
|
||||
Name: "my-ai-agent",
|
||||
Description: "Production summarisation agent",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Agent ID:", agent.AgentID)
|
||||
|
||||
// Issue an access token
|
||||
token, err := client.Tokens.Issue(ctx, agent.AgentID)
|
||||
fmt.Println("Token:", token.AccessToken)`,
|
||||
repoUrl: 'https://github.com/sentryagent/sdk-go',
|
||||
},
|
||||
{
|
||||
name: 'Java SDK',
|
||||
language: 'java',
|
||||
badge: 'Java',
|
||||
installCmd: `<dependency>
|
||||
<groupId>ai.sentryagent</groupId>
|
||||
<artifactId>idp-sdk</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>`,
|
||||
codeExample: `import ai.sentryagent.idp.AgentIdPClient;
|
||||
import ai.sentryagent.idp.model.Agent;
|
||||
import ai.sentryagent.idp.model.TokenResponse;
|
||||
|
||||
AgentIdPClient client = AgentIdPClient.builder()
|
||||
.apiUrl(System.getenv("AGENTIDP_URL"))
|
||||
.clientId(System.getenv("AGENTIDP_CLIENT_ID"))
|
||||
.clientSecret(System.getenv("AGENTIDP_CLIENT_SECRET"))
|
||||
.build();
|
||||
|
||||
// Register a new agent
|
||||
Agent agent = client.agents().register(
|
||||
RegisterAgentRequest.builder()
|
||||
.name("my-ai-agent")
|
||||
.description("Production summarisation agent")
|
||||
.build()
|
||||
);
|
||||
System.out.println("Agent ID: " + agent.getAgentId());
|
||||
|
||||
// Issue an access token
|
||||
TokenResponse token = client.tokens().issue(agent.getAgentId());
|
||||
System.out.println("Token: " + token.getAccessToken());`,
|
||||
repoUrl: 'https://github.com/sentryagent/sdk-java',
|
||||
},
|
||||
];
|
||||
|
||||
export default function SdksPage(): React.ReactElement {
|
||||
return (
|
||||
<div className="px-6 py-20">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="mb-12 text-center">
|
||||
<h1 className="mb-4 text-4xl font-extrabold text-slate-900">
|
||||
Official SDKs
|
||||
</h1>
|
||||
<p className="text-xl text-slate-600">
|
||||
Native libraries for your language of choice. Every SDK is
|
||||
type-safe, fully tested, and maintained by the SentryAgent team.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-10">
|
||||
{sdks.map((sdk) => (
|
||||
<div
|
||||
key={sdk.name}
|
||||
className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm"
|
||||
>
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-2xl font-bold text-slate-900">
|
||||
{sdk.name}
|
||||
</h2>
|
||||
<span className="rounded-full bg-brand-100 px-3 py-0.5 text-sm font-semibold text-brand-700">
|
||||
{sdk.badge}
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
href={sdk.repoUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm font-medium text-brand-600 hover:underline"
|
||||
>
|
||||
View on GitHub →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<p className="mb-2 text-sm font-semibold uppercase tracking-wider text-slate-500">
|
||||
Installation
|
||||
</p>
|
||||
<pre className="overflow-x-auto rounded-lg bg-slate-900 px-4 py-3 text-sm text-slate-100">
|
||||
<code>{sdk.installCmd}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-semibold uppercase tracking-wider text-slate-500">
|
||||
Quick Start
|
||||
</p>
|
||||
<pre className="overflow-x-auto rounded-lg bg-slate-900 px-4 py-4 text-sm leading-relaxed text-slate-100">
|
||||
<code>{sdk.codeExample}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user