create-githat-app 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +3 -3
- package/package.json +6 -2
- package/templates/agent/app/(auth)/forgot-password/page.tsx.hbs +11 -0
- package/templates/agent/app/(auth)/reset-password/page.tsx.hbs +39 -0
- package/templates/agent/app/(auth)/verify-email/page.tsx.hbs +41 -0
- package/templates/agent/app/admin/agent/page.tsx.hbs +159 -62
- package/templates/agent/app/admin/layout.tsx.hbs +82 -0
- package/templates/agent/app/admin/mcp/page.tsx.hbs +156 -0
- package/templates/agent/app/dashboard/agents/page.tsx.hbs +9 -0
- package/templates/agent/app/dashboard/layout.tsx.hbs +9 -0
- package/templates/agent/app/dashboard/mcp/page.tsx.hbs +9 -0
- package/templates/agent/app/dashboard/page.tsx.hbs +128 -0
- package/templates/agent/app/globals.css.hbs +14 -9
- package/templates/agent/app/layout.tsx.hbs +7 -2
- package/templates/agent/app/page.tsx.hbs +127 -70
- package/templates/agent/app/verify/agent/page.tsx.hbs +124 -0
- package/templates/agent/next.config.ts.hbs +4 -5
- package/templates/agent/public/HERO_IMAGE.md +23 -0
- package/templates/base/githat/api/agents.ts.hbs +6 -6
- package/templates/base/githat/config.ts.hbs +7 -9
- package/templates/base/githat/dashboard/overview.tsx.hbs +106 -16
- package/templates/classroom/app/layout.tsx.hbs +6 -1
- package/templates/classroom/next.config.ts.hbs +4 -5
- package/templates/content/app/layout.tsx.hbs +6 -1
- package/templates/content/next.config.ts.hbs +4 -5
- package/templates/dashboard/app/admin/data/[entity]/page.tsx.hbs +2 -2
- package/templates/dashboard/app/layout.tsx.hbs +6 -1
- package/templates/dashboard/next.config.ts.hbs +4 -5
- package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +6 -1
- package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +5 -5
- package/templates/marketplace/app/layout.tsx.hbs +6 -1
- package/templates/marketplace/next.config.ts.hbs +4 -5
- package/templates/nextjs/app/(auth)/forgot-password/page.tsx.hbs +2 -54
- package/templates/nextjs/app/(auth)/reset-password/page.tsx.hbs +8 -75
- package/templates/nextjs/app/layout.tsx.hbs +6 -1
- package/templates/nextjs/next.config.ts.hbs +4 -5
- package/templates/plain/app/layout.tsx.hbs +6 -1
- package/templates/plain/next.config.ts.hbs +4 -5
- package/templates/portfolio/app/layout.tsx.hbs +6 -1
- package/templates/portfolio/next.config.ts.hbs +4 -5
- package/templates/saas/app/layout.tsx.hbs +6 -1
- package/templates/saas/next.config.ts.hbs +4 -5
- package/templates/agent/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/agent/proxy.ts.hbs +0 -10
- package/templates/classroom/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/classroom/proxy.ts.hbs +0 -10
- package/templates/content/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/content/proxy.ts.hbs +0 -10
- package/templates/dashboard/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/dashboard/proxy.ts.hbs +0 -10
- package/templates/fullstack/apps-web-nextjs/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/marketplace/app/(shop)/[slug]/p/[productId]/page.tsx.hbs +0 -99
- package/templates/marketplace/app/(shop)/[slug]/page.tsx.hbs +0 -90
- package/templates/marketplace/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/marketplace/proxy.ts.hbs +0 -10
- package/templates/nextjs/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/nextjs/proxy.ts.hbs +0 -10
- package/templates/plain/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/plain/proxy.ts.hbs +0 -10
- package/templates/portfolio/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/portfolio/proxy.ts.hbs +0 -10
- package/templates/saas/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/saas/proxy.ts.hbs +0 -10
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{{#if includeDashboard}}
|
|
2
|
+
{{#if includeGithatFolder}}
|
|
3
|
+
import { DashboardLayout } from '../../githat/dashboard/layout{{#unless typescript}}.jsx{{/unless}}';
|
|
4
|
+
|
|
5
|
+
export default function Layout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
|
|
6
|
+
return <DashboardLayout>{children}</DashboardLayout>;
|
|
7
|
+
}
|
|
8
|
+
{{/if}}
|
|
9
|
+
{{/if}}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{{#if includeMcpModule}}
|
|
2
|
+
{{#if includeGithatFolder}}
|
|
3
|
+
import { DashboardMcpServers } from '../../../githat/dashboard/mcp-servers{{#unless typescript}}.jsx{{/unless}}';
|
|
4
|
+
|
|
5
|
+
export default function McpPage() {
|
|
6
|
+
return <DashboardMcpServers />;
|
|
7
|
+
}
|
|
8
|
+
{{/if}}
|
|
9
|
+
{{/if}}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{{#if includeDashboard}}
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { useAuth } from '@githat/nextjs';
|
|
7
|
+
import { githatApi } from '../../../githat/api/client{{#unless typescript}}.js{{/unless}}';
|
|
8
|
+
{{#if typescript}}
|
|
9
|
+
interface AgentSummary {
|
|
10
|
+
total: number;
|
|
11
|
+
active: number;
|
|
12
|
+
actionsLast24h: number;
|
|
13
|
+
mcpServers: number;
|
|
14
|
+
}
|
|
15
|
+
{{/if}}
|
|
16
|
+
|
|
17
|
+
export default function DashboardPage() {
|
|
18
|
+
const { user, org } = useAuth();
|
|
19
|
+
const [summary, setSummary] = useState{{#if typescript}}<AgentSummary>{{/if}}({
|
|
20
|
+
total: 0,
|
|
21
|
+
active: 0,
|
|
22
|
+
actionsLast24h: 0,
|
|
23
|
+
mcpServers: 0,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
githatApi.get{{#if typescript}}<AgentSummary>{{/if}}('/agents/summary')
|
|
28
|
+
.then((data) => setSummary(data))
|
|
29
|
+
.catch(() => {});
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<div style=\{{ marginBottom: '2rem' }}>
|
|
35
|
+
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 700, color: '#fafafa', marginBottom: '0.375rem' }}>
|
|
36
|
+
Welcome back{user?.name ? `, ${user.name}` : ''}
|
|
37
|
+
</h1>
|
|
38
|
+
{org && (
|
|
39
|
+
<p style=\{{ color: '#71717a', fontSize: '0.875rem' }}>
|
|
40
|
+
{org.name} · <span style=\{{ color: '#a1a1aa' }}>{org.role}</span>
|
|
41
|
+
</p>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* Stats row */}
|
|
46
|
+
<div style=\{{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(14rem, 1fr))', gap: '1rem', marginBottom: '2.5rem' }}>
|
|
47
|
+
{[
|
|
48
|
+
{ label: 'Total agents', value: summary.total, sub: 'Registered' },
|
|
49
|
+
{ label: 'Active agents', value: summary.active, sub: 'Running now' },
|
|
50
|
+
{ label: 'Actions (24h)', value: summary.actionsLast24h, sub: 'Across all agents' },
|
|
51
|
+
{ label: 'MCP servers', value: summary.mcpServers, sub: `Plan: ${org?.tier ?? 'Free'}` },
|
|
52
|
+
].map(({ label, value, sub }) => (
|
|
53
|
+
<div key={label} style=\{{
|
|
54
|
+
background: '#111113',
|
|
55
|
+
border: '1px solid #1e1e2e',
|
|
56
|
+
borderRadius: '0.75rem',
|
|
57
|
+
padding: '1.5rem',
|
|
58
|
+
}}>
|
|
59
|
+
<p style=\{{ fontSize: '0.8125rem', color: '#71717a', marginBottom: '0.375rem' }}>{label}</p>
|
|
60
|
+
<p style=\{{ fontSize: '1.5rem', fontWeight: 700, color: '#fafafa', marginBottom: '0.25rem' }}>{value}</p>
|
|
61
|
+
<p style=\{{ fontSize: '0.75rem', color: '#52525b' }}>{sub}</p>
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{/* Quick actions */}
|
|
67
|
+
<div style=\{{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(20rem, 1fr))', gap: '1rem' }}>
|
|
68
|
+
{[
|
|
69
|
+
{
|
|
70
|
+
title: 'Register an AI agent',
|
|
71
|
+
desc: 'Assign a wallet-bound identity to a new autonomous agent. Capability-scoped, kill-switchable, fully audited.',
|
|
72
|
+
cta: 'Register agent',
|
|
73
|
+
href: '/dashboard/agents',
|
|
74
|
+
accent: '#6366f1',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
title: 'Connect an MCP server',
|
|
78
|
+
desc: 'Grant your agents access to new tools by connecting a Model Context Protocol server.',
|
|
79
|
+
cta: 'Connect server',
|
|
80
|
+
href: '/dashboard/mcp',
|
|
81
|
+
accent: '#0ea5e9',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
title: 'Invite team members',
|
|
85
|
+
desc: 'Add collaborators who can monitor agents, adjust capabilities, or trigger the kill switch.',
|
|
86
|
+
cta: 'Invite',
|
|
87
|
+
href: '/dashboard/members',
|
|
88
|
+
accent: '#10b981',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
title: 'Agent operator console',
|
|
92
|
+
desc: 'View live status, adjust capabilities, and hit the kill switch for a specific agent.',
|
|
93
|
+
cta: 'Open console',
|
|
94
|
+
href: '/admin/agent',
|
|
95
|
+
accent: '#f59e0b',
|
|
96
|
+
},
|
|
97
|
+
].map(({ title, desc, cta, href, accent }) => (
|
|
98
|
+
<div key={title} style=\{{
|
|
99
|
+
background: '#111113',
|
|
100
|
+
border: '1px solid #1e1e2e',
|
|
101
|
+
borderRadius: '0.75rem',
|
|
102
|
+
padding: '1.5rem',
|
|
103
|
+
display: 'flex',
|
|
104
|
+
flexDirection: 'column',
|
|
105
|
+
gap: '0.75rem',
|
|
106
|
+
}}>
|
|
107
|
+
<h3 style=\{{ fontSize: '0.9375rem', fontWeight: 600, color: '#fafafa' }}>{title}</h3>
|
|
108
|
+
<p style=\{{ fontSize: '0.875rem', color: '#71717a', lineHeight: 1.6, flex: 1 }}>{desc}</p>
|
|
109
|
+
<Link href={href} style=\{{
|
|
110
|
+
display: 'inline-block',
|
|
111
|
+
background: accent,
|
|
112
|
+
color: '#fff',
|
|
113
|
+
textDecoration: 'none',
|
|
114
|
+
padding: '0.5rem 1rem',
|
|
115
|
+
borderRadius: '0.375rem',
|
|
116
|
+
fontSize: '0.875rem',
|
|
117
|
+
fontWeight: 600,
|
|
118
|
+
alignSelf: 'flex-start',
|
|
119
|
+
}}>
|
|
120
|
+
{cta} →
|
|
121
|
+
</Link>
|
|
122
|
+
</div>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
{{/if}}
|
|
@@ -39,21 +39,26 @@
|
|
|
39
39
|
--danger: #dc2626;
|
|
40
40
|
|
|
41
41
|
/* Spacing — used by @githat/nextjs/styles */
|
|
42
|
-
--space-1:
|
|
43
|
-
--space-2:
|
|
44
|
-
--space-3:
|
|
45
|
-
--space-4:
|
|
46
|
-
--space-
|
|
47
|
-
--space-
|
|
42
|
+
--space-1: 0.25rem;
|
|
43
|
+
--space-2: 0.5rem;
|
|
44
|
+
--space-3: 0.75rem;
|
|
45
|
+
--space-4: 1rem;
|
|
46
|
+
--space-5: 1.25rem;
|
|
47
|
+
--space-6: 1.5rem;
|
|
48
|
+
--space-8: 2rem;
|
|
49
|
+
--space-10: 2.5rem;
|
|
50
|
+
--space-12: 3rem;
|
|
48
51
|
|
|
49
52
|
/* Radius */
|
|
50
|
-
--radius:
|
|
51
|
-
--radius-md:
|
|
52
|
-
--radius-lg:
|
|
53
|
+
--radius: 0.5rem;
|
|
54
|
+
--radius-md: 0.5rem;
|
|
55
|
+
--radius-lg: 0.75rem;
|
|
56
|
+
--radius-full: 9999px;
|
|
53
57
|
|
|
54
58
|
/* Fonts */
|
|
55
59
|
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
56
60
|
--font-wordmark: 'Instrument Serif', Georgia, serif;
|
|
61
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
@media (prefers-color-scheme: dark) {
|
|
@@ -19,10 +19,15 @@ export default function RootLayout({ children }{{#if typescript}}: { children: R
|
|
|
19
19
|
*/}
|
|
20
20
|
<GitHatProvider config=\{{
|
|
21
21
|
publishableKey: process.env.NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY || '',
|
|
22
|
-
apiUrl: '
|
|
22
|
+
apiUrl: 'https://api.githat.io',
|
|
23
|
+
{{#if typescript}}
|
|
24
|
+
tokenStorage: 'localStorage' as const,
|
|
25
|
+
{{else}}
|
|
26
|
+
tokenStorage: 'localStorage',
|
|
27
|
+
{{/if}}
|
|
23
28
|
signInUrl: '/sign-in',
|
|
24
29
|
signUpUrl: '/sign-up',
|
|
25
|
-
afterSignInUrl: '/',
|
|
30
|
+
afterSignInUrl: '/dashboard',
|
|
26
31
|
afterSignOutUrl: '/',
|
|
27
32
|
}}>
|
|
28
33
|
<header style=\{{
|
|
@@ -4,90 +4,52 @@ import Link from 'next/link';
|
|
|
4
4
|
import { useAuth } from '@githat/nextjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Landing page — split-hero layout.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* can verify an agent at /verify/agent/<wallet>.
|
|
9
|
+
* Left: pitch + CTA
|
|
10
|
+
* Right: your hero image or logo (drop a file in public/ and swap
|
|
11
|
+
* <HeroImagePlaceholder /> for <Image src="/your-image.png" ... />).
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* The <Image> tag from next/image is already imported so the swap is
|
|
14
|
+
* one line. See public/HERO_IMAGE.md for full instructions.
|
|
16
15
|
*/
|
|
17
|
-
export default function Home() {
|
|
18
|
-
const { isSignedIn } = useAuth();
|
|
19
16
|
|
|
17
|
+
function HeroImagePlaceholder() {
|
|
20
18
|
return (
|
|
21
|
-
<div style=\{{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
padding: 'var(--space-3) var(--space-6)',
|
|
45
|
-
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
46
|
-
background: 'var(--primary)',
|
|
47
|
-
color: 'var(--bg)',
|
|
48
|
-
fontWeight: 600,
|
|
49
|
-
textDecoration: 'none',
|
|
50
|
-
}}>
|
|
51
|
-
Register an agent →
|
|
52
|
-
</Link>
|
|
53
|
-
) : (
|
|
54
|
-
<Link href="/admin/agent" style=\{{
|
|
55
|
-
display: 'inline-block',
|
|
56
|
-
padding: 'var(--space-3) var(--space-6)',
|
|
57
|
-
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
58
|
-
background: 'var(--primary)',
|
|
59
|
-
color: 'var(--bg)',
|
|
60
|
-
fontWeight: 600,
|
|
61
|
-
textDecoration: 'none',
|
|
62
|
-
}}>
|
|
63
|
-
Open agent dashboard →
|
|
64
|
-
</Link>
|
|
65
|
-
)}
|
|
66
|
-
</section>
|
|
67
|
-
|
|
68
|
-
<section style=\{{ padding: 'var(--space-8) var(--space-4)', background: 'var(--surface-sub)' }}>
|
|
69
|
-
<div style=\{{ maxWidth: '48rem', margin: '0 auto' }}>
|
|
70
|
-
<h2 style=\{{ fontSize: '1.5rem', marginBottom: 'var(--space-4)' }}>What you get</h2>
|
|
71
|
-
<ul style=\{{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 'var(--space-3)' }}>
|
|
72
|
-
<Item emoji="🪪" title="Wallet-bound identity" body="Ethereum-style key pair. The agent signs requests; clients verify the signature. No shared secrets to leak." />
|
|
73
|
-
<Item emoji="🎚" title="Capability scopes" body="Each agent can only call the tools you explicitly granted. Add or revoke at any time." />
|
|
74
|
-
<Item emoji="🛑" title="Kill switch" body="One click and the agent is revoked everywhere. Existing tokens stop working immediately." />
|
|
75
|
-
<Item emoji="📜" title="Audit log" body="Every action is logged with timestamp, capability used, and signature. Prove what happened." />
|
|
76
|
-
<Item emoji="🔍" title="Public verification" body="Anyone can hit githat.io/verify/agent/<wallet> and see the agent's status, capabilities, and recent actions." />
|
|
77
|
-
</ul>
|
|
78
|
-
</div>
|
|
79
|
-
</section>
|
|
19
|
+
<div style=\{{
|
|
20
|
+
position: 'relative',
|
|
21
|
+
width: '100%',
|
|
22
|
+
aspectRatio: '4 / 3',
|
|
23
|
+
borderRadius: '1rem',
|
|
24
|
+
border: '2px dashed var(--border)',
|
|
25
|
+
background: 'var(--surface)',
|
|
26
|
+
display: 'flex',
|
|
27
|
+
flexDirection: 'column',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
justifyContent: 'center',
|
|
30
|
+
gap: '0.75rem',
|
|
31
|
+
color: 'var(--fg-muted)',
|
|
32
|
+
}}>
|
|
33
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden>
|
|
34
|
+
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
35
|
+
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
36
|
+
<path d="m21 15-5-5L5 21" />
|
|
37
|
+
</svg>
|
|
38
|
+
<p style=\{{ fontSize: '0.8125rem', textAlign: 'center', lineHeight: 1.5, maxWidth: '16rem' }}>
|
|
39
|
+
Drop your hero image in <code style=\{{ fontFamily: 'var(--font-mono)' }}>public/</code>
|
|
40
|
+
<br />See <code style=\{{ fontFamily: 'var(--font-mono)' }}>public/HERO_IMAGE.md</code>
|
|
41
|
+
</p>
|
|
80
42
|
</div>
|
|
81
43
|
);
|
|
82
44
|
}
|
|
83
45
|
|
|
84
|
-
function
|
|
46
|
+
function FeatureItem({ emoji, title, body }{{#if typescript}}: { emoji: string; title: string; body: string }{{/if}}) {
|
|
85
47
|
return (
|
|
86
48
|
<li style=\{{
|
|
87
49
|
display: 'flex',
|
|
88
50
|
gap: 'var(--space-4)',
|
|
89
51
|
padding: 'var(--space-4)',
|
|
90
|
-
borderRadius: 'var(--radius-md
|
|
52
|
+
borderRadius: 'var(--radius-md)',
|
|
91
53
|
background: 'var(--surface)',
|
|
92
54
|
}}>
|
|
93
55
|
<span style=\{{ fontSize: '1.5rem' }} aria-hidden>{emoji}</span>
|
|
@@ -98,3 +60,98 @@ function Item({ emoji, title, body }: { emoji: string; title: string; body: stri
|
|
|
98
60
|
</li>
|
|
99
61
|
);
|
|
100
62
|
}
|
|
63
|
+
|
|
64
|
+
export default function Home() {
|
|
65
|
+
const { isSignedIn } = useAuth();
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)' }}>
|
|
69
|
+
|
|
70
|
+
{/* Hero — split layout */}
|
|
71
|
+
<section style=\{{
|
|
72
|
+
display: 'grid',
|
|
73
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(18rem, 1fr))',
|
|
74
|
+
gap: 'var(--space-12)',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
padding: 'var(--space-12) var(--space-6)',
|
|
77
|
+
maxWidth: '72rem',
|
|
78
|
+
margin: '0 auto',
|
|
79
|
+
}}>
|
|
80
|
+
{/* Left — pitch */}
|
|
81
|
+
<div>
|
|
82
|
+
<h1 style=\{{
|
|
83
|
+
fontFamily: 'var(--font-wordmark)',
|
|
84
|
+
fontSize: 'clamp(2rem, 5vw, 3.25rem)',
|
|
85
|
+
lineHeight: 1.1,
|
|
86
|
+
marginBottom: 'var(--space-4)',
|
|
87
|
+
}}>
|
|
88
|
+
{{businessName}}
|
|
89
|
+
</h1>
|
|
90
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '1.125rem', lineHeight: 1.6, marginBottom: 'var(--space-6)', maxWidth: '32rem' }}>
|
|
91
|
+
Wallet-bound identity for autonomous AI agents. Capability
|
|
92
|
+
scoping, kill switch, audit trail — and a public{' '}
|
|
93
|
+
<code style=\{{ fontSize: '0.9em', fontFamily: 'var(--font-mono)' }}>/verify</code>{' '}
|
|
94
|
+
endpoint anyone can hit.
|
|
95
|
+
</p>
|
|
96
|
+
{!isSignedIn ? (
|
|
97
|
+
<div style=\{{ display: 'flex', gap: 'var(--space-3)', flexWrap: 'wrap' }}>
|
|
98
|
+
<Link href="/sign-up" style=\{{
|
|
99
|
+
display: 'inline-block',
|
|
100
|
+
padding: 'var(--space-3) var(--space-6)',
|
|
101
|
+
borderRadius: 'var(--radius-md)',
|
|
102
|
+
background: 'var(--primary)',
|
|
103
|
+
color: '#fff',
|
|
104
|
+
fontWeight: 600,
|
|
105
|
+
textDecoration: 'none',
|
|
106
|
+
}}>
|
|
107
|
+
Register an agent →
|
|
108
|
+
</Link>
|
|
109
|
+
<Link href="/sign-in" style=\{{
|
|
110
|
+
display: 'inline-block',
|
|
111
|
+
padding: 'var(--space-3) var(--space-6)',
|
|
112
|
+
borderRadius: 'var(--radius-md)',
|
|
113
|
+
border: '1px solid var(--border)',
|
|
114
|
+
color: 'var(--fg)',
|
|
115
|
+
fontWeight: 600,
|
|
116
|
+
textDecoration: 'none',
|
|
117
|
+
}}>
|
|
118
|
+
Sign in
|
|
119
|
+
</Link>
|
|
120
|
+
</div>
|
|
121
|
+
) : (
|
|
122
|
+
<Link href="/dashboard" style=\{{
|
|
123
|
+
display: 'inline-block',
|
|
124
|
+
padding: 'var(--space-3) var(--space-6)',
|
|
125
|
+
borderRadius: 'var(--radius-md)',
|
|
126
|
+
background: 'var(--primary)',
|
|
127
|
+
color: '#fff',
|
|
128
|
+
fontWeight: 600,
|
|
129
|
+
textDecoration: 'none',
|
|
130
|
+
}}>
|
|
131
|
+
Open dashboard →
|
|
132
|
+
</Link>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Right — hero image */}
|
|
137
|
+
<div>
|
|
138
|
+
<HeroImagePlaceholder />
|
|
139
|
+
</div>
|
|
140
|
+
</section>
|
|
141
|
+
|
|
142
|
+
{/* Features */}
|
|
143
|
+
<section style=\{{ padding: 'var(--space-12) var(--space-6)', background: 'var(--surface-sub)' }}>
|
|
144
|
+
<div style=\{{ maxWidth: '52rem', margin: '0 auto' }}>
|
|
145
|
+
<h2 style=\{{ fontSize: '1.5rem', marginBottom: 'var(--space-6)' }}>What you get</h2>
|
|
146
|
+
<ul style=\{{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 'var(--space-3)' }}>
|
|
147
|
+
<FeatureItem emoji="🪪" title="Wallet-bound identity" body="Ethereum-style key pair. The agent signs requests; clients verify the signature. No shared secrets." />
|
|
148
|
+
<FeatureItem emoji="🎚" title="Capability scopes" body="Each agent can only call the tools you explicitly granted. Add or revoke at any time." />
|
|
149
|
+
<FeatureItem emoji="🛑" title="Kill switch" body="One click and the agent is revoked everywhere. Existing tokens stop working immediately." />
|
|
150
|
+
<FeatureItem emoji="📜" title="Audit log" body="Every action is logged with timestamp, capability used, and signature." />
|
|
151
|
+
<FeatureItem emoji="🔍" title="Public verification" body="Anyone can hit /verify/agent/<wallet> and see the agent's current status and capabilities." />
|
|
152
|
+
</ul>
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public agent verification page.
|
|
5
|
+
* Anyone can visit /verify/agent?wallet=<wallet> to confirm an agent's
|
|
6
|
+
* identity, check its current status, and review its capabilities.
|
|
7
|
+
*
|
|
8
|
+
* Wallet is a query param (not a path) so this is a single static page
|
|
9
|
+
* that handles any wallet — output: 'export' can't pre-render an unbounded
|
|
10
|
+
* dynamic-route param space.
|
|
11
|
+
*/
|
|
12
|
+
import { Suspense, useEffect, useState } from 'react';
|
|
13
|
+
import { useSearchParams } from 'next/navigation';
|
|
14
|
+
|
|
15
|
+
const GITHAT_API = 'https://api.githat.io';
|
|
16
|
+
|
|
17
|
+
{{#if typescript}}
|
|
18
|
+
interface AgentVerify {
|
|
19
|
+
verified: boolean;
|
|
20
|
+
name: string;
|
|
21
|
+
wallet: string;
|
|
22
|
+
chainId: number;
|
|
23
|
+
status: 'active' | 'paused' | 'revoked' | 'pending';
|
|
24
|
+
verifiedSince: string;
|
|
25
|
+
lastActivity: string;
|
|
26
|
+
capabilities: string[];
|
|
27
|
+
actionsLast24h: number;
|
|
28
|
+
verificationUrl: string;
|
|
29
|
+
}
|
|
30
|
+
{{/if}}
|
|
31
|
+
|
|
32
|
+
function VerifyAgentContent() {
|
|
33
|
+
const searchParams = useSearchParams();
|
|
34
|
+
const wallet = searchParams.get('wallet') ?? '';
|
|
35
|
+
const [agent, setAgent] = useState{{#if typescript}}<AgentVerify | null>{{/if}}(null);
|
|
36
|
+
const [loading, setLoading] = useState(true);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!wallet) { setLoading(false); return; }
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
fetch(`${GITHAT_API}/verify/agent/${wallet}`)
|
|
42
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
43
|
+
.then((data) => { if (!cancelled) setAgent(data); })
|
|
44
|
+
.catch(() => { if (!cancelled) setAgent(null); })
|
|
45
|
+
.finally(() => { if (!cancelled) setLoading(false); });
|
|
46
|
+
return () => { cancelled = true; };
|
|
47
|
+
}, [wallet]);
|
|
48
|
+
|
|
49
|
+
if (loading) {
|
|
50
|
+
return (
|
|
51
|
+
<main style=\{{ minHeight: '100vh', background: '#0a0a0a', color: '#a1a1aa', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
52
|
+
<p style=\{{ fontSize: '0.875rem' }}>Loading…</p>
|
|
53
|
+
</main>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!agent?.verified) {
|
|
58
|
+
return (
|
|
59
|
+
<main style=\{{ minHeight: '100vh', background: '#0a0a0a', color: '#fafafa', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '4rem 1rem' }}>
|
|
60
|
+
<div style=\{{ textAlign: 'center' }}>
|
|
61
|
+
<p style=\{{ fontSize: '1.125rem', color: '#a1a1aa', marginBottom: '0.5rem' }}>Agent not found</p>
|
|
62
|
+
<p style=\{{ fontSize: '0.875rem', color: '#52525b', fontFamily: "'JetBrains Mono', monospace", wordBreak: 'break-all' }}>{wallet || '(no wallet provided)'}</p>
|
|
63
|
+
</div>
|
|
64
|
+
</main>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const statusColor = ({
|
|
69
|
+
active: '#16a34a',
|
|
70
|
+
paused: '#d97706',
|
|
71
|
+
revoked: '#dc2626',
|
|
72
|
+
pending: '#6366f1',
|
|
73
|
+
}{{#if typescript}} as Record<string, string>{{/if}})[agent.status] ?? '#71717a';
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<main style=\{{ minHeight: '100vh', background: '#0a0a0a', color: '#fafafa', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '4rem 1rem' }}>
|
|
77
|
+
<div style=\{{ width: '100%', maxWidth: '36rem' }}>
|
|
78
|
+
<div style=\{{ marginBottom: '2rem', textAlign: 'center' }}>
|
|
79
|
+
<div style=\{{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', padding: '0.375rem 0.875rem', borderRadius: '9999px', background: statusColor + '22', border: `1px solid ${statusColor}44`, marginBottom: '1.5rem' }}>
|
|
80
|
+
<span style=\{{ width: '0.5rem', height: '0.5rem', borderRadius: '9999px', background: statusColor, display: 'inline-block' }} />
|
|
81
|
+
<span style=\{{ fontSize: '0.75rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: statusColor }}>{agent.status}</span>
|
|
82
|
+
</div>
|
|
83
|
+
<h1 style=\{{ fontSize: '1.75rem', fontWeight: 700, marginBottom: '0.5rem' }}>{agent.name}</h1>
|
|
84
|
+
<p style=\{{ fontFamily: "'JetBrains Mono', monospace", fontSize: '0.8125rem', color: '#71717a', wordBreak: 'break-all' }}>{agent.wallet}</p>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div style=\{{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginBottom: '1.5rem' }}>
|
|
88
|
+
{[
|
|
89
|
+
{ label: 'Actions (24h)', value: agent.actionsLast24h },
|
|
90
|
+
{ label: 'Verified since', value: new Date(agent.verifiedSince).toLocaleDateString() },
|
|
91
|
+
].map(({ label, value }) => (
|
|
92
|
+
<div key={label} style=\{{ padding: '1.25rem', borderRadius: '0.75rem', border: '1px solid #27272a', background: '#18181b' }}>
|
|
93
|
+
<div style=\{{ fontSize: '0.75rem', color: '#71717a', marginBottom: '0.375rem' }}>{label}</div>
|
|
94
|
+
<div style=\{{ fontSize: '1.125rem', fontWeight: 600 }}>{value}</div>
|
|
95
|
+
</div>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{agent.capabilities.length > 0 && (
|
|
100
|
+
<div style=\{{ padding: '1.25rem', borderRadius: '0.75rem', border: '1px solid #27272a', background: '#18181b', marginBottom: '1.5rem' }}>
|
|
101
|
+
<h2 style=\{{ fontSize: '0.875rem', fontWeight: 600, color: '#a1a1aa', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '1rem' }}>Capabilities</h2>
|
|
102
|
+
<div style=\{{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
|
103
|
+
{agent.capabilities.map((cap{{#if typescript}}: string{{/if}}) => (
|
|
104
|
+
<span key={cap} style=\{{ padding: '0.375rem 0.75rem', borderRadius: '0.375rem', border: '1px solid #3f3f46', fontSize: '0.8125rem', fontFamily: "'JetBrains Mono', monospace", color: '#a1a1aa' }}>{cap}</span>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
</main>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default function VerifyAgentPage() {
|
|
115
|
+
return (
|
|
116
|
+
<Suspense fallback={
|
|
117
|
+
<main style=\{{ minHeight: '100vh', background: '#0a0a0a', color: '#a1a1aa', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
118
|
+
<p style=\{{ fontSize: '0.875rem' }}>Loading…</p>
|
|
119
|
+
</main>
|
|
120
|
+
}>
|
|
121
|
+
<VerifyAgentContent />
|
|
122
|
+
</Suspense>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
{{/if}}import { withGitHat } from '@githat/nextjs/server';
|
|
1
|
+
import { withGitHat } from '@githat/nextjs/server';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const nextConfig = {
|
|
4
|
+
output: 'export',
|
|
5
|
+
images: { unoptimized: true },
|
|
7
6
|
};
|
|
8
7
|
|
|
9
8
|
export default withGitHat(nextConfig);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Hero Image
|
|
2
|
+
|
|
3
|
+
Drop your logo or hero image (`.png`, `.jpg`, `.webp`) in this folder.
|
|
4
|
+
|
|
5
|
+
Then in `app/page.tsx`, replace `<HeroImagePlaceholder />` with:
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Image
|
|
9
|
+
src="/your-image.png"
|
|
10
|
+
alt="{{businessName}}"
|
|
11
|
+
fill
|
|
12
|
+
style={{ objectFit: 'cover', borderRadius: '1rem' }}
|
|
13
|
+
priority
|
|
14
|
+
/>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Wrap it in a `position: 'relative'` container with the same `aspectRatio: '4 / 3'` as the placeholder.
|
|
18
|
+
|
|
19
|
+
The `Image` component from `next/image` is not yet imported — add it at the top of `page.tsx`:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import Image from 'next/image';
|
|
23
|
+
```
|
|
@@ -5,13 +5,13 @@ import type { AiAgent } from './types';
|
|
|
5
5
|
{{/if}}
|
|
6
6
|
|
|
7
7
|
export const agentsApi = {
|
|
8
|
-
register: (
|
|
9
|
-
githatApi.post{{#if typescript}}<{ agent: AiAgent }>{{/if}}('/agent/register', {
|
|
8
|
+
register: (walletAddress{{#if typescript}}: string{{/if}}, chainId{{#if typescript}}: number{{/if}}, name{{#if typescript}}: string{{/if}}) =>
|
|
9
|
+
githatApi.post{{#if typescript}}<{ agent: AiAgent }>{{/if}}('/agent/register', { walletAddress, chainId, name }),
|
|
10
10
|
|
|
11
|
-
challenge: (
|
|
12
|
-
githatApi.post{{#if typescript}}<{ nonce: string }>{{/if}}('/agent/challenge', {
|
|
11
|
+
challenge: (walletAddress{{#if typescript}}: string{{/if}}) =>
|
|
12
|
+
githatApi.post{{#if typescript}}<{ nonce: string }>{{/if}}('/agent/challenge', { walletAddress }),
|
|
13
13
|
|
|
14
|
-
getToken: (
|
|
15
|
-
githatApi.post{{#if typescript}}<{ accessToken: string }>{{/if}}('/agent/token', {
|
|
14
|
+
getToken: (walletAddress{{#if typescript}}: string{{/if}}, signature{{#if typescript}}: string{{/if}}, nonce{{#if typescript}}: string{{/if}}) =>
|
|
15
|
+
githatApi.post{{#if typescript}}<{ accessToken: string }>{{/if}}('/agent/token', { walletAddress, signature, nonce }),
|
|
16
16
|
};
|
|
17
17
|
{{/if}}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{{#if includeGithatFolder}}
|
|
2
2
|
export const githatConfig = {
|
|
3
3
|
appName: '{{businessName}}',
|
|
4
|
-
publishableKey: {{#
|
|
5
|
-
// For
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
// cookies.
|
|
12
|
-
apiUrl: {{#ifEquals framework "nextjs"}}process.env.NEXT_PUBLIC_GITHAT_API_URL || '/api/githat'{{else}}import.meta.env.VITE_GITHAT_API_URL || '{{apiUrl}}'{{/ifEquals}},
|
|
4
|
+
publishableKey: {{#ifNext framework}}process.env.NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY{{else}}import.meta.env.VITE_GITHAT_PUBLISHABLE_KEY{{/ifNext}} || '',
|
|
5
|
+
// For Next.js-shaped frameworks we point at the same-origin proxy
|
|
6
|
+
// mounted at app/api/githat/[...path]/route.ts — that proxy forwards
|
|
7
|
+
// to api.githat.io and re-emits Set-Cookie on this app's domain so
|
|
8
|
+
// cookies are visible to proxy.ts and getAuth(). For react-vite we
|
|
9
|
+
// hit api.githat.io directly (browser-token flows only).
|
|
10
|
+
apiUrl: {{#ifNext framework}}process.env.NEXT_PUBLIC_GITHAT_API_URL || '/api/githat'{{else}}import.meta.env.VITE_GITHAT_API_URL || '{{apiUrl}}'{{/ifNext}},
|
|
13
11
|
signInUrl: '/sign-in',
|
|
14
12
|
signUpUrl: '/sign-up',
|
|
15
13
|
afterSignInUrl: '/dashboard',
|