Skip to content

Commit b3595c7

Browse files
committed
More experimentation
1 parent c8e9db5 commit b3595c7

9 files changed

Lines changed: 400 additions & 267 deletions

File tree

claude-agent-github-wiki/.env.example

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ ANTHROPIC_API_KEY=your_anthropic_api_key
1111
# Get these from your Supabase project settings
1212
# https://app.supabase.com/project/_/settings/api
1313
SUPABASE_URL=your_supabase_project_url
14-
SUPABASE_PRIVATE_KEY=your_supabase_service_role_jwt # Backend only - JWT token (starts with eyJ...) from "service_role" in API settings, NOT the database secret!
1514

16-
# Public Supabase keys for frontend (safe to expose)
15+
# Public Supabase keys (safe to expose, used by both frontend and backend)
1716
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url # Same as SUPABASE_URL
18-
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key # Found under "anon public" in API settings
17+
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY=your_supabase_publishable_key # Publishable key for Realtime broadcasts (both client & server use this for listening)
18+
19+
# Optional: Only needed if sending broadcasts FROM the backend (not used in this app)
20+
# SUPABASE_SECRET_KEY=your_secret_key # Secret key - only for sending broadcasts from trusted server

claude-agent-github-wiki/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ ANTHROPIC_API_KEY=your_anthropic_api_key
6060
SUPABASE_URL=your_supabase_project_url
6161
SUPABASE_ANON_KEY=your_supabase_anon_key
6262
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
63-
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
63+
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY=your_supabase_anon_key
6464
```
6565

6666
### Installation

claude-agent-github-wiki/app/chat/[runId]/page.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,32 +100,25 @@ export default function ChatPage({ params }: { params: { runId: string } }) {
100100

101101
console.log("[Chat] Setting up Supabase channel for sessionId:", sessionId);
102102

103+
const channelName = `session:${sessionId}`;
104+
103105
// Create channel for broadcasting questions
104106
// NOTE: Clean up console.logs after debugging
105-
const channel = supabase.channel(`session:${sessionId}`, {
107+
const channel = supabase.channel(channelName, {
106108
config: {
109+
private: false, // Use private channel with RLS
107110
broadcast: {
108111
self: false,
109-
ack: false, // Match backend configuration
112+
ack: true,
110113
},
111114
},
112115
});
113116

114-
// Subscribe to channel (no need to listen, just for sending)
115-
channel.subscribe((status) => {
116-
console.log(`[Chat] Channel subscription status: ${status}`, {
117-
sessionId,
118-
channelName: `session:${sessionId}`,
119-
});
120-
121-
if (status === "SUBSCRIBED") {
122-
console.log("[Chat] ✅ Successfully subscribed to channel");
123-
channelRef.current = channel;
124-
} else if (status === "CHANNEL_ERROR") {
125-
console.error("[Chat] ❌ Channel subscription error");
126-
setError("Failed to connect to chat channel");
127-
}
128-
});
117+
channel
118+
.on("broadcast", { event: "question" }, (payload) => {
119+
console.log(`[Chat] Channel received question: ${payload}`);
120+
})
121+
.subscribe();
129122

130123
// Cleanup on unmount
131124
return () => {
@@ -171,15 +164,16 @@ export default function ChatPage({ params }: { params: { runId: string } }) {
171164

172165
// Send question directly via Supabase broadcast
173166
// NOTE: Clean up console.logs after debugging
174-
const result = await channelRef.current.send({
167+
const result = await supabase.channel(`session:${sessionId}`).send({
175168
type: "broadcast",
176169
event: "question",
177170
payload: {
178-
question,
171+
question: question,
179172
timestamp: new Date().toISOString(),
180173
messageId: userMessage.id,
181174
},
182175
});
176+
console.log("[Chat] result: ", result);
183177

184178
if (result === "ok") {
185179
console.log("[Chat] ✅ Question broadcast successfully");

claude-agent-github-wiki/lib/supabase.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import { createClient } from "@supabase/supabase-js";
44
// NOTE: Clean up console.logs after debugging
55
export const supabase = createClient(
66
process.env.NEXT_PUBLIC_SUPABASE_URL!,
7-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
7+
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY!,
88
{
99
realtime: {
1010
params: {
1111
eventsPerSecond: 10,
1212
},
1313
},
14-
}
14+
},
1515
);
1616

17-
console.log("[supabase.ts] Client initialized with URL:", process.env.NEXT_PUBLIC_SUPABASE_URL);
17+
console.log(
18+
"[supabase.ts] Client initialized with URL:",
19+
process.env.NEXT_PUBLIC_SUPABASE_URL,
20+
);

claude-agent-github-wiki/repo-wiki-chat.mdx

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Developers can learn advanced Trigger.dev patterns (build extensions, long-runni
1919
## Target Users
2020

2121
Developers building production AI applications with Trigger.dev v4, especially those needing:
22+
2223
- Long-running stateful tasks
2324
- Build extensions for system dependencies
2425
- Hybrid realtime architectures
@@ -31,12 +32,14 @@ Developers building production AI applications with Trigger.dev v4, especially t
3132
### Two-Task System
3233

3334
**Task 1: clone-repo**
35+
3436
- Uses build extension to install git via apt-get
3537
- Performs shallow clone (depth=1)
3638
- Generates unique sessionId
3739
- Returns tempDir, sessionId, repoName
3840

3941
**Task 2: repo-chat-session**
42+
4043
- Long-running (60 minute timeout)
4144
- Maintains cloned repo in memory
4245
- Listens for questions via Supabase Broadcast (control plane)
@@ -51,6 +54,7 @@ Task 2 → Trigger.dev Streams v2 → Frontend (response)
5154
```
5255

5356
**Why this architecture?**
57+
5458
- Repo stays in memory = no re-cloning
5559
- Supabase handles bidirectional communication
5660
- Trigger.dev Streams v2 handles main data flow
@@ -85,7 +89,9 @@ export const cloneRepo = schemaTask({
8589
const sessionId = generateSessionId();
8690

8791
// Shallow clone with build extension git
88-
await execAsync(`git clone --depth=1 --single-branch "${githubUrl}" "${tempDir}/repo"`);
92+
await execAsync(
93+
`git clone --depth=1 --single-branch "${githubUrl}" "${tempDir}/repo"`
94+
);
8995

9096
return { tempDir: join(tempDir, "repo"), sessionId, repoName };
9197
},
@@ -110,17 +116,19 @@ export const repoChatSession = schemaTask({
110116
// Supabase for receiving questions
111117
const channel = supabase.channel(`session:${sessionId}`);
112118

113-
channel.on('broadcast', { event: 'question' }, async ({ payload }) => {
114-
const result = query({
115-
prompt: payload.question,
116-
options: { cwd: tempDir, /* Claude options */ }
117-
});
119+
channel
120+
.on("broadcast", { event: "question" }, async ({ payload }) => {
121+
const result = query({
122+
prompt: payload.question,
123+
options: { cwd: tempDir /* Claude options */ },
124+
});
118125

119-
// Stream all responses via Trigger.dev
120-
for await (const message of result) {
121-
streamHandle.write(message);
122-
}
123-
}).subscribe();
126+
// Stream all responses via Trigger.dev
127+
for await (const message of result) {
128+
streamHandle.write(message);
129+
}
130+
})
131+
.subscribe();
124132

125133
// Keep alive until abort
126134
await waitForAbort(signal);
@@ -158,9 +166,9 @@ const { parts } = useRealtimeStream(agentStream, chatRunId, { accessToken });
158166
// Send questions via Supabase
159167
const channel = supabase.channel(`session:${sessionId}`);
160168
channel.send({
161-
type: 'broadcast',
162-
event: 'question',
163-
payload: { question, messageId }
169+
type: "broadcast",
170+
event: "question",
171+
payload: { question, messageId },
164172
});
165173
```
166174

@@ -169,19 +177,22 @@ channel.send({
169177
## Key Features Demonstrated
170178

171179
### Trigger.dev Features
180+
172181
- **Build Extensions**: Install system dependencies (git) in containers
173182
- **Long-running Tasks**: 60-minute sessions with state persistence
174183
- **Realtime Streams v2**: Type-safe streaming with `streams.define<T>()`
175184
- **Task Composition**: Two-task pattern with data passing
176185
- **Abort Handling**: Graceful cleanup on cancellation
177186

178187
### Claude Agent SDK Features
188+
179189
- **Extended Thinking**: 8192 tokens for complex reasoning
180190
- **Full Tool Suite**: Task, Bash, Glob, Grep, Read, Edit, Write
181191
- **Working Directory**: Operates within cloned repo
182192
- **Streaming Results**: AsyncIterable interface
183193

184194
### Architecture Patterns
195+
185196
- **Control/Data Plane Separation**: Supabase for control, Trigger.dev for data
186197
- **No Re-cloning**: Repository persists in memory
187198
- **Session Management**: Unique IDs link tasks and channels
@@ -192,27 +203,32 @@ channel.send({
192203
## Implementation Checklist
193204

194205
- [x] **Phase 1: UI + Landing Page**
206+
195207
- Landing page with GitHub URL input
196208
- Chat interface with message components
197209
- Tool cards with collapsible results
198210

199211
- [x] **Phase 2: Trigger.dev Setup**
212+
200213
- Initialize project with v4 SDK
201214
- Configure build extension for git
202215
- Define typed stream with SDKMessage
203216

204217
- [x] **Phase 3: Two-Task Implementation**
218+
205219
- Clone task with sessionId generation
206220
- Chat session task with 60-min timeout
207221
- Supabase Broadcast subscription
208222
- Trigger Streams v2 piping
209223

210224
- [x] **Phase 4: API Routes**
225+
211226
- Orchestration route (trigger both tasks)
212227
- Question sending via Supabase
213228
- Public token generation
214229

215230
- [x] **Phase 5: Frontend Integration**
231+
216232
- useRealtimeStream for responses
217233
- Supabase client for questions
218234
- Message transformation logic
@@ -239,7 +255,7 @@ ANTHROPIC_API_KEY=your_anthropic_api_key
239255
SUPABASE_URL=your_supabase_url
240256
SUPABASE_ANON_KEY=your_supabase_anon_key
241257
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
242-
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
258+
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY=your_supabase_anon_key
243259
```
244260

245261
---
@@ -264,16 +280,19 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
264280
## Future Enhancements
265281

266282
### V1.1: Optimization
283+
267284
- Redis caching for frequently accessed repos
268285
- Multiple concurrent questions
269286
- Persistent query history
270287

271288
### V1.2: Features
289+
272290
- Private repo support (GitHub OAuth)
273291
- Full git history access
274292
- Repo comparison queries
275293

276294
### V2: Advanced
295+
277296
- File tree navigation UI
278297
- Code generation capabilities
279298
- PR creation from chat
@@ -289,4 +308,4 @@ This demo showcases several advanced patterns:
289308
3. **Hybrid Architecture**: Combining platforms for optimal performance
290309
4. **No Re-cloning**: Solving a real performance problem in AI-powered dev tools
291310

292-
The architecture prioritizes Trigger.dev's strengths (task orchestration, streaming) while using Supabase minimally for coordination, making it an excellent showcase of Trigger.dev v4 capabilities.
311+
The architecture prioritizes Trigger.dev's strengths (task orchestration, streaming) while using Supabase minimally for coordination, making it an excellent showcase of Trigger.dev v4 capabilities.

claude-agent-github-wiki/test-broadcast.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ async function test() {
77
// Using environment variables
88
const SUPABASE_URL =
99
process.env.SUPABASE_URL || "https://rnxcjullrjwhgtdbpqbp.supabase.co";
10-
const SERVICE_KEY = process.env.SUPABASE_PRIVATE_KEY;
11-
const ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
10+
const SERVICE_KEY = process.env.SUPABASE_SECRET_KEY;
11+
const ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY;
1212

1313
if (!SERVICE_KEY || !ANON_KEY) {
1414
console.error("Missing keys in environment!");

claude-agent-github-wiki/test-supabase.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ async function testSupabase() {
99

1010
// Check environment variables
1111
const url = process.env.SUPABASE_URL;
12-
const privateKey = process.env.SUPABASE_PRIVATE_KEY;
12+
const privateKey = process.env.SUPABASE_SECRET_KEY;
1313

1414
console.log("Environment Check:");
1515
console.log(
1616
"SUPABASE_URL:",
1717
url ? `✅ ${url.substring(0, 40)}...` : "❌ NOT SET"
1818
);
1919
console.log(
20-
"SUPABASE_PRIVATE_KEY:",
20+
"SUPABASE_SECRET_KEY:",
2121
privateKey ? `✅ ${privateKey.substring(0, 20)}...` : "❌ NOT SET"
2222
);
2323
console.log("");
@@ -26,7 +26,7 @@ async function testSupabase() {
2626
console.error("❌ Missing required environment variables!");
2727
console.log("\nMake sure your .env file contains:");
2828
console.log("SUPABASE_URL=https://YOUR_PROJECT.supabase.co");
29-
console.log("SUPABASE_PRIVATE_KEY=your-service-role-key-here\n");
29+
console.log("SUPABASE_SECRET_KEY=your-service-role-key-here\n");
3030
process.exit(1);
3131
}
3232

0 commit comments

Comments
 (0)