create-githat-app 1.7.0 → 1.8.1
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 +4 -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/README.md.hbs +2 -1
- package/templates/base/githat/api/agents.ts.hbs +6 -6
- package/templates/base/githat/auth/guard.tsx.hbs +6 -6
- package/templates/base/githat/config.ts.hbs +7 -9
- package/templates/base/githat/dashboard/layout.tsx.hbs +6 -6
- package/templates/base/githat/dashboard/overview.tsx.hbs +106 -16
- package/templates/classroom/app/layout.tsx.hbs +6 -1
- package/templates/classroom/app/projects/[id]/feedback/feedback-content.tsx.hbs +153 -0
- package/templates/classroom/app/projects/[id]/feedback/page.tsx.hbs +10 -149
- package/templates/classroom/app/projects/[id]/present/page.tsx.hbs +11 -104
- package/templates/classroom/app/projects/[id]/present/presenter-content.tsx.hbs +118 -0
- package/templates/classroom/next.config.ts.hbs +4 -5
- package/templates/content/app/layout.tsx.hbs +6 -1
- package/templates/content/app/posts/[slug]/page.tsx.hbs +10 -86
- package/templates/content/app/posts/[slug]/post-content.tsx.hbs +93 -0
- package/templates/content/next.config.ts.hbs +4 -5
- package/templates/dashboard/app/admin/data/[entity]/page.tsx.hbs +15 -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/globals.css.hbs +71 -1
- package/templates/nextjs/app/layout.tsx.hbs +9 -3
- package/templates/nextjs/app/page.tsx.hbs +6 -3
- 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
package/dist/cli.js
CHANGED
|
@@ -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
|
};
|
|
@@ -770,6 +770,7 @@ var NEXT_LIKE2 = /* @__PURE__ */ new Set([
|
|
|
770
770
|
"classroom"
|
|
771
771
|
]);
|
|
772
772
|
var MINIMAL = /* @__PURE__ */ new Set([
|
|
773
|
+
"nextjs",
|
|
773
774
|
"plain",
|
|
774
775
|
"saas",
|
|
775
776
|
"marketplace",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-githat-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
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
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useAuth } from '@githat/nextjs';
|
|
4
|
+
|
|
5
|
+
const DEMO_SERVERS = [
|
|
6
|
+
{
|
|
7
|
+
name: 'web-search',
|
|
8
|
+
description: 'Search the web and fetch pages',
|
|
9
|
+
tools: ['web.search', 'web.fetch'],
|
|
10
|
+
status: 'connected' as const,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'arxiv',
|
|
14
|
+
description: 'Read and search arXiv papers',
|
|
15
|
+
tools: ['arxiv.read', 'arxiv.search'],
|
|
16
|
+
status: 'connected' as const,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'github',
|
|
20
|
+
description: 'Read and write GitHub repos',
|
|
21
|
+
tools: ['github.read', 'github.push'],
|
|
22
|
+
status: 'disconnected' as const,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export default function McpAdminPage() {
|
|
27
|
+
const { isSignedIn, isLoading } = useAuth();
|
|
28
|
+
|
|
29
|
+
if (isLoading) return <div style=\{{ padding: 'var(--space-8)', color: 'var(--fg-muted)' }}>Loading…</div>;
|
|
30
|
+
if (!isSignedIn) return null;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div style=\{{ padding: 'var(--space-8) var(--space-6)', maxWidth: '52rem' }}>
|
|
34
|
+
<div style=\{{ marginBottom: 'var(--space-6)' }}>
|
|
35
|
+
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 'var(--space-2)' }}>MCP Servers</h1>
|
|
36
|
+
<p style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)' }}>
|
|
37
|
+
Model Context Protocol servers expose tools your agent can call. Connect servers
|
|
38
|
+
to grant your agent access to the tools they expose.
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div style=\{{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
|
|
43
|
+
{DEMO_SERVERS.map((server) => (
|
|
44
|
+
<div
|
|
45
|
+
key={server.name}
|
|
46
|
+
style=\{{
|
|
47
|
+
padding: 'var(--space-5)',
|
|
48
|
+
borderRadius: 'var(--radius-lg)',
|
|
49
|
+
border: '1px solid var(--border)',
|
|
50
|
+
background: 'var(--surface)',
|
|
51
|
+
display: 'flex',
|
|
52
|
+
alignItems: 'flex-start',
|
|
53
|
+
justifyContent: 'space-between',
|
|
54
|
+
gap: 'var(--space-4)',
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<div style=\{{ flex: 1 }}>
|
|
58
|
+
<div style=\{{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)', marginBottom: 'var(--space-2)' }}>
|
|
59
|
+
<code style=\{{ fontFamily: 'var(--font-mono)', fontSize: '0.9375rem', fontWeight: 600 }}>
|
|
60
|
+
{server.name}
|
|
61
|
+
</code>
|
|
62
|
+
<span style=\{{
|
|
63
|
+
padding: '0.125rem var(--space-2)',
|
|
64
|
+
borderRadius: 'var(--radius-full)',
|
|
65
|
+
fontSize: '0.6875rem',
|
|
66
|
+
fontWeight: 700,
|
|
67
|
+
textTransform: 'uppercase',
|
|
68
|
+
letterSpacing: '0.04em',
|
|
69
|
+
background: server.status === 'connected' ? 'var(--success)' : 'var(--border)',
|
|
70
|
+
color: server.status === 'connected' ? '#fff' : 'var(--fg-muted)',
|
|
71
|
+
}}>
|
|
72
|
+
{server.status}
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
<p style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)', marginBottom: 'var(--space-3)' }}>
|
|
76
|
+
{server.description}
|
|
77
|
+
</p>
|
|
78
|
+
<div style=\{{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-2)' }}>
|
|
79
|
+
{server.tools.map((tool) => (
|
|
80
|
+
<span
|
|
81
|
+
key={tool}
|
|
82
|
+
style=\{{
|
|
83
|
+
padding: 'var(--space-1) var(--space-2)',
|
|
84
|
+
borderRadius: 'var(--radius-md)',
|
|
85
|
+
border: '1px solid var(--border)',
|
|
86
|
+
fontSize: '0.75rem',
|
|
87
|
+
fontFamily: 'var(--font-mono)',
|
|
88
|
+
color: 'var(--fg-muted)',
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{tool}
|
|
92
|
+
</span>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
<button
|
|
97
|
+
style=\{{
|
|
98
|
+
flexShrink: 0,
|
|
99
|
+
padding: 'var(--space-2) var(--space-4)',
|
|
100
|
+
borderRadius: 'var(--radius-md)',
|
|
101
|
+
border: '1px solid var(--border)',
|
|
102
|
+
background: 'transparent',
|
|
103
|
+
color: server.status === 'connected' ? 'var(--danger)' : 'var(--primary)',
|
|
104
|
+
fontSize: '0.875rem',
|
|
105
|
+
fontWeight: 600,
|
|
106
|
+
cursor: 'pointer',
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{server.status === 'connected' ? 'Disconnect' : 'Connect'}
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div style=\{{
|
|
116
|
+
marginTop: 'var(--space-8)',
|
|
117
|
+
padding: 'var(--space-5)',
|
|
118
|
+
borderRadius: 'var(--radius-lg)',
|
|
119
|
+
border: '1px dashed var(--border)',
|
|
120
|
+
textAlign: 'center',
|
|
121
|
+
}}>
|
|
122
|
+
<p style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)', marginBottom: 'var(--space-3)' }}>
|
|
123
|
+
Connect a custom MCP server by URL
|
|
124
|
+
</p>
|
|
125
|
+
<div style=\{{ display: 'flex', gap: 'var(--space-2)', maxWidth: '28rem', margin: '0 auto' }}>
|
|
126
|
+
<input
|
|
127
|
+
type="url"
|
|
128
|
+
placeholder="https://my-mcp-server.com/sse"
|
|
129
|
+
style=\{{
|
|
130
|
+
flex: 1,
|
|
131
|
+
padding: 'var(--space-2) var(--space-3)',
|
|
132
|
+
borderRadius: 'var(--radius-md)',
|
|
133
|
+
border: '1px solid var(--border)',
|
|
134
|
+
background: 'var(--bg)',
|
|
135
|
+
color: 'var(--fg)',
|
|
136
|
+
fontSize: '0.875rem',
|
|
137
|
+
fontFamily: 'var(--font-mono)',
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
<button style=\{{
|
|
141
|
+
padding: 'var(--space-2) var(--space-4)',
|
|
142
|
+
borderRadius: 'var(--radius-md)',
|
|
143
|
+
border: 'none',
|
|
144
|
+
background: 'var(--primary)',
|
|
145
|
+
color: '#fff',
|
|
146
|
+
fontSize: '0.875rem',
|
|
147
|
+
fontWeight: 600,
|
|
148
|
+
cursor: 'pointer',
|
|
149
|
+
}}>
|
|
150
|
+
Add
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|