create-githat-app 1.6.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 +5 -5
- 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 -1
- 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 -2
- package/templates/base/githat/dashboard/overview.tsx.hbs +106 -16
- package/templates/classroom/app/layout.tsx.hbs +6 -0
- package/templates/classroom/next.config.ts.hbs +4 -5
- package/templates/content/app/layout.tsx.hbs +6 -0
- 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 -0
- package/templates/dashboard/next.config.ts.hbs +4 -5
- package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +6 -0
- package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +5 -5
- package/templates/marketplace/app/layout.tsx.hbs +6 -0
- 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 -0
- package/templates/nextjs/next.config.ts.hbs +4 -5
- package/templates/plain/app/layout.tsx.hbs +6 -0
- package/templates/plain/next.config.ts.hbs +4 -5
- package/templates/portfolio/app/layout.tsx.hbs +6 -0
- package/templates/portfolio/next.config.ts.hbs +4 -5
- package/templates/saas/app/layout.tsx.hbs +6 -0
- package/templates/saas/next.config.ts.hbs +4 -5
- package/templates/agent/proxy.ts.hbs +0 -10
- package/templates/classroom/proxy.ts.hbs +0 -10
- package/templates/content/proxy.ts.hbs +0 -10
- package/templates/dashboard/proxy.ts.hbs +0 -10
- 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/proxy.ts.hbs +0 -10
- package/templates/nextjs/proxy.ts.hbs +0 -10
- package/templates/plain/proxy.ts.hbs +0 -10
- package/templates/portfolio/proxy.ts.hbs +0 -10
- package/templates/saas/proxy.ts.hbs +0 -10
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ var DEPS = {
|
|
|
21
21
|
next: "^16.0.0",
|
|
22
22
|
react: "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0",
|
|
24
|
-
"@githat/nextjs": "^0.
|
|
24
|
+
"@githat/nextjs": "^0.12.0",
|
|
25
25
|
"@githat/ui": "^1.0.0"
|
|
26
26
|
},
|
|
27
27
|
devDependencies: {
|
|
@@ -36,7 +36,7 @@ var DEPS = {
|
|
|
36
36
|
react: "^19.0.0",
|
|
37
37
|
"react-dom": "^19.0.0",
|
|
38
38
|
"react-router-dom": "^7.0.0",
|
|
39
|
-
"@githat/nextjs": "^0.
|
|
39
|
+
"@githat/nextjs": "^0.12.0",
|
|
40
40
|
"@githat/ui": "^1.0.0"
|
|
41
41
|
},
|
|
42
42
|
devDependencies: {
|
|
@@ -616,11 +616,11 @@ function getDefaults(projectName, publishableKey, typescript, fullstack, backend
|
|
|
616
616
|
packageManager: detectPackageManager(),
|
|
617
617
|
publishableKey: publishableKey || "",
|
|
618
618
|
apiUrl: DEFAULT_API_URL,
|
|
619
|
-
authFeatures: isMinimal ? [] : ["forgot-password"],
|
|
619
|
+
authFeatures: template === "agent" ? ["forgot-password", "email-verification", "mcp-servers", "ai-agents"] : isMinimal ? [] : ["forgot-password"],
|
|
620
620
|
databaseChoice: "none",
|
|
621
621
|
useTailwind: true,
|
|
622
|
-
includeDashboard: !isMinimal,
|
|
623
|
-
includeGithatFolder: !isMinimal && projectType === "frontend",
|
|
622
|
+
includeDashboard: template === "agent" ? true : !isMinimal,
|
|
623
|
+
includeGithatFolder: template === "agent" ? true : !isMinimal && projectType === "frontend",
|
|
624
624
|
initGit: true,
|
|
625
625
|
installDeps: true
|
|
626
626
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-githat-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "GitHat CLI — scaffold apps and manage the skills marketplace",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"figlet": "^1.8.0",
|
|
25
25
|
"fs-extra": "^11.2.0",
|
|
26
26
|
"gradient-string": "^3.0.0",
|
|
27
|
-
"handlebars": "^4.7.
|
|
27
|
+
"handlebars": "^4.7.9",
|
|
28
28
|
"ora": "^9.3.0",
|
|
29
29
|
"unzipper": "^0.12.3"
|
|
30
30
|
},
|
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
"type": "git",
|
|
62
62
|
"url": "git+https://github.com/GitHat-IO/create-githat-app.git"
|
|
63
63
|
},
|
|
64
|
+
"overrides": {
|
|
65
|
+
"picomatch": ">=4.0.4",
|
|
66
|
+
"rollup": ">=4.59.0"
|
|
67
|
+
},
|
|
64
68
|
"engines": {
|
|
65
69
|
"node": ">=20.10"
|
|
66
70
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{{#if includeForgotPassword}}
|
|
2
|
+
import { ForgotPasswordForm } from '@githat/nextjs';
|
|
3
|
+
|
|
4
|
+
export default function ForgotPasswordPage() {
|
|
5
|
+
return (
|
|
6
|
+
<main {{#if useTailwind}}className="flex items-center justify-center min-h-screen"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)' }}{{/if}}>
|
|
7
|
+
<ForgotPasswordForm signInUrl="/sign-in" />
|
|
8
|
+
</main>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
{{/if}}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{{#if includeForgotPassword}}
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import { Suspense } from 'react';
|
|
5
|
+
import { useSearchParams } from 'next/navigation';
|
|
6
|
+
import { ResetPasswordForm } from '@githat/nextjs';
|
|
7
|
+
|
|
8
|
+
function ResetPasswordContent() {
|
|
9
|
+
const searchParams = useSearchParams();
|
|
10
|
+
const token = searchParams.get('token');
|
|
11
|
+
|
|
12
|
+
if (!token) {
|
|
13
|
+
return (
|
|
14
|
+
<main {{#if useTailwind}}className="flex flex-col items-center justify-center min-h-screen gap-4"{{else}}style=\{{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', gap: '1rem', background: 'var(--bg)' }}{{/if}}>
|
|
15
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem' }}>No reset token found.</p>
|
|
16
|
+
<a href="/forgot-password" style=\{{ color: 'var(--primary)', fontSize: '0.875rem' }}>Request a new link →</a>
|
|
17
|
+
</main>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<main {{#if useTailwind}}className="flex items-center justify-center min-h-screen"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)' }}{{/if}}>
|
|
23
|
+
<ResetPasswordForm token={token} signInUrl="/sign-in" />
|
|
24
|
+
</main>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function ResetPasswordPage() {
|
|
29
|
+
return (
|
|
30
|
+
<Suspense fallback={
|
|
31
|
+
<div style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)', color: 'var(--fg-muted)', fontSize: '0.875rem' }}>
|
|
32
|
+
Loading…
|
|
33
|
+
</div>
|
|
34
|
+
}>
|
|
35
|
+
<ResetPasswordContent />
|
|
36
|
+
</Suspense>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
{{/if}}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{{#if includeEmailVerification}}
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import { Suspense } from 'react';
|
|
5
|
+
import { useSearchParams } from 'next/navigation';
|
|
6
|
+
import { VerifyEmailStatus } from '@githat/nextjs';
|
|
7
|
+
|
|
8
|
+
function VerifyEmailContent() {
|
|
9
|
+
const searchParams = useSearchParams();
|
|
10
|
+
const token = searchParams.get('token');
|
|
11
|
+
|
|
12
|
+
if (!token) {
|
|
13
|
+
return (
|
|
14
|
+
<main style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)' }}>
|
|
15
|
+
<div style=\{{ textAlign: 'center' }}>
|
|
16
|
+
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: 'var(--fg)', marginBottom: '0.5rem' }}>Invalid verification link</h1>
|
|
17
|
+
<p style=\{{ color: 'var(--fg-muted)' }}>The link may have expired. <a href="/sign-up" style=\{{ color: 'var(--primary)' }}>Try again</a>.</p>
|
|
18
|
+
</div>
|
|
19
|
+
</main>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<main {{#if useTailwind}}className="flex items-center justify-center min-h-screen"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)' }}{{/if}}>
|
|
25
|
+
<VerifyEmailStatus token={token} signInUrl="/sign-in" />
|
|
26
|
+
</main>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function VerifyEmailPage() {
|
|
31
|
+
return (
|
|
32
|
+
<Suspense fallback={
|
|
33
|
+
<div style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)', color: 'var(--fg-muted)', fontSize: '0.875rem' }}>
|
|
34
|
+
Loading…
|
|
35
|
+
</div>
|
|
36
|
+
}>
|
|
37
|
+
<VerifyEmailContent />
|
|
38
|
+
</Suspense>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
{{/if}}
|
|
@@ -1,86 +1,170 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
4
|
import Link from 'next/link';
|
|
4
5
|
import { useAuth } from '@githat/nextjs';
|
|
6
|
+
import { githatApi } from '../../../githat/api/client';
|
|
7
|
+
{{#if typescript}}
|
|
8
|
+
type AgentStatus = 'active' | 'paused' | 'revoked';
|
|
9
|
+
|
|
10
|
+
interface Agent {
|
|
11
|
+
name: string;
|
|
12
|
+
walletAddress: string;
|
|
13
|
+
status: AgentStatus;
|
|
14
|
+
capabilities: string[];
|
|
15
|
+
actionsLast24h: number;
|
|
16
|
+
verifyUrl: string;
|
|
17
|
+
}
|
|
18
|
+
{{/if}}
|
|
19
|
+
|
|
20
|
+
const STATUS_COLOR{{#if typescript}}: Record<string, string>{{/if}} = {
|
|
21
|
+
active: 'var(--success)',
|
|
22
|
+
paused: 'var(--warn)',
|
|
23
|
+
revoked: 'var(--danger)',
|
|
24
|
+
};
|
|
5
25
|
|
|
6
|
-
/**
|
|
7
|
-
* Agent operator dashboard — control the agent you registered.
|
|
8
|
-
*
|
|
9
|
-
* Three core actions an operator does:
|
|
10
|
-
* 1. See live status (active / paused / revoked)
|
|
11
|
-
* 2. Adjust capabilities (which tools the agent can call)
|
|
12
|
-
* 3. Hit the kill switch
|
|
13
|
-
*
|
|
14
|
-
* Real apps fetch the agent record from GitHat's
|
|
15
|
-
* GET /agent/{walletAddress}. Stub below shows the data shape.
|
|
16
|
-
*/
|
|
17
26
|
export default function AgentAdminPage() {
|
|
18
27
|
const { isSignedIn, isLoading } = useAuth();
|
|
28
|
+
const [agent, setAgent] = useState{{#if typescript}}<Agent | null>{{/if}}(null);
|
|
29
|
+
const [fetching, setFetching] = useState(true);
|
|
30
|
+
const [killing, setKilling] = useState(false);
|
|
31
|
+
|
|
32
|
+
const fetchAgent = useCallback(async () => {
|
|
33
|
+
try {
|
|
34
|
+
const data = await githatApi.get{{#if typescript}}<{
|
|
35
|
+
agents: Array<{
|
|
36
|
+
name: string;
|
|
37
|
+
walletAddress: string;
|
|
38
|
+
status: string;
|
|
39
|
+
allowedScopes: string[];
|
|
40
|
+
requests: number;
|
|
41
|
+
}>;
|
|
42
|
+
}>{{/if}}('/agent/list');
|
|
43
|
+
const first = data.agents[0];
|
|
44
|
+
if (first) {
|
|
45
|
+
setAgent({
|
|
46
|
+
name: first.name,
|
|
47
|
+
walletAddress: first.walletAddress,
|
|
48
|
+
status: first.status{{#if typescript}} as AgentStatus{{/if}},
|
|
49
|
+
capabilities: first.allowedScopes,
|
|
50
|
+
actionsLast24h: first.requests,
|
|
51
|
+
verifyUrl: `/verify/agent/${first.walletAddress}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// no agents registered yet
|
|
56
|
+
} finally {
|
|
57
|
+
setFetching(false);
|
|
58
|
+
}
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (isSignedIn) fetchAgent();
|
|
63
|
+
else if (!isLoading) setFetching(false);
|
|
64
|
+
}, [isSignedIn, isLoading, fetchAgent]);
|
|
65
|
+
|
|
66
|
+
const handleKillSwitch = async () => {
|
|
67
|
+
if (!agent || killing) return;
|
|
68
|
+
if (!confirm('Revoke this agent? All existing tokens stop working immediately.')) return;
|
|
69
|
+
|
|
70
|
+
setKilling(true);
|
|
71
|
+
try {
|
|
72
|
+
await githatApi.post(`/agent/${agent.walletAddress}/suspend`);
|
|
73
|
+
setAgent((prev) => prev ? { ...prev, status: 'revoked' } : prev);
|
|
74
|
+
} finally {
|
|
75
|
+
setKilling(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (isLoading || fetching) {
|
|
80
|
+
return <div style=\{{ padding: 'var(--space-8)', color: 'var(--fg-muted)', fontSize: '0.875rem' }}>Loading…</div>;
|
|
81
|
+
}
|
|
19
82
|
|
|
20
|
-
if (isLoading) return <div style=\{{ padding: 'var(--space-8)', color: 'var(--fg-muted)' }}>Loading…</div>;
|
|
21
83
|
if (!isSignedIn) {
|
|
22
84
|
return (
|
|
23
85
|
<div style=\{{ padding: 'var(--space-8)', textAlign: 'center' }}>
|
|
24
|
-
<Link href="/sign-in" style=\{{ color: 'var(--primary)' }}>Sign in →</Link>
|
|
86
|
+
<Link href="/sign-in" style=\{{ color: 'var(--primary)', fontWeight: 600 }}>Sign in →</Link>
|
|
25
87
|
</div>
|
|
26
88
|
);
|
|
27
89
|
}
|
|
28
90
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
91
|
+
if (!agent) {
|
|
92
|
+
return (
|
|
93
|
+
<div style=\{{ padding: 'var(--space-8) var(--space-6)', maxWidth: '32rem' }}>
|
|
94
|
+
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 'var(--space-3)' }}>No agent registered</h1>
|
|
95
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem', marginBottom: 'var(--space-6)' }}>
|
|
96
|
+
Register a wallet-bound agent to get started. Each agent gets an Ethereum-style
|
|
97
|
+
key pair — it signs requests so clients can verify its identity on-chain.
|
|
98
|
+
</p>
|
|
99
|
+
<Link href="/sign-up" style=\{{
|
|
100
|
+
display: 'inline-block',
|
|
101
|
+
padding: 'var(--space-3) var(--space-5)',
|
|
102
|
+
borderRadius: 'var(--radius-md)',
|
|
103
|
+
background: 'var(--primary)',
|
|
104
|
+
color: '#fff',
|
|
105
|
+
fontWeight: 600,
|
|
106
|
+
textDecoration: 'none',
|
|
107
|
+
fontSize: '0.875rem',
|
|
108
|
+
}}>
|
|
109
|
+
Register agent →
|
|
110
|
+
</Link>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
37
114
|
|
|
38
115
|
return (
|
|
39
|
-
<div style=\{{ padding: 'var(--space-8) var(--space-
|
|
40
|
-
<
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
116
|
+
<div style=\{{ padding: 'var(--space-8) var(--space-6)', maxWidth: '48rem' }}>
|
|
117
|
+
<div style=\{{ marginBottom: 'var(--space-6)' }}>
|
|
118
|
+
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 'var(--space-1)' }}>
|
|
119
|
+
{agent.name}
|
|
120
|
+
</h1>
|
|
121
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '0.8125rem', fontFamily: 'var(--font-mono)' }}>
|
|
122
|
+
{agent.walletAddress}
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
46
125
|
|
|
126
|
+
{/* Status card */}
|
|
47
127
|
<section style=\{{
|
|
48
128
|
padding: 'var(--space-5)',
|
|
49
|
-
borderRadius: 'var(--radius-
|
|
129
|
+
borderRadius: 'var(--radius-lg)',
|
|
50
130
|
border: '1px solid var(--border)',
|
|
51
131
|
background: 'var(--surface)',
|
|
52
132
|
marginBottom: 'var(--space-6)',
|
|
53
133
|
}}>
|
|
54
|
-
<div style=\{{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--space-
|
|
134
|
+
<div style=\{{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--space-4)' }}>
|
|
55
135
|
<span style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)' }}>Status</span>
|
|
56
136
|
<span style=\{{
|
|
57
137
|
padding: 'var(--space-1) var(--space-3)',
|
|
58
|
-
borderRadius: 'var(--radius-full
|
|
59
|
-
background: agent.status
|
|
60
|
-
color: '
|
|
61
|
-
fontSize: '0.
|
|
138
|
+
borderRadius: 'var(--radius-full)',
|
|
139
|
+
background: STATUS_COLOR[agent.status] ?? 'var(--border)',
|
|
140
|
+
color: '#fff',
|
|
141
|
+
fontSize: '0.6875rem',
|
|
62
142
|
fontWeight: 700,
|
|
143
|
+
textTransform: 'uppercase',
|
|
144
|
+
letterSpacing: '0.06em',
|
|
63
145
|
}}>
|
|
64
|
-
{agent.status
|
|
146
|
+
{agent.status}
|
|
65
147
|
</span>
|
|
66
148
|
</div>
|
|
67
|
-
<div style=\{{ fontSize: '2rem', fontWeight:
|
|
149
|
+
<div style=\{{ fontSize: '2rem', fontWeight: 700, marginBottom: 'var(--space-1)' }}>
|
|
68
150
|
{agent.actionsLast24h}
|
|
69
151
|
</div>
|
|
70
152
|
<div style=\{{ fontSize: '0.75rem', color: 'var(--fg-subtle)' }}>actions in the last 24 hours</div>
|
|
71
153
|
</section>
|
|
72
154
|
|
|
155
|
+
{/* Capabilities */}
|
|
73
156
|
<section style=\{{ marginBottom: 'var(--space-6)' }}>
|
|
74
|
-
<h2 style=\{{ fontSize: '
|
|
157
|
+
<h2 style=\{{ fontSize: '1rem', fontWeight: 600, marginBottom: 'var(--space-3)' }}>Capabilities</h2>
|
|
75
158
|
<ul style=\{{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexWrap: 'wrap', gap: 'var(--space-2)' }}>
|
|
76
159
|
{agent.capabilities.map((cap) => (
|
|
77
160
|
<li key={cap} style=\{{
|
|
78
161
|
padding: 'var(--space-2) var(--space-3)',
|
|
79
|
-
borderRadius: 'var(--radius-md
|
|
162
|
+
borderRadius: 'var(--radius-md)',
|
|
80
163
|
border: '1px solid var(--border)',
|
|
81
164
|
background: 'var(--surface)',
|
|
82
|
-
fontSize: '0.
|
|
83
|
-
fontFamily: 'var(--font-mono
|
|
165
|
+
fontSize: '0.8125rem',
|
|
166
|
+
fontFamily: 'var(--font-mono)',
|
|
167
|
+
color: 'var(--fg-muted)',
|
|
84
168
|
}}>
|
|
85
169
|
{cap}
|
|
86
170
|
</li>
|
|
@@ -88,39 +172,52 @@ export default function AgentAdminPage() {
|
|
|
88
172
|
</ul>
|
|
89
173
|
</section>
|
|
90
174
|
|
|
175
|
+
{/* Actions */}
|
|
91
176
|
<section style=\{{ display: 'flex', gap: 'var(--space-3)', flexWrap: 'wrap' }}>
|
|
92
|
-
<a
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
177
|
+
<a
|
|
178
|
+
href={agent.verifyUrl}
|
|
179
|
+
target="_blank"
|
|
180
|
+
rel="noopener noreferrer"
|
|
181
|
+
style=\{{
|
|
182
|
+
padding: 'var(--space-3) var(--space-4)',
|
|
183
|
+
borderRadius: 'var(--radius-md)',
|
|
184
|
+
border: '1px solid var(--border)',
|
|
185
|
+
color: 'var(--fg)',
|
|
186
|
+
textDecoration: 'none',
|
|
187
|
+
fontSize: '0.875rem',
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
99
190
|
Public verify URL ↗
|
|
100
191
|
</a>
|
|
101
192
|
<Link href="/admin/mcp" style=\{{
|
|
102
193
|
padding: 'var(--space-3) var(--space-4)',
|
|
103
|
-
borderRadius: 'var(--radius-md
|
|
194
|
+
borderRadius: 'var(--radius-md)',
|
|
104
195
|
border: '1px solid var(--border)',
|
|
105
196
|
color: 'var(--fg)',
|
|
106
197
|
textDecoration: 'none',
|
|
198
|
+
fontSize: '0.875rem',
|
|
107
199
|
}}>
|
|
108
200
|
MCP servers
|
|
109
201
|
</Link>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
202
|
+
{agent.status !== 'revoked' && (
|
|
203
|
+
<button
|
|
204
|
+
onClick={handleKillSwitch}
|
|
205
|
+
disabled={killing}
|
|
206
|
+
style=\{{
|
|
207
|
+
padding: 'var(--space-3) var(--space-4)',
|
|
208
|
+
borderRadius: 'var(--radius-md)',
|
|
209
|
+
border: '1px solid var(--danger)',
|
|
210
|
+
background: 'transparent',
|
|
211
|
+
color: 'var(--danger)',
|
|
212
|
+
fontWeight: 600,
|
|
213
|
+
fontSize: '0.875rem',
|
|
214
|
+
cursor: killing ? 'not-allowed' : 'pointer',
|
|
215
|
+
opacity: killing ? 0.6 : 1,
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
{killing ? 'Revoking…' : 'Kill switch'}
|
|
219
|
+
</button>
|
|
220
|
+
)}
|
|
124
221
|
</section>
|
|
125
222
|
</div>
|
|
126
223
|
);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
import { useAuth, UserButton } from '@githat/nextjs';
|
|
6
|
+
|
|
7
|
+
const navItems = [
|
|
8
|
+
{ label: 'Agent', href: '/admin/agent' },
|
|
9
|
+
{ label: 'MCP Servers', href: '/admin/mcp' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export default function AdminLayout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
|
|
13
|
+
const pathname = usePathname();
|
|
14
|
+
const { isSignedIn, isLoading } = useAuth();
|
|
15
|
+
|
|
16
|
+
if (isLoading) {
|
|
17
|
+
return (
|
|
18
|
+
<div style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)' }}>
|
|
19
|
+
<span style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem' }}>Loading…</span>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!isSignedIn) {
|
|
25
|
+
return (
|
|
26
|
+
<div style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg)' }}>
|
|
27
|
+
<Link href="/sign-in" style=\{{ color: 'var(--primary)', fontWeight: 600 }}>Sign in to access the admin panel →</Link>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div style=\{{ display: 'flex', minHeight: '100vh', background: 'var(--bg)' }}>
|
|
34
|
+
<aside style=\{{
|
|
35
|
+
width: '14rem',
|
|
36
|
+
borderRight: '1px solid var(--border)',
|
|
37
|
+
padding: 'var(--space-6) var(--space-4)',
|
|
38
|
+
display: 'flex',
|
|
39
|
+
flexDirection: 'column',
|
|
40
|
+
gap: 'var(--space-2)',
|
|
41
|
+
}}>
|
|
42
|
+
<Link href="/" style=\{{
|
|
43
|
+
fontFamily: 'var(--font-wordmark)',
|
|
44
|
+
fontSize: '1.125rem',
|
|
45
|
+
fontWeight: 700,
|
|
46
|
+
color: 'var(--fg)',
|
|
47
|
+
marginBottom: 'var(--space-4)',
|
|
48
|
+
display: 'block',
|
|
49
|
+
}}>
|
|
50
|
+
{{businessName}}
|
|
51
|
+
</Link>
|
|
52
|
+
<nav style=\{{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
|
|
53
|
+
{navItems.map((item) => (
|
|
54
|
+
<Link
|
|
55
|
+
key={item.href}
|
|
56
|
+
href={item.href}
|
|
57
|
+
style=\{{
|
|
58
|
+
padding: 'var(--space-2) var(--space-3)',
|
|
59
|
+
borderRadius: 'var(--radius-md)',
|
|
60
|
+
color: pathname === item.href ? 'var(--fg)' : 'var(--fg-muted)',
|
|
61
|
+
background: pathname === item.href ? 'var(--surface)' : 'transparent',
|
|
62
|
+
textDecoration: 'none',
|
|
63
|
+
fontSize: '0.875rem',
|
|
64
|
+
fontWeight: pathname === item.href ? 600 : 400,
|
|
65
|
+
border: pathname === item.href ? '1px solid var(--border)' : '1px solid transparent',
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{item.label}
|
|
69
|
+
</Link>
|
|
70
|
+
))}
|
|
71
|
+
</nav>
|
|
72
|
+
<div style=\{{ marginTop: 'auto' }}>
|
|
73
|
+
<UserButton />
|
|
74
|
+
</div>
|
|
75
|
+
</aside>
|
|
76
|
+
|
|
77
|
+
<main style=\{{ flex: 1, overflowY: 'auto' }}>
|
|
78
|
+
{children}
|
|
79
|
+
</main>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|