thepopebot 1.2.72 → 1.2.73-beta.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/config/index.js CHANGED
@@ -15,7 +15,7 @@ export function withThepopebot(nextConfig = {}) {
15
15
  distDir: process.env.NEXT_BUILD_DIR || '.next',
16
16
  env: {
17
17
  ...nextConfig.env,
18
- NEXT_PUBLIC_CLAUDE_WORKSPACE: process.env.CLAUDE_CODE_OAUTH_TOKEN && process.env.BETA ? 'true' : '',
18
+ NEXT_PUBLIC_CODE_WORKSPACE: process.env.CLAUDE_CODE_OAUTH_TOKEN && process.env.BETA ? 'true' : '',
19
19
  },
20
20
  serverExternalPackages: [
21
21
  ...(nextConfig.serverExternalPackages || []),
@@ -0,0 +1,5 @@
1
+ ALTER TABLE `claude_workspaces` RENAME TO `code_workspaces`;--> statement-breakpoint
2
+ DROP INDEX IF EXISTS `claude_workspaces_container_name_unique`;--> statement-breakpoint
3
+ CREATE UNIQUE INDEX `code_workspaces_container_name_unique` ON `code_workspaces` (`container_name`);--> statement-breakpoint
4
+ ALTER TABLE `code_workspaces` ADD `coding_agent` text NOT NULL DEFAULT 'claude-code';--> statement-breakpoint
5
+ ALTER TABLE `chats` RENAME COLUMN `claude_workspace_id` TO `code_workspace_id`;
@@ -0,0 +1,419 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
5
+ "prevId": "393d6739-2717-4020-bedb-8a9e1ed756da",
6
+ "tables": {
7
+ "chats": {
8
+ "name": "chats",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "text",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": false
16
+ },
17
+ "user_id": {
18
+ "name": "user_id",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "title": {
25
+ "name": "title",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false,
30
+ "default": "'New Chat'"
31
+ },
32
+ "starred": {
33
+ "name": "starred",
34
+ "type": "integer",
35
+ "primaryKey": false,
36
+ "notNull": true,
37
+ "autoincrement": false,
38
+ "default": 0
39
+ },
40
+ "code_workspace_id": {
41
+ "name": "code_workspace_id",
42
+ "type": "text",
43
+ "primaryKey": false,
44
+ "notNull": false,
45
+ "autoincrement": false
46
+ },
47
+ "created_at": {
48
+ "name": "created_at",
49
+ "type": "integer",
50
+ "primaryKey": false,
51
+ "notNull": true,
52
+ "autoincrement": false
53
+ },
54
+ "updated_at": {
55
+ "name": "updated_at",
56
+ "type": "integer",
57
+ "primaryKey": false,
58
+ "notNull": true,
59
+ "autoincrement": false
60
+ }
61
+ },
62
+ "indexes": {},
63
+ "foreignKeys": {},
64
+ "compositePrimaryKeys": {},
65
+ "uniqueConstraints": {},
66
+ "checkConstraints": {}
67
+ },
68
+ "code_workspaces": {
69
+ "name": "code_workspaces",
70
+ "columns": {
71
+ "id": {
72
+ "name": "id",
73
+ "type": "text",
74
+ "primaryKey": true,
75
+ "notNull": true,
76
+ "autoincrement": false
77
+ },
78
+ "user_id": {
79
+ "name": "user_id",
80
+ "type": "text",
81
+ "primaryKey": false,
82
+ "notNull": true,
83
+ "autoincrement": false
84
+ },
85
+ "container_name": {
86
+ "name": "container_name",
87
+ "type": "text",
88
+ "primaryKey": false,
89
+ "notNull": false,
90
+ "autoincrement": false
91
+ },
92
+ "repo": {
93
+ "name": "repo",
94
+ "type": "text",
95
+ "primaryKey": false,
96
+ "notNull": false,
97
+ "autoincrement": false
98
+ },
99
+ "branch": {
100
+ "name": "branch",
101
+ "type": "text",
102
+ "primaryKey": false,
103
+ "notNull": false,
104
+ "autoincrement": false
105
+ },
106
+ "title": {
107
+ "name": "title",
108
+ "type": "text",
109
+ "primaryKey": false,
110
+ "notNull": true,
111
+ "autoincrement": false,
112
+ "default": "'Code Workspace'"
113
+ },
114
+ "coding_agent": {
115
+ "name": "coding_agent",
116
+ "type": "text",
117
+ "primaryKey": false,
118
+ "notNull": true,
119
+ "autoincrement": false,
120
+ "default": "'claude-code'"
121
+ },
122
+ "starred": {
123
+ "name": "starred",
124
+ "type": "integer",
125
+ "primaryKey": false,
126
+ "notNull": true,
127
+ "autoincrement": false,
128
+ "default": 0
129
+ },
130
+ "created_at": {
131
+ "name": "created_at",
132
+ "type": "integer",
133
+ "primaryKey": false,
134
+ "notNull": true,
135
+ "autoincrement": false
136
+ },
137
+ "updated_at": {
138
+ "name": "updated_at",
139
+ "type": "integer",
140
+ "primaryKey": false,
141
+ "notNull": true,
142
+ "autoincrement": false
143
+ }
144
+ },
145
+ "indexes": {
146
+ "code_workspaces_container_name_unique": {
147
+ "name": "code_workspaces_container_name_unique",
148
+ "columns": [
149
+ "container_name"
150
+ ],
151
+ "isUnique": true
152
+ }
153
+ },
154
+ "foreignKeys": {},
155
+ "compositePrimaryKeys": {},
156
+ "uniqueConstraints": {},
157
+ "checkConstraints": {}
158
+ },
159
+ "messages": {
160
+ "name": "messages",
161
+ "columns": {
162
+ "id": {
163
+ "name": "id",
164
+ "type": "text",
165
+ "primaryKey": true,
166
+ "notNull": true,
167
+ "autoincrement": false
168
+ },
169
+ "chat_id": {
170
+ "name": "chat_id",
171
+ "type": "text",
172
+ "primaryKey": false,
173
+ "notNull": true,
174
+ "autoincrement": false
175
+ },
176
+ "role": {
177
+ "name": "role",
178
+ "type": "text",
179
+ "primaryKey": false,
180
+ "notNull": true,
181
+ "autoincrement": false
182
+ },
183
+ "content": {
184
+ "name": "content",
185
+ "type": "text",
186
+ "primaryKey": false,
187
+ "notNull": true,
188
+ "autoincrement": false
189
+ },
190
+ "created_at": {
191
+ "name": "created_at",
192
+ "type": "integer",
193
+ "primaryKey": false,
194
+ "notNull": true,
195
+ "autoincrement": false
196
+ }
197
+ },
198
+ "indexes": {},
199
+ "foreignKeys": {},
200
+ "compositePrimaryKeys": {},
201
+ "uniqueConstraints": {},
202
+ "checkConstraints": {}
203
+ },
204
+ "notifications": {
205
+ "name": "notifications",
206
+ "columns": {
207
+ "id": {
208
+ "name": "id",
209
+ "type": "text",
210
+ "primaryKey": true,
211
+ "notNull": true,
212
+ "autoincrement": false
213
+ },
214
+ "notification": {
215
+ "name": "notification",
216
+ "type": "text",
217
+ "primaryKey": false,
218
+ "notNull": true,
219
+ "autoincrement": false
220
+ },
221
+ "payload": {
222
+ "name": "payload",
223
+ "type": "text",
224
+ "primaryKey": false,
225
+ "notNull": true,
226
+ "autoincrement": false
227
+ },
228
+ "read": {
229
+ "name": "read",
230
+ "type": "integer",
231
+ "primaryKey": false,
232
+ "notNull": true,
233
+ "autoincrement": false,
234
+ "default": 0
235
+ },
236
+ "created_at": {
237
+ "name": "created_at",
238
+ "type": "integer",
239
+ "primaryKey": false,
240
+ "notNull": true,
241
+ "autoincrement": false
242
+ }
243
+ },
244
+ "indexes": {},
245
+ "foreignKeys": {},
246
+ "compositePrimaryKeys": {},
247
+ "uniqueConstraints": {},
248
+ "checkConstraints": {}
249
+ },
250
+ "settings": {
251
+ "name": "settings",
252
+ "columns": {
253
+ "id": {
254
+ "name": "id",
255
+ "type": "text",
256
+ "primaryKey": true,
257
+ "notNull": true,
258
+ "autoincrement": false
259
+ },
260
+ "type": {
261
+ "name": "type",
262
+ "type": "text",
263
+ "primaryKey": false,
264
+ "notNull": true,
265
+ "autoincrement": false
266
+ },
267
+ "key": {
268
+ "name": "key",
269
+ "type": "text",
270
+ "primaryKey": false,
271
+ "notNull": true,
272
+ "autoincrement": false
273
+ },
274
+ "value": {
275
+ "name": "value",
276
+ "type": "text",
277
+ "primaryKey": false,
278
+ "notNull": true,
279
+ "autoincrement": false
280
+ },
281
+ "created_by": {
282
+ "name": "created_by",
283
+ "type": "text",
284
+ "primaryKey": false,
285
+ "notNull": false,
286
+ "autoincrement": false
287
+ },
288
+ "created_at": {
289
+ "name": "created_at",
290
+ "type": "integer",
291
+ "primaryKey": false,
292
+ "notNull": true,
293
+ "autoincrement": false
294
+ },
295
+ "updated_at": {
296
+ "name": "updated_at",
297
+ "type": "integer",
298
+ "primaryKey": false,
299
+ "notNull": true,
300
+ "autoincrement": false
301
+ }
302
+ },
303
+ "indexes": {},
304
+ "foreignKeys": {},
305
+ "compositePrimaryKeys": {},
306
+ "uniqueConstraints": {},
307
+ "checkConstraints": {}
308
+ },
309
+ "subscriptions": {
310
+ "name": "subscriptions",
311
+ "columns": {
312
+ "id": {
313
+ "name": "id",
314
+ "type": "text",
315
+ "primaryKey": true,
316
+ "notNull": true,
317
+ "autoincrement": false
318
+ },
319
+ "platform": {
320
+ "name": "platform",
321
+ "type": "text",
322
+ "primaryKey": false,
323
+ "notNull": true,
324
+ "autoincrement": false
325
+ },
326
+ "channel_id": {
327
+ "name": "channel_id",
328
+ "type": "text",
329
+ "primaryKey": false,
330
+ "notNull": true,
331
+ "autoincrement": false
332
+ },
333
+ "created_at": {
334
+ "name": "created_at",
335
+ "type": "integer",
336
+ "primaryKey": false,
337
+ "notNull": true,
338
+ "autoincrement": false
339
+ }
340
+ },
341
+ "indexes": {},
342
+ "foreignKeys": {},
343
+ "compositePrimaryKeys": {},
344
+ "uniqueConstraints": {},
345
+ "checkConstraints": {}
346
+ },
347
+ "users": {
348
+ "name": "users",
349
+ "columns": {
350
+ "id": {
351
+ "name": "id",
352
+ "type": "text",
353
+ "primaryKey": true,
354
+ "notNull": true,
355
+ "autoincrement": false
356
+ },
357
+ "email": {
358
+ "name": "email",
359
+ "type": "text",
360
+ "primaryKey": false,
361
+ "notNull": true,
362
+ "autoincrement": false
363
+ },
364
+ "password_hash": {
365
+ "name": "password_hash",
366
+ "type": "text",
367
+ "primaryKey": false,
368
+ "notNull": true,
369
+ "autoincrement": false
370
+ },
371
+ "role": {
372
+ "name": "role",
373
+ "type": "text",
374
+ "primaryKey": false,
375
+ "notNull": true,
376
+ "autoincrement": false,
377
+ "default": "'admin'"
378
+ },
379
+ "created_at": {
380
+ "name": "created_at",
381
+ "type": "integer",
382
+ "primaryKey": false,
383
+ "notNull": true,
384
+ "autoincrement": false
385
+ },
386
+ "updated_at": {
387
+ "name": "updated_at",
388
+ "type": "integer",
389
+ "primaryKey": false,
390
+ "notNull": true,
391
+ "autoincrement": false
392
+ }
393
+ },
394
+ "indexes": {
395
+ "users_email_unique": {
396
+ "name": "users_email_unique",
397
+ "columns": [
398
+ "email"
399
+ ],
400
+ "isUnique": true
401
+ }
402
+ },
403
+ "foreignKeys": {},
404
+ "compositePrimaryKeys": {},
405
+ "uniqueConstraints": {},
406
+ "checkConstraints": {}
407
+ }
408
+ },
409
+ "views": {},
410
+ "enums": {},
411
+ "_meta": {
412
+ "schemas": {},
413
+ "tables": {},
414
+ "columns": {}
415
+ },
416
+ "internal": {
417
+ "indexes": {}
418
+ }
419
+ }
@@ -22,6 +22,13 @@
22
22
  "when": 1772340907917,
23
23
  "tag": "0002_black_daimon_hellstrom",
24
24
  "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1772486536207,
30
+ "tag": "0003_rename_code_workspaces",
31
+ "breakpoints": true
25
32
  }
26
33
  ]
27
34
  }
package/lib/ai/index.js CHANGED
@@ -116,8 +116,8 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
116
116
 
117
117
  if (!existingChat) {
118
118
  // First message: create workspace row, then chat, then link them
119
- const { createClaudeWorkspace } = await import('../db/claude-workspaces.js');
120
- const workspace = createClaudeWorkspace(options.userId || 'unknown', {
119
+ const { createCodeWorkspace } = await import('../db/code-workspaces.js');
120
+ const workspace = createCodeWorkspace(options.userId || 'unknown', {
121
121
  repo: options.repo,
122
122
  branch: options.branch,
123
123
  });
@@ -125,7 +125,7 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
125
125
  createChat(options.userId || 'unknown', 'New Chat', threadId);
126
126
  linkChatToWorkspace(threadId, workspaceId);
127
127
  } else {
128
- workspaceId = existingChat.claudeWorkspaceId;
128
+ workspaceId = existingChat.codeWorkspaceId;
129
129
  }
130
130
 
131
131
  agent = await getCodeAgent({
package/lib/ai/tools.js CHANGED
@@ -194,12 +194,15 @@ function createStartCodingTool({ repo, branch, workspaceId }) {
194
194
  async ({ task_description }) => {
195
195
  try {
196
196
  const { randomUUID } = await import('crypto');
197
- const containerName = `claude-workspace-${randomUUID().slice(0, 8)}`;
197
+ const containerName = `code-workspace-${randomUUID().slice(0, 8)}`;
198
198
 
199
- const { createClaudeWorkspaceContainer } = await import('../tools/docker.js');
200
- await createClaudeWorkspaceContainer({ containerName, repo, branch });
199
+ const { getCodeWorkspaceById, updateContainerName } = await import('../db/code-workspaces.js');
200
+ const workspace = getCodeWorkspaceById(workspaceId);
201
+ const codingAgent = workspace?.codingAgent || 'claude-code';
202
+
203
+ const { createCodeWorkspaceContainer } = await import('../tools/docker.js');
204
+ await createCodeWorkspaceContainer({ containerName, repo, branch, codingAgent });
201
205
 
202
- const { updateContainerName } = await import('../db/claude-workspaces.js');
203
206
  updateContainerName(workspaceId, containerName);
204
207
 
205
208
  return JSON.stringify({
@@ -218,7 +221,7 @@ function createStartCodingTool({ repo, branch, workspaceId }) {
218
221
  {
219
222
  name: 'start_coding',
220
223
  description:
221
- 'Launch a live Claude Code workspace in a Docker container. Only call this when the user explicitly says they are ready to start coding (e.g. "let\'s start coding", "okay let\'s get started", "launch it"). Returns a link to the live workspace.',
224
+ 'Launch a live code workspace in a Docker container. Only call this when the user explicitly says they are ready to start coding (e.g. "let\'s start coding", "okay let\'s get started", "launch it"). Returns a link to the live workspace.',
222
225
  schema: z.object({
223
226
  task_description: z
224
227
  .string()
@@ -36,7 +36,7 @@ export async function getChats() {
36
36
  const user = await requireAuth();
37
37
  const { or, eq, desc } = await import('drizzle-orm');
38
38
  const { getDb } = await import('../db/index.js');
39
- const { chats, claudeWorkspaces } = await import('../db/schema.js');
39
+ const { chats, codeWorkspaces } = await import('../db/schema.js');
40
40
  const db = getDb();
41
41
  return db
42
42
  .select({
@@ -44,13 +44,13 @@ export async function getChats() {
44
44
  userId: chats.userId,
45
45
  title: chats.title,
46
46
  starred: chats.starred,
47
- claudeWorkspaceId: chats.claudeWorkspaceId,
48
- containerName: claudeWorkspaces.containerName,
47
+ codeWorkspaceId: chats.codeWorkspaceId,
48
+ containerName: codeWorkspaces.containerName,
49
49
  createdAt: chats.createdAt,
50
50
  updatedAt: chats.updatedAt,
51
51
  })
52
52
  .from(chats)
53
- .leftJoin(claudeWorkspaces, eq(chats.claudeWorkspaceId, claudeWorkspaces.id))
53
+ .leftJoin(codeWorkspaces, eq(chats.codeWorkspaceId, codeWorkspaces.id))
54
54
  .where(or(eq(chats.userId, user.id), eq(chats.userId, 'telegram')))
55
55
  .orderBy(desc(chats.updatedAt))
56
56
  .all();
@@ -205,10 +205,13 @@ export async function getAppVersion() {
205
205
  await requireAuth();
206
206
  const { getInstalledVersion } = await import('../cron.js');
207
207
  const { getAvailableVersion, getReleaseNotes } = await import('../db/update-check.js');
208
+ const version = getInstalledVersion();
209
+ const available = getAvailableVersion();
210
+ const isNewer = available && available !== version;
208
211
  return {
209
- version: getInstalledVersion(),
210
- updateAvailable: getAvailableVersion(),
211
- changelog: getReleaseNotes(),
212
+ version,
213
+ updateAvailable: isNewer ? available : null,
214
+ changelog: isNewer ? getReleaseNotes() : null,
212
215
  };
213
216
  }
214
217
 
@@ -313,9 +316,9 @@ export async function getBranches(repoFullName) {
313
316
  }
314
317
 
315
318
  /**
316
- * Get chat metadata (claudeWorkspaceId) for an existing chat.
319
+ * Get chat metadata (codeWorkspaceId) for an existing chat.
317
320
  * @param {string} chatId
318
- * @returns {Promise<{claudeWorkspaceId: string|null}|null>}
321
+ * @returns {Promise<{codeWorkspaceId: string|null}|null>}
319
322
  */
320
323
  export async function getChatMeta(chatId) {
321
324
  const user = await requireAuth();
@@ -323,7 +326,7 @@ export async function getChatMeta(chatId) {
323
326
  if (!chat || (chat.userId !== user.id && chat.userId !== 'telegram')) {
324
327
  return null;
325
328
  }
326
- return { claudeWorkspaceId: chat.claudeWorkspaceId || null };
329
+ return { codeWorkspaceId: chat.codeWorkspaceId || null };
327
330
  }
328
331
 
329
332
  /**
@@ -334,10 +337,10 @@ export async function getChatMeta(chatId) {
334
337
  export async function getWorkspace(workspaceId) {
335
338
  await requireAuth();
336
339
  try {
337
- const { getClaudeWorkspaceById } = await import('../db/claude-workspaces.js');
338
- const ws = getClaudeWorkspaceById(workspaceId);
340
+ const { getCodeWorkspaceById } = await import('../db/code-workspaces.js');
341
+ const ws = getCodeWorkspaceById(workspaceId);
339
342
  if (!ws) return null;
340
- return { id: ws.id, repo: ws.repo, branch: ws.branch, containerName: ws.containerName };
343
+ return { id: ws.id, repo: ws.repo, branch: ws.branch, containerName: ws.containerName, codingAgent: ws.codingAgent };
341
344
  } catch (err) {
342
345
  console.error('Failed to get workspace:', err);
343
346
  return null;
@@ -65,8 +65,8 @@ function ChatPage({ session, needsSetup, chatId }) {
65
65
  setInitialMessages(uiMessages);
66
66
  try {
67
67
  const meta = await getChatMeta(activeChatId);
68
- if (meta?.claudeWorkspaceId) {
69
- const ws = await getWorkspace(meta.claudeWorkspaceId);
68
+ if (meta?.codeWorkspaceId) {
69
+ const ws = await getWorkspace(meta.codeWorkspaceId);
70
70
  setWorkspace(ws);
71
71
  } else {
72
72
  setWorkspace(null);
@@ -82,8 +82,8 @@ export function ChatPage({ session, needsSetup, chatId }) {
82
82
  // Check if this is a code chat
83
83
  try {
84
84
  const meta = await getChatMeta(activeChatId);
85
- if (meta?.claudeWorkspaceId) {
86
- const ws = await getWorkspace(meta.claudeWorkspaceId);
85
+ if (meta?.codeWorkspaceId) {
86
+ const ws = await getWorkspace(meta.codeWorkspaceId);
87
87
  setWorkspace(ws);
88
88
  } else {
89
89
  setWorkspace(null);
@@ -187,7 +187,7 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
187
187
  return /* @__PURE__ */ jsxs(
188
188
  "a",
189
189
  {
190
- href: chat.claudeWorkspaceId && chat.containerName ? `/code/${chat.claudeWorkspaceId}` : `/chat/${chat.id}`,
190
+ href: chat.codeWorkspaceId && chat.containerName ? `/code/${chat.codeWorkspaceId}` : `/chat/${chat.id}`,
191
191
  className: "relative group flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-muted/50 rounded-md",
192
192
  style: { textDecoration: "inherit", color: "inherit" },
193
193
  onMouseEnter: () => setHovered(true),
@@ -198,14 +198,14 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
198
198
  return;
199
199
  }
200
200
  e.preventDefault();
201
- if (chat.claudeWorkspaceId && chat.containerName) {
202
- window.location.href = `/code/${chat.claudeWorkspaceId}`;
201
+ if (chat.codeWorkspaceId && chat.containerName) {
202
+ window.location.href = `/code/${chat.codeWorkspaceId}`;
203
203
  } else {
204
204
  onNavigate(chat.id);
205
205
  }
206
206
  },
207
207
  children: [
208
- chat.claudeWorkspaceId && chat.containerName ? /* @__PURE__ */ jsx(CodeIcon, { size: 16 }) : /* @__PURE__ */ jsx(MessageIcon, { size: 16 }),
208
+ chat.codeWorkspaceId && chat.containerName ? /* @__PURE__ */ jsx(CodeIcon, { size: 16 }) : /* @__PURE__ */ jsx(MessageIcon, { size: 16 }),
209
209
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
210
210
  editing ? /* @__PURE__ */ jsx(
211
211
  "input",
@@ -229,7 +229,7 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
229
229
 
230
230
  return (
231
231
  <a
232
- href={chat.claudeWorkspaceId && chat.containerName ? `/code/${chat.claudeWorkspaceId}` : `/chat/${chat.id}`}
232
+ href={chat.codeWorkspaceId && chat.containerName ? `/code/${chat.codeWorkspaceId}` : `/chat/${chat.id}`}
233
233
  className="relative group flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-muted/50 rounded-md"
234
234
  style={{ textDecoration: 'inherit', color: 'inherit' }}
235
235
  onMouseEnter={() => setHovered(true)}
@@ -237,14 +237,14 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
237
237
  onClick={(e) => {
238
238
  if (editing) { e.preventDefault(); return; }
239
239
  e.preventDefault();
240
- if (chat.claudeWorkspaceId && chat.containerName) {
241
- window.location.href = `/code/${chat.claudeWorkspaceId}`;
240
+ if (chat.codeWorkspaceId && chat.containerName) {
241
+ window.location.href = `/code/${chat.codeWorkspaceId}`;
242
242
  } else {
243
243
  onNavigate(chat.id);
244
244
  }
245
245
  }}
246
246
  >
247
- {chat.claudeWorkspaceId && chat.containerName ? <CodeIcon size={16} /> : <MessageIcon size={16} />}
247
+ {chat.codeWorkspaceId && chat.containerName ? <CodeIcon size={16} /> : <MessageIcon size={16} />}
248
248
  <div className="flex-1 min-w-0">
249
249
  {editing ? (
250
250
  <input
@@ -52,7 +52,7 @@ function CodeModeToggle({
52
52
  setLoadingBranches(false);
53
53
  }).catch(() => setLoadingBranches(false));
54
54
  }, [repo]);
55
- if (!process.env.NEXT_PUBLIC_CLAUDE_WORKSPACE) return null;
55
+ if (!process.env.NEXT_PUBLIC_CODE_WORKSPACE) return null;
56
56
  if (locked && enabled) {
57
57
  return /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2.5 text-sm text-muted-foreground", children: [
58
58
  repo && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -76,7 +76,7 @@ function CodeModeToggle({
76
76
  className: "inline-flex items-center gap-2 group",
77
77
  role: "switch",
78
78
  "aria-checked": enabled,
79
- "aria-label": "Toggle Claude Code mode",
79
+ "aria-label": "Toggle Code mode",
80
80
  children: [
81
81
  /* @__PURE__ */ jsx(
82
82
  "span",
@@ -99,7 +99,7 @@ function CodeModeToggle({
99
99
  /* @__PURE__ */ jsx("span", { className: cn(
100
100
  "text-xs font-medium transition-colors",
101
101
  enabled ? "text-foreground" : "text-muted-foreground group-hover:text-foreground"
102
- ), children: "Claude Code" })
102
+ ), children: "Code" })
103
103
  ]
104
104
  }
105
105
  ),
@@ -73,7 +73,7 @@ export function CodeModeToggle({
73
73
  }).catch(() => setLoadingBranches(false));
74
74
  }, [repo]);
75
75
 
76
- if (!process.env.NEXT_PUBLIC_CLAUDE_WORKSPACE) return null;
76
+ if (!process.env.NEXT_PUBLIC_CODE_WORKSPACE) return null;
77
77
 
78
78
  // Locked mode: show as centered inline label
79
79
  if (locked && enabled) {
@@ -109,7 +109,7 @@ export function CodeModeToggle({
109
109
  className="inline-flex items-center gap-2 group"
110
110
  role="switch"
111
111
  aria-checked={enabled}
112
- aria-label="Toggle Claude Code mode"
112
+ aria-label="Toggle Code mode"
113
113
  >
114
114
  {/* Track */}
115
115
  <span
@@ -131,7 +131,7 @@ export function CodeModeToggle({
131
131
  'text-xs font-medium transition-colors',
132
132
  enabled ? 'text-foreground' : 'text-muted-foreground group-hover:text-foreground'
133
133
  )}>
134
- Claude Code
134
+ Code
135
135
  </span>
136
136
  </button>
137
137
 
@@ -27,20 +27,20 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
27
27
  /* @__PURE__ */ jsxs(
28
28
  SidebarMenuButton,
29
29
  {
30
- href: chat.claudeWorkspaceId && chat.containerName ? `/code/${chat.claudeWorkspaceId}` : `/chat/${chat.id}`,
30
+ href: chat.codeWorkspaceId && chat.containerName ? `/code/${chat.codeWorkspaceId}` : `/chat/${chat.id}`,
31
31
  className: "pr-8",
32
32
  isActive,
33
33
  onClick: (e) => {
34
34
  e.preventDefault();
35
- if (chat.claudeWorkspaceId && chat.containerName) {
36
- window.location.href = `/code/${chat.claudeWorkspaceId}`;
35
+ if (chat.codeWorkspaceId && chat.containerName) {
36
+ window.location.href = `/code/${chat.codeWorkspaceId}`;
37
37
  } else {
38
38
  navigateToChat(chat.id);
39
39
  }
40
40
  setOpenMobile(false);
41
41
  },
42
42
  children: [
43
- chat.claudeWorkspaceId && chat.containerName ? /* @__PURE__ */ jsx(CodeIcon, { size: 14 }) : /* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
43
+ chat.codeWorkspaceId && chat.containerName ? /* @__PURE__ */ jsx(CodeIcon, { size: 14 }) : /* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
44
44
  /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: chat.title })
45
45
  ]
46
46
  }
@@ -27,20 +27,20 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
27
27
  onMouseLeave={() => setHovered(false)}
28
28
  >
29
29
  <SidebarMenuButton
30
- href={chat.claudeWorkspaceId && chat.containerName ? `/code/${chat.claudeWorkspaceId}` : `/chat/${chat.id}`}
30
+ href={chat.codeWorkspaceId && chat.containerName ? `/code/${chat.codeWorkspaceId}` : `/chat/${chat.id}`}
31
31
  className="pr-8"
32
32
  isActive={isActive}
33
33
  onClick={(e) => {
34
34
  e.preventDefault();
35
- if (chat.claudeWorkspaceId && chat.containerName) {
36
- window.location.href = `/code/${chat.claudeWorkspaceId}`;
35
+ if (chat.codeWorkspaceId && chat.containerName) {
36
+ window.location.href = `/code/${chat.codeWorkspaceId}`;
37
37
  } else {
38
38
  navigateToChat(chat.id);
39
39
  }
40
40
  setOpenMobile(false);
41
41
  }}
42
42
  >
43
- {chat.claudeWorkspaceId && chat.containerName ? <CodeIcon size={14} /> : <MessageIcon size={14} />}
43
+ {chat.codeWorkspaceId && chat.containerName ? <CodeIcon size={14} /> : <MessageIcon size={14} />}
44
44
  <span className="truncate flex-1">
45
45
  {chat.title}
46
46
  </span>
@@ -2,13 +2,13 @@
2
2
 
3
3
  import { auth } from '../auth/index.js';
4
4
  import {
5
- createClaudeWorkspace as dbCreateClaudeWorkspace,
6
- getClaudeWorkspaceById,
7
- getClaudeWorkspacesByUser,
8
- updateClaudeWorkspaceTitle,
9
- toggleClaudeWorkspaceStarred,
10
- deleteClaudeWorkspace as dbDeleteClaudeWorkspace,
11
- } from '../db/claude-workspaces.js';
5
+ createCodeWorkspace as dbCreateCodeWorkspace,
6
+ getCodeWorkspaceById,
7
+ getCodeWorkspacesByUser,
8
+ updateCodeWorkspaceTitle,
9
+ toggleCodeWorkspaceStarred,
10
+ deleteCodeWorkspace as dbDeleteCodeWorkspace,
11
+ } from '../db/code-workspaces.js';
12
12
 
13
13
  /**
14
14
  * Get the authenticated user or throw.
@@ -22,67 +22,67 @@ async function requireAuth() {
22
22
  }
23
23
 
24
24
  /**
25
- * Get all claude workspaces for the authenticated user.
25
+ * Get all code workspaces for the authenticated user.
26
26
  * @returns {Promise<object[]>}
27
27
  */
28
- export async function getClaudeWorkspaces() {
28
+ export async function getCodeWorkspaces() {
29
29
  const user = await requireAuth();
30
- return getClaudeWorkspacesByUser(user.id);
30
+ return getCodeWorkspacesByUser(user.id);
31
31
  }
32
32
 
33
33
  /**
34
- * Create a new claude workspace.
34
+ * Create a new code workspace.
35
35
  * @param {string} containerName - Docker container DNS name
36
- * @param {string} [title='Claude Workspace']
36
+ * @param {string} [title='Code Workspace']
37
37
  * @returns {Promise<object>}
38
38
  */
39
- export async function createClaudeWorkspace(containerName, title = 'Claude Workspace') {
39
+ export async function createCodeWorkspace(containerName, title = 'Code Workspace') {
40
40
  const user = await requireAuth();
41
- return dbCreateClaudeWorkspace(user.id, { containerName, title });
41
+ return dbCreateCodeWorkspace(user.id, { containerName, title });
42
42
  }
43
43
 
44
44
  /**
45
- * Rename a claude workspace (with ownership check).
45
+ * Rename a code workspace (with ownership check).
46
46
  * @param {string} id
47
47
  * @param {string} title
48
48
  * @returns {Promise<{success: boolean}>}
49
49
  */
50
- export async function renameClaudeWorkspace(id, title) {
50
+ export async function renameCodeWorkspace(id, title) {
51
51
  const user = await requireAuth();
52
- const workspace = getClaudeWorkspaceById(id);
52
+ const workspace = getCodeWorkspaceById(id);
53
53
  if (!workspace || workspace.userId !== user.id) {
54
54
  return { success: false };
55
55
  }
56
- updateClaudeWorkspaceTitle(id, title);
56
+ updateCodeWorkspaceTitle(id, title);
57
57
  return { success: true };
58
58
  }
59
59
 
60
60
  /**
61
- * Toggle a claude workspace's starred status (with ownership check).
61
+ * Toggle a code workspace's starred status (with ownership check).
62
62
  * @param {string} id
63
63
  * @returns {Promise<{success: boolean, starred?: number}>}
64
64
  */
65
- export async function starClaudeWorkspace(id) {
65
+ export async function starCodeWorkspace(id) {
66
66
  const user = await requireAuth();
67
- const workspace = getClaudeWorkspaceById(id);
67
+ const workspace = getCodeWorkspaceById(id);
68
68
  if (!workspace || workspace.userId !== user.id) {
69
69
  return { success: false };
70
70
  }
71
- const starred = toggleClaudeWorkspaceStarred(id);
71
+ const starred = toggleCodeWorkspaceStarred(id);
72
72
  return { success: true, starred };
73
73
  }
74
74
 
75
75
  /**
76
- * Delete a claude workspace (with ownership check).
76
+ * Delete a code workspace (with ownership check).
77
77
  * @param {string} id
78
78
  * @returns {Promise<{success: boolean}>}
79
79
  */
80
- export async function deleteClaudeWorkspace(id) {
80
+ export async function deleteCodeWorkspace(id) {
81
81
  const user = await requireAuth();
82
- const workspace = getClaudeWorkspaceById(id);
82
+ const workspace = getCodeWorkspaceById(id);
83
83
  if (!workspace || workspace.userId !== user.id) {
84
84
  return { success: false };
85
85
  }
86
- dbDeleteClaudeWorkspace(id);
86
+ dbDeleteCodeWorkspace(id);
87
87
  return { success: true };
88
88
  }
@@ -13,7 +13,7 @@ import { ChatHeader } from "../chat/components/chat-header.js";
13
13
  import "@xterm/xterm/css/xterm.css";
14
14
  const STATUS = { connected: "#22c55e", connecting: "#eab308", disconnected: "#ef4444" };
15
15
  const RECONNECT_INTERVAL = 3e3;
16
- function CodePage({ session, claudeWorkspaceId }) {
16
+ function CodePage({ session, codeWorkspaceId }) {
17
17
  const containerRef = useRef(null);
18
18
  const termRef = useRef(null);
19
19
  const fitAddonRef = useRef(null);
@@ -39,7 +39,7 @@ function CodePage({ session, claudeWorkspaceId }) {
39
39
  if (!term) return;
40
40
  setStatus(STATUS.connecting);
41
41
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
42
- const ws = new WebSocket(`${protocol}//${window.location.host}/code/${claudeWorkspaceId}/ws`);
42
+ const ws = new WebSocket(`${protocol}//${window.location.host}/code/${codeWorkspaceId}/ws`);
43
43
  wsRef.current = ws;
44
44
  ws.binaryType = "arraybuffer";
45
45
  ws.onopen = () => {
@@ -56,7 +56,7 @@ function CodePage({ session, claudeWorkspaceId }) {
56
56
  term.write(payload);
57
57
  break;
58
58
  case "1":
59
- document.title = payload || "Claude Code";
59
+ document.title = payload || "Code Workspace";
60
60
  break;
61
61
  case "2":
62
62
  break;
@@ -69,7 +69,7 @@ function CodePage({ session, claudeWorkspaceId }) {
69
69
  ws.onerror = () => {
70
70
  ws.close();
71
71
  };
72
- }, [claudeWorkspaceId, setStatus]);
72
+ }, [codeWorkspaceId, setStatus]);
73
73
  useEffect(() => {
74
74
  const term = new Terminal({
75
75
  cursorBlink: true,
@@ -129,7 +129,7 @@ function CodePage({ session, claudeWorkspaceId }) {
129
129
  } }, children: /* @__PURE__ */ jsxs(SidebarProvider, { children: [
130
130
  /* @__PURE__ */ jsx(AppSidebar, { user: session.user }),
131
131
  /* @__PURE__ */ jsx(SidebarInset, { children: /* @__PURE__ */ jsxs("div", { className: "flex h-svh flex-col overflow-hidden", children: [
132
- /* @__PURE__ */ jsx(ChatHeader, { workspaceId: claudeWorkspaceId }),
132
+ /* @__PURE__ */ jsx(ChatHeader, { workspaceId: codeWorkspaceId }),
133
133
  /* @__PURE__ */ jsxs("div", { style: { position: "relative", flex: 1, minHeight: 0 }, children: [
134
134
  /* @__PURE__ */ jsx("div", { ref: containerRef, className: "mx-4", style: { height: "100%", borderRadius: 6, overflow: "hidden" } }),
135
135
  !connected && /* @__PURE__ */ jsx("div", { style: {
@@ -15,7 +15,7 @@ import '@xterm/xterm/css/xterm.css';
15
15
  const STATUS = { connected: '#22c55e', connecting: '#eab308', disconnected: '#ef4444' };
16
16
  const RECONNECT_INTERVAL = 3000;
17
17
 
18
- export default function CodePage({ session, claudeWorkspaceId }) {
18
+ export default function CodePage({ session, codeWorkspaceId }) {
19
19
  const containerRef = useRef(null);
20
20
  const termRef = useRef(null);
21
21
  const fitAddonRef = useRef(null);
@@ -45,7 +45,7 @@ export default function CodePage({ session, claudeWorkspaceId }) {
45
45
  setStatus(STATUS.connecting);
46
46
 
47
47
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
48
- const ws = new WebSocket(`${protocol}//${window.location.host}/code/${claudeWorkspaceId}/ws`);
48
+ const ws = new WebSocket(`${protocol}//${window.location.host}/code/${codeWorkspaceId}/ws`);
49
49
  wsRef.current = ws;
50
50
 
51
51
  ws.binaryType = 'arraybuffer';
@@ -67,7 +67,7 @@ export default function CodePage({ session, claudeWorkspaceId }) {
67
67
  term.write(payload);
68
68
  break;
69
69
  case '1': // title
70
- document.title = payload || 'Claude Code';
70
+ document.title = payload || 'Code Workspace';
71
71
  break;
72
72
  case '2': // prefs (ignored)
73
73
  break;
@@ -82,7 +82,7 @@ export default function CodePage({ session, claudeWorkspaceId }) {
82
82
  ws.onerror = () => {
83
83
  ws.close();
84
84
  };
85
- }, [claudeWorkspaceId, setStatus]);
85
+ }, [codeWorkspaceId, setStatus]);
86
86
 
87
87
  useEffect(() => {
88
88
  const term = new Terminal({
@@ -160,7 +160,7 @@ export default function CodePage({ session, claudeWorkspaceId }) {
160
160
  <AppSidebar user={session.user} />
161
161
  <SidebarInset>
162
162
  <div className="flex h-svh flex-col overflow-hidden">
163
- <ChatHeader workspaceId={claudeWorkspaceId} />
163
+ <ChatHeader workspaceId={codeWorkspaceId} />
164
164
  <div style={{ position: 'relative', flex: 1, minHeight: 0 }}>
165
165
  <div ref={containerRef} className="mx-4" style={{ height: '100%', borderRadius: 6, overflow: 'hidden' }} />
166
166
  {!connected && (
@@ -1,6 +1,6 @@
1
1
  import { WebSocketServer, WebSocket } from 'ws';
2
2
  import { decode } from 'next-auth/jwt';
3
- import { getClaudeWorkspaceById } from '../db/claude-workspaces.js';
3
+ import { getCodeWorkspaceById } from '../db/code-workspaces.js';
4
4
 
5
5
  async function isAuthenticated(req) {
6
6
  const cookies = req.headers.cookie || '';
@@ -37,16 +37,16 @@ export function attachCodeProxy(server) {
37
37
  return;
38
38
  }
39
39
 
40
- const claudeWorkspaceId = match[1];
41
- const claudeWorkspace = getClaudeWorkspaceById(claudeWorkspaceId);
42
- if (!claudeWorkspace) {
43
- console.log(`[ws-proxy] rejected: unknown workspace ${claudeWorkspaceId}`);
40
+ const codeWorkspaceId = match[1];
41
+ const codeWorkspace = getCodeWorkspaceById(codeWorkspaceId);
42
+ if (!codeWorkspace) {
43
+ console.log(`[ws-proxy] rejected: unknown workspace ${codeWorkspaceId}`);
44
44
  socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
45
45
  socket.destroy();
46
46
  return;
47
47
  }
48
48
 
49
- const container = claudeWorkspace.containerName;
49
+ const container = codeWorkspace.containerName;
50
50
 
51
51
  wss.handleUpgrade(req, socket, head, (clientWs) => {
52
52
  const backendWs = new WebSocket(`ws://${container}:7681/ws`, 'tty');
package/lib/db/chats.js CHANGED
@@ -50,13 +50,13 @@ export function getChatById(chatId) {
50
50
  }
51
51
 
52
52
  /**
53
- * Get a single chat by its claude_workspace_id.
53
+ * Get a single chat by its code_workspace_id.
54
54
  * @param {string} workspaceId
55
55
  * @returns {object|undefined}
56
56
  */
57
57
  export function getChatByWorkspaceId(workspaceId) {
58
58
  const db = getDb();
59
- return db.select().from(chats).where(eq(chats.claudeWorkspaceId, workspaceId)).get();
59
+ return db.select().from(chats).where(eq(chats.codeWorkspaceId, workspaceId)).get();
60
60
  }
61
61
 
62
62
  /**
@@ -132,14 +132,14 @@ export function getMessagesByChatId(chatId) {
132
132
  }
133
133
 
134
134
  /**
135
- * Link a chat to a claude workspace.
135
+ * Link a chat to a code workspace.
136
136
  * @param {string} chatId
137
137
  * @param {string} workspaceId
138
138
  */
139
139
  export function linkChatToWorkspace(chatId, workspaceId) {
140
140
  const db = getDb();
141
141
  db.update(chats)
142
- .set({ claudeWorkspaceId: workspaceId, updatedAt: Date.now() })
142
+ .set({ codeWorkspaceId: workspaceId, updatedAt: Date.now() })
143
143
  .where(eq(chats.id, chatId))
144
144
  .run();
145
145
  }
@@ -1,20 +1,21 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { eq, desc } from 'drizzle-orm';
3
3
  import { getDb } from './index.js';
4
- import { claudeWorkspaces } from './schema.js';
4
+ import { codeWorkspaces } from './schema.js';
5
5
 
6
6
  /**
7
- * Create a new claude workspace.
7
+ * Create a new code workspace.
8
8
  * @param {string} userId
9
9
  * @param {object} options
10
10
  * @param {string} [options.containerName] - Docker container DNS name (null until launched)
11
11
  * @param {string} [options.repo] - GitHub repo full name (e.g. "owner/repo")
12
12
  * @param {string} [options.branch] - Git branch name
13
- * @param {string} [options.title='Claude Workspace']
13
+ * @param {string} [options.title='Code Workspace']
14
+ * @param {string} [options.codingAgent='claude-code'] - Coding agent identifier
14
15
  * @param {string} [options.id] - Optional ID (UUID). Generated if not provided.
15
16
  * @returns {object} The created workspace
16
17
  */
17
- export function createClaudeWorkspace(userId, { containerName = null, repo = null, branch = null, title = 'Claude Workspace', id = null } = {}) {
18
+ export function createCodeWorkspace(userId, { containerName = null, repo = null, branch = null, title = 'Code Workspace', codingAgent = 'claude-code', id = null } = {}) {
18
19
  const db = getDb();
19
20
  const now = Date.now();
20
21
  const workspace = {
@@ -24,10 +25,11 @@ export function createClaudeWorkspace(userId, { containerName = null, repo = nul
24
25
  repo,
25
26
  branch,
26
27
  title,
28
+ codingAgent,
27
29
  createdAt: now,
28
30
  updatedAt: now,
29
31
  };
30
- db.insert(claudeWorkspaces).values(workspace).run();
32
+ db.insert(codeWorkspaces).values(workspace).run();
31
33
  return workspace;
32
34
  }
33
35
 
@@ -38,71 +40,71 @@ export function createClaudeWorkspace(userId, { containerName = null, repo = nul
38
40
  */
39
41
  export function updateContainerName(id, containerName) {
40
42
  const db = getDb();
41
- db.update(claudeWorkspaces)
43
+ db.update(codeWorkspaces)
42
44
  .set({ containerName, updatedAt: Date.now() })
43
- .where(eq(claudeWorkspaces.id, id))
45
+ .where(eq(codeWorkspaces.id, id))
44
46
  .run();
45
47
  }
46
48
 
47
49
  /**
48
- * Get a single claude workspace by ID.
50
+ * Get a single code workspace by ID.
49
51
  * @param {string} id
50
52
  * @returns {object|undefined}
51
53
  */
52
- export function getClaudeWorkspaceById(id) {
54
+ export function getCodeWorkspaceById(id) {
53
55
  const db = getDb();
54
- return db.select().from(claudeWorkspaces).where(eq(claudeWorkspaces.id, id)).get();
56
+ return db.select().from(codeWorkspaces).where(eq(codeWorkspaces.id, id)).get();
55
57
  }
56
58
 
57
59
  /**
58
- * Get all claude workspaces for a user, ordered by most recently updated.
60
+ * Get all code workspaces for a user, ordered by most recently updated.
59
61
  * @param {string} userId
60
62
  * @returns {object[]}
61
63
  */
62
- export function getClaudeWorkspacesByUser(userId) {
64
+ export function getCodeWorkspacesByUser(userId) {
63
65
  const db = getDb();
64
66
  return db
65
67
  .select()
66
- .from(claudeWorkspaces)
67
- .where(eq(claudeWorkspaces.userId, userId))
68
- .orderBy(desc(claudeWorkspaces.updatedAt))
68
+ .from(codeWorkspaces)
69
+ .where(eq(codeWorkspaces.userId, userId))
70
+ .orderBy(desc(codeWorkspaces.updatedAt))
69
71
  .all();
70
72
  }
71
73
 
72
74
  /**
73
- * Update a claude workspace's title.
75
+ * Update a code workspace's title.
74
76
  * @param {string} id
75
77
  * @param {string} title
76
78
  */
77
- export function updateClaudeWorkspaceTitle(id, title) {
79
+ export function updateCodeWorkspaceTitle(id, title) {
78
80
  const db = getDb();
79
- db.update(claudeWorkspaces)
81
+ db.update(codeWorkspaces)
80
82
  .set({ title, updatedAt: Date.now() })
81
- .where(eq(claudeWorkspaces.id, id))
83
+ .where(eq(codeWorkspaces.id, id))
82
84
  .run();
83
85
  }
84
86
 
85
87
  /**
86
- * Toggle a claude workspace's starred status.
88
+ * Toggle a code workspace's starred status.
87
89
  * @param {string} id
88
90
  * @returns {number} The new starred value (0 or 1)
89
91
  */
90
- export function toggleClaudeWorkspaceStarred(id) {
92
+ export function toggleCodeWorkspaceStarred(id) {
91
93
  const db = getDb();
92
- const workspace = db.select({ starred: claudeWorkspaces.starred }).from(claudeWorkspaces).where(eq(claudeWorkspaces.id, id)).get();
94
+ const workspace = db.select({ starred: codeWorkspaces.starred }).from(codeWorkspaces).where(eq(codeWorkspaces.id, id)).get();
93
95
  const newValue = workspace?.starred ? 0 : 1;
94
- db.update(claudeWorkspaces)
96
+ db.update(codeWorkspaces)
95
97
  .set({ starred: newValue })
96
- .where(eq(claudeWorkspaces.id, id))
98
+ .where(eq(codeWorkspaces.id, id))
97
99
  .run();
98
100
  return newValue;
99
101
  }
100
102
 
101
103
  /**
102
- * Delete a claude workspace.
104
+ * Delete a code workspace.
103
105
  * @param {string} id
104
106
  */
105
- export function deleteClaudeWorkspace(id) {
107
+ export function deleteCodeWorkspace(id) {
106
108
  const db = getDb();
107
- db.delete(claudeWorkspaces).where(eq(claudeWorkspaces.id, id)).run();
109
+ db.delete(codeWorkspaces).where(eq(codeWorkspaces.id, id)).run();
108
110
  }
package/lib/db/schema.js CHANGED
@@ -14,7 +14,7 @@ export const chats = sqliteTable('chats', {
14
14
  userId: text('user_id').notNull(),
15
15
  title: text('title').notNull().default('New Chat'),
16
16
  starred: integer('starred').notNull().default(0),
17
- claudeWorkspaceId: text('claude_workspace_id'),
17
+ codeWorkspaceId: text('code_workspace_id'),
18
18
  createdAt: integer('created_at').notNull(),
19
19
  updatedAt: integer('updated_at').notNull(),
20
20
  });
@@ -42,13 +42,14 @@ export const subscriptions = sqliteTable('subscriptions', {
42
42
  createdAt: integer('created_at').notNull(),
43
43
  });
44
44
 
45
- export const claudeWorkspaces = sqliteTable('claude_workspaces', {
45
+ export const codeWorkspaces = sqliteTable('code_workspaces', {
46
46
  id: text('id').primaryKey(),
47
47
  userId: text('user_id').notNull(),
48
48
  containerName: text('container_name').unique(),
49
49
  repo: text('repo'),
50
50
  branch: text('branch'),
51
- title: text('title').notNull().default('Claude Workspace'),
51
+ title: text('title').notNull().default('Code Workspace'),
52
+ codingAgent: text('coding_agent').notNull().default('claude-code'),
52
53
  starred: integer('starred').notNull().default(0),
53
54
  createdAt: integer('created_at').notNull(),
54
55
  updatedAt: integer('updated_at').notNull(),
@@ -50,16 +50,21 @@ async function detectNetwork() {
50
50
  }
51
51
 
52
52
  /**
53
- * Create and start a Claude workspace Docker container.
53
+ * Create and start a code workspace Docker container.
54
54
  * @param {object} options
55
55
  * @param {string} options.containerName - Docker container name
56
56
  * @param {string} options.repo - GitHub repo full name (e.g. "owner/repo")
57
57
  * @param {string} options.branch - Git branch name
58
+ * @param {string} [options.codingAgent='claude-code'] - Coding agent identifier
58
59
  * @returns {Promise<{containerId: string}>}
59
60
  */
60
- async function createClaudeWorkspaceContainer({ containerName, repo, branch }) {
61
+ async function createCodeWorkspaceContainer({ containerName, repo, branch, codingAgent = 'claude-code' }) {
62
+ if (codingAgent !== 'claude-code') {
63
+ throw new Error(`Unsupported coding agent: ${codingAgent}`);
64
+ }
65
+
61
66
  const version = process.env.THEPOPEBOT_VERSION || 'latest';
62
- const image = `stephengpope/thepopebot:claude-workspace-${version}`;
67
+ const image = `stephengpope/thepopebot:claude-code-workspace-${version}`;
63
68
  const network = await detectNetwork();
64
69
 
65
70
  const env = [
@@ -97,4 +102,4 @@ async function createClaudeWorkspaceContainer({ containerName, repo, branch }) {
97
102
  return { containerId };
98
103
  }
99
104
 
100
- export { createClaudeWorkspaceContainer };
105
+ export { createCodeWorkspaceContainer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.72",
3
+ "version": "1.2.73-beta.1",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {
@@ -3,6 +3,6 @@ import { CodePage } from 'thepopebot/code';
3
3
 
4
4
  export default async function CodeRoute({ params }) {
5
5
  const session = await auth();
6
- const { claudeWorkspaceId } = await params;
7
- return <CodePage session={session} claudeWorkspaceId={claudeWorkspaceId} />;
6
+ const { codeWorkspaceId } = await params;
7
+ return <CodePage session={session} codeWorkspaceId={codeWorkspaceId} />;
8
8
  }
@@ -41,18 +41,18 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
41
41
  RUN npm install -g @anthropic-ai/claude-code
42
42
 
43
43
  # Non-root user
44
- RUN useradd -m -s /bin/bash claude \
45
- && mkdir -p /home/claude/claude-workspace \
46
- && chown claude:claude /home/claude/claude-workspace
44
+ RUN useradd -m -s /bin/bash claude-code \
45
+ && mkdir -p /home/claude-code/workspace \
46
+ && chown claude-code:claude-code /home/claude-code/workspace
47
47
 
48
- COPY .tmux.conf /home/claude/.tmux.conf
49
- RUN chown claude:claude /home/claude/.tmux.conf
48
+ COPY .tmux.conf /home/claude-code/.tmux.conf
49
+ RUN chown claude-code:claude-code /home/claude-code/.tmux.conf
50
50
 
51
51
  COPY entrypoint.sh /entrypoint.sh
52
52
  RUN chmod +x /entrypoint.sh
53
53
 
54
- USER claude
55
- WORKDIR /home/claude/claude-workspace
54
+ USER claude-code
55
+ WORKDIR /home/claude-code/workspace
56
56
 
57
57
  ENV REPO=""
58
58
  ENV BRANCH=main
@@ -11,10 +11,10 @@ git config --global user.email "$GH_USER_EMAIL"
11
11
 
12
12
  # Clone repo
13
13
  if [ -n "$REPO" ]; then
14
- git clone --branch "$BRANCH" "https://github.com/$REPO" /home/claude/claude-workspace
14
+ git clone --branch "$BRANCH" "https://github.com/$REPO" /home/claude-code/workspace
15
15
  fi
16
16
 
17
- cd /home/claude/claude-workspace
17
+ cd /home/claude-code/workspace
18
18
  WORKSPACE_DIR=$(pwd)
19
19
 
20
20
  # Claude Code auth — use OAuth token, not API key