veryfront 0.0.46 → 0.0.48
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/ai/components.js +3 -3
- package/dist/ai/components.js.map +2 -2
- package/dist/ai/index.d.ts +7 -2
- package/dist/ai/index.js +21 -5
- package/dist/ai/index.js.map +2 -2
- package/dist/cli.js +267 -70
- package/dist/components.js +1 -1
- package/dist/components.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/data.js +1 -1
- package/dist/data.js.map +1 -1
- package/dist/index.js +32 -31
- package/dist/index.js.map +2 -2
- package/dist/integrations/_base/files/app/api/auth/status/route.ts +30 -0
- package/dist/integrations/_base/files/app/components/ServiceConnections.tsx +153 -0
- package/dist/integrations/_base/files/lib/token-store.ts +5 -1
- package/dist/integrations/calendar/connector.json +7 -1
- package/dist/integrations/calendar/files/_env.example +29 -0
- package/dist/integrations/calendar/files/ai/tools/create-event.ts +2 -6
- package/dist/integrations/calendar/files/ai/tools/find-free-time.ts +2 -6
- package/dist/integrations/calendar/files/ai/tools/list-events.ts +2 -6
- package/dist/integrations/calendar/files/lib/token-store.ts +5 -1
- package/dist/integrations/github/files/ai/tools/create-issue.ts +2 -6
- package/dist/integrations/github/files/ai/tools/get-pr-diff.ts +2 -6
- package/dist/integrations/github/files/ai/tools/list-prs.ts +2 -6
- package/dist/integrations/github/files/ai/tools/list-repos.ts +2 -6
- package/dist/integrations/github/files/lib/token-store.ts +5 -1
- package/dist/integrations/gmail/connector.json +7 -1
- package/dist/integrations/gmail/files/_env.example +29 -0
- package/dist/integrations/gmail/files/ai/tools/list-emails.ts +2 -6
- package/dist/integrations/gmail/files/ai/tools/search-emails.ts +2 -6
- package/dist/integrations/gmail/files/ai/tools/send-email.ts +2 -6
- package/dist/integrations/gmail/files/lib/token-store.ts +5 -1
- package/dist/integrations/slack/files/ai/tools/get-messages.ts +2 -6
- package/dist/integrations/slack/files/ai/tools/list-channels.ts +2 -6
- package/dist/integrations/slack/files/ai/tools/send-message.ts +2 -6
- package/dist/integrations/slack/files/lib/token-store.ts +5 -1
- package/dist/templates/ai/app/api/chat/route.ts +8 -1
- package/dist/templates/ai/app/page.tsx +6 -5
- package/dist/templates/ai/tsconfig.json +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { tokenStore } from "../../../../lib/token-store.ts";
|
|
2
|
+
|
|
3
|
+
// Services to check - add/remove based on your integrations
|
|
4
|
+
// Gmail and Calendar share OAuth credentials, so we check both separately
|
|
5
|
+
const SERVICES = [
|
|
6
|
+
{ id: "gmail", name: "Gmail" },
|
|
7
|
+
{ id: "calendar", name: "Calendar" },
|
|
8
|
+
// { id: 'slack', name: 'Slack' },
|
|
9
|
+
// { id: 'github', name: 'GitHub' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export async function GET() {
|
|
13
|
+
// In production, get userId from session/cookie
|
|
14
|
+
// For development, we use a default user
|
|
15
|
+
const userId = "current-user";
|
|
16
|
+
|
|
17
|
+
const services: Record<string, boolean> = {};
|
|
18
|
+
|
|
19
|
+
for (const service of SERVICES) {
|
|
20
|
+
try {
|
|
21
|
+
services[service.id] = await tokenStore.isConnected(userId, service.id);
|
|
22
|
+
} catch {
|
|
23
|
+
services[service.id] = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return new Response(JSON.stringify({ services }), {
|
|
28
|
+
headers: { "Content-Type": "application/json" },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
interface Service {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
connected: boolean;
|
|
9
|
+
authUrl: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ServiceConnectionsProps {
|
|
13
|
+
services: Array<{
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
authUrl: string;
|
|
17
|
+
}>;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ServiceConnections({ services, className = "" }: ServiceConnectionsProps) {
|
|
22
|
+
const [status, setStatus] = useState<Record<string, boolean>>({});
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
async function checkStatus() {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch("/api/auth/status");
|
|
29
|
+
if (res.ok) {
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
setStatus(data.services || {});
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error("Failed to check service status:", err);
|
|
35
|
+
} finally {
|
|
36
|
+
setLoading(false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
checkStatus();
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const servicesWithStatus: Service[] = services.map((s) => ({
|
|
43
|
+
...s,
|
|
44
|
+
connected: status[s.id] ?? false,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const connectedCount = servicesWithStatus.filter((s) => s.connected).length;
|
|
48
|
+
|
|
49
|
+
if (loading) {
|
|
50
|
+
return (
|
|
51
|
+
<div className={`flex items-center gap-2 ${className}`}>
|
|
52
|
+
<div className="animate-pulse h-6 w-32 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className={`flex items-center gap-2 ${className}`}>
|
|
59
|
+
{servicesWithStatus.map((service) => <ServiceBadge key={service.id} service={service} />)}
|
|
60
|
+
{connectedCount < services.length && (
|
|
61
|
+
<span className="text-xs text-neutral-500 dark:text-neutral-400 ml-1">
|
|
62
|
+
{connectedCount}/{services.length} connected
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ServiceBadge({ service }: { service: Service }) {
|
|
70
|
+
const handleConnect = () => {
|
|
71
|
+
window.location.href = service.authUrl;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (service.connected) {
|
|
75
|
+
return (
|
|
76
|
+
<span
|
|
77
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
|
78
|
+
title={`${service.name} connected`}
|
|
79
|
+
>
|
|
80
|
+
<span className="w-1.5 h-1.5 rounded-full bg-green-500" />
|
|
81
|
+
{service.name}
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<button
|
|
88
|
+
onClick={handleConnect}
|
|
89
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-neutral-100 text-neutral-600 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700 transition-colors"
|
|
90
|
+
title={`Connect ${service.name}`}
|
|
91
|
+
>
|
|
92
|
+
<span className="w-1.5 h-1.5 rounded-full bg-neutral-400" />
|
|
93
|
+
{service.name}
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function ServiceConnectionsCard({ services, className = "" }: ServiceConnectionsProps) {
|
|
99
|
+
const [status, setStatus] = useState<Record<string, boolean>>({});
|
|
100
|
+
const [loading, setLoading] = useState(true);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
async function checkStatus() {
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch("/api/auth/status");
|
|
106
|
+
if (res.ok) {
|
|
107
|
+
const data = await res.json();
|
|
108
|
+
setStatus(data.services || {});
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error("Failed to check service status:", err);
|
|
112
|
+
} finally {
|
|
113
|
+
setLoading(false);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
checkStatus();
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
const servicesWithStatus: Service[] = services.map((s) => ({
|
|
120
|
+
...s,
|
|
121
|
+
connected: status[s.id] ?? false,
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
const disconnectedServices = servicesWithStatus.filter((s) => !s.connected);
|
|
125
|
+
|
|
126
|
+
if (loading || disconnectedServices.length === 0) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div
|
|
132
|
+
className={`rounded-lg border border-amber-200 dark:border-amber-900/50 bg-amber-50 dark:bg-amber-900/20 p-4 ${className}`}
|
|
133
|
+
>
|
|
134
|
+
<h3 className="font-medium text-amber-900 dark:text-amber-200 mb-2">
|
|
135
|
+
Connect your services
|
|
136
|
+
</h3>
|
|
137
|
+
<p className="text-sm text-amber-700 dark:text-amber-300/80 mb-3">
|
|
138
|
+
Connect the following services to unlock all features:
|
|
139
|
+
</p>
|
|
140
|
+
<div className="flex flex-wrap gap-2">
|
|
141
|
+
{disconnectedServices.map((service) => (
|
|
142
|
+
<a
|
|
143
|
+
key={service.id}
|
|
144
|
+
href={service.authUrl}
|
|
145
|
+
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium bg-amber-100 text-amber-800 hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60 transition-colors"
|
|
146
|
+
>
|
|
147
|
+
Connect {service.name}
|
|
148
|
+
</a>
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -21,7 +21,11 @@ export interface TokenStore {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// In-memory storage for development
|
|
24
|
-
|
|
24
|
+
// Use globalThis to share across esbuild bundles (each API route is bundled separately)
|
|
25
|
+
const TOKENS_KEY = "__veryfront_oauth_tokens__";
|
|
26
|
+
// deno-lint-ignore no-explicit-any
|
|
27
|
+
const globalStore = globalThis as any;
|
|
28
|
+
const tokens: Map<string, OAuthToken> = globalStore[TOKENS_KEY] ||= new Map<string, OAuthToken>();
|
|
25
29
|
|
|
26
30
|
function getKey(userId: string, service: string): string {
|
|
27
31
|
return `${userId}:${service}`;
|
|
@@ -12,7 +12,13 @@
|
|
|
12
12
|
"https://www.googleapis.com/auth/calendar.readonly",
|
|
13
13
|
"https://www.googleapis.com/auth/calendar.events"
|
|
14
14
|
],
|
|
15
|
-
"callbackPath": "/api/auth/calendar/callback"
|
|
15
|
+
"callbackPath": "/api/auth/calendar/callback",
|
|
16
|
+
"requiredApis": [
|
|
17
|
+
{
|
|
18
|
+
"name": "Google Calendar API",
|
|
19
|
+
"enableUrl": "https://console.cloud.google.com/apis/library/calendar-json.googleapis.com"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
16
22
|
},
|
|
17
23
|
"envVars": [
|
|
18
24
|
{
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Google Calendar Integration Setup
|
|
3
|
+
# =============================================================================
|
|
4
|
+
#
|
|
5
|
+
# STEP 1: Create a Google Cloud Project
|
|
6
|
+
# Visit: https://console.cloud.google.com/projectcreate
|
|
7
|
+
#
|
|
8
|
+
# STEP 2: Enable the Google Calendar API
|
|
9
|
+
# Visit: https://console.cloud.google.com/apis/library/calendar-json.googleapis.com
|
|
10
|
+
# Click "Enable" to activate the Calendar API for your project
|
|
11
|
+
#
|
|
12
|
+
# STEP 3: Configure OAuth Consent Screen
|
|
13
|
+
# Visit: https://console.cloud.google.com/apis/credentials/consent
|
|
14
|
+
# - Choose "External" user type (or "Internal" for Workspace)
|
|
15
|
+
# - Fill in app name, support email
|
|
16
|
+
# - Add scopes: calendar.readonly, calendar.events
|
|
17
|
+
# - Add your email as a test user (required for development)
|
|
18
|
+
#
|
|
19
|
+
# STEP 4: Create OAuth Credentials
|
|
20
|
+
# Visit: https://console.cloud.google.com/apis/credentials
|
|
21
|
+
# - Click "Create Credentials" > "OAuth client ID"
|
|
22
|
+
# - Application type: "Web application"
|
|
23
|
+
# - Add Authorized redirect URI: http://localhost:3000/api/auth/calendar/callback
|
|
24
|
+
# - Copy the Client ID and Client Secret below
|
|
25
|
+
#
|
|
26
|
+
# =============================================================================
|
|
27
|
+
|
|
28
|
+
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
|
29
|
+
GOOGLE_CLIENT_SECRET=your-client-secret
|
|
@@ -37,12 +37,8 @@ export default tool({
|
|
|
37
37
|
{ title, startTime, endTime, description, location, attendees, timeZone },
|
|
38
38
|
context,
|
|
39
39
|
) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
error: "User not authenticated. Please log in first.",
|
|
44
|
-
};
|
|
45
|
-
}
|
|
40
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
41
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
46
42
|
|
|
47
43
|
try {
|
|
48
44
|
const calendar = createCalendarClient(userId);
|
|
@@ -24,12 +24,8 @@ export default tool({
|
|
|
24
24
|
.describe("Only show slots during working hours (9 AM - 6 PM)"),
|
|
25
25
|
}),
|
|
26
26
|
execute: async ({ durationMinutes, daysToSearch, workingHoursOnly }, context) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
error: "User not authenticated. Please log in first.",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
27
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
28
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
33
29
|
|
|
34
30
|
try {
|
|
35
31
|
const calendar = createCalendarClient(userId);
|
|
@@ -24,12 +24,8 @@ export default tool({
|
|
|
24
24
|
.describe("Only show events for today"),
|
|
25
25
|
}),
|
|
26
26
|
execute: async ({ maxResults, daysAhead, todayOnly }, context) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
error: "User not authenticated. Please log in first.",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
27
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
28
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
33
29
|
|
|
34
30
|
try {
|
|
35
31
|
const calendar = createCalendarClient(userId);
|
|
@@ -21,7 +21,11 @@ export interface TokenStore {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// In-memory storage for development
|
|
24
|
-
|
|
24
|
+
// Use globalThis to share across esbuild bundles (each API route is bundled separately)
|
|
25
|
+
const TOKENS_KEY = "__veryfront_oauth_tokens__";
|
|
26
|
+
// deno-lint-ignore no-explicit-any
|
|
27
|
+
const globalStore = globalThis as any;
|
|
28
|
+
const tokens: Map<string, OAuthToken> = globalStore[TOKENS_KEY] ||= new Map<string, OAuthToken>();
|
|
25
29
|
|
|
26
30
|
function getKey(userId: string, service: string): string {
|
|
27
31
|
return `${userId}:${service}`;
|
|
@@ -27,12 +27,8 @@ export default tool({
|
|
|
27
27
|
.describe("GitHub usernames to assign to the issue"),
|
|
28
28
|
}),
|
|
29
29
|
execute: async ({ repo, title, body, labels, assignees }, context) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
error: "User not authenticated. Please log in first.",
|
|
34
|
-
};
|
|
35
|
-
}
|
|
30
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
31
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
36
32
|
|
|
37
33
|
const [owner, repoName] = repo.split("/");
|
|
38
34
|
if (!owner || !repoName) {
|
|
@@ -16,12 +16,8 @@ export default tool({
|
|
|
16
16
|
.describe("Pull request number"),
|
|
17
17
|
}),
|
|
18
18
|
execute: async ({ repo, prNumber }, context) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
error: "User not authenticated. Please log in first.",
|
|
23
|
-
};
|
|
24
|
-
}
|
|
19
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
20
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
25
21
|
|
|
26
22
|
const [owner, repoName] = repo.split("/");
|
|
27
23
|
if (!owner || !repoName) {
|
|
@@ -21,12 +21,8 @@ export default tool({
|
|
|
21
21
|
.describe("Maximum number of pull requests to return"),
|
|
22
22
|
}),
|
|
23
23
|
execute: async ({ repo, state, limit }, context) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
error: "User not authenticated. Please log in first.",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
24
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
25
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
30
26
|
|
|
31
27
|
const [owner, repoName] = repo.split("/");
|
|
32
28
|
if (!owner || !repoName) {
|
|
@@ -22,12 +22,8 @@ export default tool({
|
|
|
22
22
|
.describe("Maximum number of repositories to return"),
|
|
23
23
|
}),
|
|
24
24
|
execute: async ({ type, sort, limit }, context) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
error: "User not authenticated. Please log in first.",
|
|
29
|
-
};
|
|
30
|
-
}
|
|
25
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
26
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
31
27
|
|
|
32
28
|
try {
|
|
33
29
|
const github = createGitHubClient(userId);
|
|
@@ -21,7 +21,11 @@ export interface TokenStore {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// In-memory storage for development
|
|
24
|
-
|
|
24
|
+
// Use globalThis to share across esbuild bundles (each API route is bundled separately)
|
|
25
|
+
const TOKENS_KEY = "__veryfront_oauth_tokens__";
|
|
26
|
+
// deno-lint-ignore no-explicit-any
|
|
27
|
+
const globalStore = globalThis as any;
|
|
28
|
+
const tokens: Map<string, OAuthToken> = globalStore[TOKENS_KEY] ||= new Map<string, OAuthToken>();
|
|
25
29
|
|
|
26
30
|
function getKey(userId: string, service: string): string {
|
|
27
31
|
return `${userId}:${service}`;
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
"https://www.googleapis.com/auth/gmail.send",
|
|
14
14
|
"https://www.googleapis.com/auth/gmail.modify"
|
|
15
15
|
],
|
|
16
|
-
"callbackPath": "/api/auth/gmail/callback"
|
|
16
|
+
"callbackPath": "/api/auth/gmail/callback",
|
|
17
|
+
"requiredApis": [
|
|
18
|
+
{
|
|
19
|
+
"name": "Gmail API",
|
|
20
|
+
"enableUrl": "https://console.cloud.google.com/apis/library/gmail.googleapis.com"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
17
23
|
},
|
|
18
24
|
"envVars": [
|
|
19
25
|
{
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Gmail Integration Setup
|
|
3
|
+
# =============================================================================
|
|
4
|
+
#
|
|
5
|
+
# STEP 1: Create a Google Cloud Project
|
|
6
|
+
# Visit: https://console.cloud.google.com/projectcreate
|
|
7
|
+
#
|
|
8
|
+
# STEP 2: Enable the Gmail API
|
|
9
|
+
# Visit: https://console.cloud.google.com/apis/library/gmail.googleapis.com
|
|
10
|
+
# Click "Enable" to activate the Gmail API for your project
|
|
11
|
+
#
|
|
12
|
+
# STEP 3: Configure OAuth Consent Screen
|
|
13
|
+
# Visit: https://console.cloud.google.com/apis/credentials/consent
|
|
14
|
+
# - Choose "External" user type (or "Internal" for Workspace)
|
|
15
|
+
# - Fill in app name, support email
|
|
16
|
+
# - Add scopes: gmail.readonly, gmail.send, gmail.modify
|
|
17
|
+
# - Add your email as a test user (required for development)
|
|
18
|
+
#
|
|
19
|
+
# STEP 4: Create OAuth Credentials
|
|
20
|
+
# Visit: https://console.cloud.google.com/apis/credentials
|
|
21
|
+
# - Click "Create Credentials" > "OAuth client ID"
|
|
22
|
+
# - Application type: "Web application"
|
|
23
|
+
# - Add Authorized redirect URI: http://localhost:3000/api/auth/gmail/callback
|
|
24
|
+
# - Copy the Client ID and Client Secret below
|
|
25
|
+
#
|
|
26
|
+
# =============================================================================
|
|
27
|
+
|
|
28
|
+
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
|
29
|
+
GOOGLE_CLIENT_SECRET=your-client-secret
|
|
@@ -23,12 +23,8 @@ export default tool({
|
|
|
23
23
|
.describe("Filter by Gmail label (e.g., 'INBOX', 'IMPORTANT', 'STARRED')"),
|
|
24
24
|
}),
|
|
25
25
|
execute: async ({ maxResults, unreadOnly, label }, context) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
error: "User not authenticated. Please log in first.",
|
|
30
|
-
};
|
|
31
|
-
}
|
|
26
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
27
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
32
28
|
|
|
33
29
|
try {
|
|
34
30
|
const gmail = createGmailClient(userId);
|
|
@@ -21,12 +21,8 @@ export default tool({
|
|
|
21
21
|
.describe("Maximum number of results to return"),
|
|
22
22
|
}),
|
|
23
23
|
execute: async ({ query, maxResults }, context) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
error: "User not authenticated. Please log in first.",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
24
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
25
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
30
26
|
|
|
31
27
|
try {
|
|
32
28
|
const gmail = createGmailClient(userId);
|
|
@@ -31,12 +31,8 @@ export default tool({
|
|
|
31
31
|
.describe("Whether the body contains HTML"),
|
|
32
32
|
}),
|
|
33
33
|
execute: async ({ to, subject, body, cc, bcc, isHtml }, context) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
error: "User not authenticated. Please log in first.",
|
|
38
|
-
};
|
|
39
|
-
}
|
|
34
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
35
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
40
36
|
|
|
41
37
|
try {
|
|
42
38
|
const gmail = createGmailClient(userId);
|
|
@@ -21,7 +21,11 @@ export interface TokenStore {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// In-memory storage for development
|
|
24
|
-
|
|
24
|
+
// Use globalThis to share across esbuild bundles (each API route is bundled separately)
|
|
25
|
+
const TOKENS_KEY = "__veryfront_oauth_tokens__";
|
|
26
|
+
// deno-lint-ignore no-explicit-any
|
|
27
|
+
const globalStore = globalThis as any;
|
|
28
|
+
const tokens: Map<string, OAuthToken> = globalStore[TOKENS_KEY] ||= new Map<string, OAuthToken>();
|
|
25
29
|
|
|
26
30
|
function getKey(userId: string, service: string): string {
|
|
27
31
|
return `${userId}:${service}`;
|
|
@@ -17,12 +17,8 @@ export default tool({
|
|
|
17
17
|
.describe("Maximum number of messages to return"),
|
|
18
18
|
}),
|
|
19
19
|
execute: async ({ channel, limit }, context) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
error: "User not authenticated. Please log in first.",
|
|
24
|
-
};
|
|
25
|
-
}
|
|
20
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
21
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
26
22
|
|
|
27
23
|
try {
|
|
28
24
|
const slack = createSlackClient(userId);
|
|
@@ -18,12 +18,8 @@ export default tool({
|
|
|
18
18
|
.describe("Exclude archived channels"),
|
|
19
19
|
}),
|
|
20
20
|
execute: async ({ limit, excludeArchived }, context) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
error: "User not authenticated. Please log in first.",
|
|
25
|
-
};
|
|
26
|
-
}
|
|
21
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
22
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
27
23
|
|
|
28
24
|
try {
|
|
29
25
|
const slack = createSlackClient(userId);
|
|
@@ -19,12 +19,8 @@ export default tool({
|
|
|
19
19
|
.describe("Thread timestamp to reply to (for threaded messages)"),
|
|
20
20
|
}),
|
|
21
21
|
execute: async ({ channel, text, threadTs }, context) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
error: "User not authenticated. Please log in first.",
|
|
26
|
-
};
|
|
27
|
-
}
|
|
22
|
+
// Default to "current-user" for development; in production, always pass userId from session
|
|
23
|
+
const userId = (context?.userId as string | undefined) || "current-user";
|
|
28
24
|
|
|
29
25
|
try {
|
|
30
26
|
const slack = createSlackClient(userId);
|
|
@@ -21,7 +21,11 @@ export interface TokenStore {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// In-memory storage for development
|
|
24
|
-
|
|
24
|
+
// Use globalThis to share across esbuild bundles (each API route is bundled separately)
|
|
25
|
+
const TOKENS_KEY = "__veryfront_oauth_tokens__";
|
|
26
|
+
// deno-lint-ignore no-explicit-any
|
|
27
|
+
const globalStore = globalThis as any;
|
|
28
|
+
const tokens: Map<string, OAuthToken> = globalStore[TOKENS_KEY] ||= new Map<string, OAuthToken>();
|
|
25
29
|
|
|
26
30
|
function getKey(userId: string, service: string): string {
|
|
27
31
|
return `${userId}:${service}`;
|
|
@@ -20,7 +20,14 @@ export async function POST(request: Request) {
|
|
|
20
20
|
return Response.json({ error: "Agent not found" }, { status: 404 });
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// In production, extract userId from session/cookie
|
|
24
|
+
// For development, we use a default user
|
|
25
|
+
const userId = "current-user";
|
|
26
|
+
|
|
27
|
+
const result = await agent.stream({
|
|
28
|
+
messages,
|
|
29
|
+
context: { userId },
|
|
30
|
+
});
|
|
24
31
|
return result.toDataStreamResponse();
|
|
25
32
|
} catch (error) {
|
|
26
33
|
if (error instanceof z.ZodError) {
|
|
@@ -8,15 +8,16 @@ export default function ChatPage() {
|
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
10
|
<div className="flex flex-col h-screen bg-white dark:bg-neutral-900">
|
|
11
|
-
{/* Header -
|
|
12
|
-
<header className="flex-shrink-0 border-b border-neutral-200 dark:border-neutral-800">
|
|
13
|
-
<div className="
|
|
11
|
+
{/* Header - sticky at top, full width */}
|
|
12
|
+
<header className="sticky top-0 z-10 flex-shrink-0 border-b border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900">
|
|
13
|
+
<div className="px-4 py-3 flex items-center justify-between">
|
|
14
14
|
<h1 className="font-medium text-neutral-900 dark:text-white">AI Assistant</h1>
|
|
15
|
+
{/* Add ServiceConnections here when integrations are added */}
|
|
15
16
|
</div>
|
|
16
17
|
</header>
|
|
17
18
|
|
|
18
|
-
{/* Chat */}
|
|
19
|
-
<Chat {...chat} className="flex-1" placeholder="Message" />
|
|
19
|
+
{/* Chat - fills remaining space with scrollable content */}
|
|
20
|
+
<Chat {...chat} className="flex-1 min-h-0" placeholder="Message" />
|
|
20
21
|
</div>
|
|
21
22
|
)
|
|
22
23
|
}
|