From c53f3a5fac094576ccf18e1ac566610cf29f6a20 Mon Sep 17 00:00:00 2001
From: Nicholai
Date: Mon, 16 Feb 2026 22:05:01 -0700
Subject: [PATCH] feat(auth): add Anthropic OAuth with PKCE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Browser-based OAuth flow using Anthropic's hosted callback.
Users authorize on claude.ai, paste the code back, and tokens
are encrypted and stored in D1. Includes auto-refresh, Bearer
auth via custom fetch wrapper, and mcp_ tool name prefixing
required by the OAuth endpoint.
Also fixes provider-config save bug that required encryption
key unconditionally — now only checks when API key is present.
---
drizzle/0029_fantastic_mach_iv.sql | 8 +
drizzle/meta/0029_snapshot.json | 5403 ++++++++++++++++++++++
drizzle/meta/_journal.json | 7 +
packages/agent-core/src/client.ts | 32 +-
packages/agent-core/src/index.ts | 6 +
packages/agent-core/src/loop.ts | 28 +-
packages/agent-core/src/oauth.ts | 118 +
packages/agent-server/src/stream.ts | 4 +
src/app/actions/anthropic-oauth.ts | 238 +
src/app/actions/provider-config.ts | 69 +-
src/app/api/agent/route.ts | 27 +-
src/components/settings/ai-model-tab.tsx | 343 +-
src/db/schema-ai-config.ts | 19 +
src/lib/anthropic-oauth-client.ts | 52 +
14 files changed, 6227 insertions(+), 127 deletions(-)
create mode 100644 drizzle/0029_fantastic_mach_iv.sql
create mode 100644 drizzle/meta/0029_snapshot.json
create mode 100644 packages/agent-core/src/oauth.ts
create mode 100644 src/app/actions/anthropic-oauth.ts
create mode 100644 src/lib/anthropic-oauth-client.ts
diff --git a/drizzle/0029_fantastic_mach_iv.sql b/drizzle/0029_fantastic_mach_iv.sql
new file mode 100644
index 0000000..2366a38
--- /dev/null
+++ b/drizzle/0029_fantastic_mach_iv.sql
@@ -0,0 +1,8 @@
+CREATE TABLE `anthropic_oauth_tokens` (
+ `user_id` text PRIMARY KEY NOT NULL,
+ `access_token` text NOT NULL,
+ `refresh_token` text NOT NULL,
+ `expires_at` text NOT NULL,
+ `updated_at` text NOT NULL,
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
+);
diff --git a/drizzle/meta/0029_snapshot.json b/drizzle/meta/0029_snapshot.json
new file mode 100644
index 0000000..9dd646f
--- /dev/null
+++ b/drizzle/meta/0029_snapshot.json
@@ -0,0 +1,5403 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "3a8e94b1-0b92-4d6a-b1c5-624913e899ae",
+ "prevId": "9bcc3080-7092-4a86-8d8e-022df0344be9",
+ "tables": {
+ "agent_conversations": {
+ "name": "agent_conversations",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_conversations_user_id_users_id_fk": {
+ "name": "agent_conversations_user_id_users_id_fk",
+ "tableFrom": "agent_conversations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "agent_memories": {
+ "name": "agent_memories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "conversation_id": {
+ "name": "conversation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "embedding": {
+ "name": "embedding",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_memories_conversation_id_agent_conversations_id_fk": {
+ "name": "agent_memories_conversation_id_agent_conversations_id_fk",
+ "tableFrom": "agent_memories",
+ "tableTo": "agent_conversations",
+ "columnsFrom": [
+ "conversation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_memories_user_id_users_id_fk": {
+ "name": "agent_memories_user_id_users_id_fk",
+ "tableFrom": "agent_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "customers": {
+ "name": "customers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "company": {
+ "name": "company",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "netsuite_id": {
+ "name": "netsuite_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "customers_organization_id_organizations_id_fk": {
+ "name": "customers_organization_id_organizations_id_fk",
+ "tableFrom": "customers",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feedback": {
+ "name": "feedback",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "page_url": {
+ "name": "page_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "viewport_width": {
+ "name": "viewport_width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "viewport_height": {
+ "name": "viewport_height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_hash": {
+ "name": "ip_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "github_issue_url": {
+ "name": "github_issue_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "feedback_interviews": {
+ "name": "feedback_interviews",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_role": {
+ "name": "user_role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "responses": {
+ "name": "responses",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pain_points": {
+ "name": "pain_points",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "feature_requests": {
+ "name": "feature_requests",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "overall_sentiment": {
+ "name": "overall_sentiment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "github_issue_url": {
+ "name": "github_issue_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "conversation_id": {
+ "name": "conversation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "feedback_interviews_user_id_users_id_fk": {
+ "name": "feedback_interviews_user_id_users_id_fk",
+ "tableFrom": "feedback_interviews",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "group_members": {
+ "name": "group_members",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "group_members_group_id_groups_id_fk": {
+ "name": "group_members_group_id_groups_id_fk",
+ "tableFrom": "group_members",
+ "tableTo": "groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_members_user_id_users_id_fk": {
+ "name": "group_members_user_id_users_id_fk",
+ "tableFrom": "group_members",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "groups": {
+ "name": "groups",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "groups_organization_id_organizations_id_fk": {
+ "name": "groups_organization_id_organizations_id_fk",
+ "tableFrom": "groups",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "organization_invites": {
+ "name": "organization_invites",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "code": {
+ "name": "code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'office'"
+ },
+ "max_uses": {
+ "name": "max_uses",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "use_count": {
+ "name": "use_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "organization_invites_code_unique": {
+ "name": "organization_invites_code_unique",
+ "columns": [
+ "code"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "organization_invites_organization_id_organizations_id_fk": {
+ "name": "organization_invites_organization_id_organizations_id_fk",
+ "tableFrom": "organization_invites",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "organization_invites_created_by_users_id_fk": {
+ "name": "organization_invites_created_by_users_id_fk",
+ "tableFrom": "organization_invites",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "organization_members": {
+ "name": "organization_members",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "organization_members_organization_id_organizations_id_fk": {
+ "name": "organization_members_organization_id_organizations_id_fk",
+ "tableFrom": "organization_members",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "organization_members_user_id_users_id_fk": {
+ "name": "organization_members_user_id_users_id_fk",
+ "tableFrom": "organization_members",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "organizations": {
+ "name": "organizations",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "logo_url": {
+ "name": "logo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "organizations_slug_unique": {
+ "name": "organizations_slug_unique",
+ "columns": [
+ "slug"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "project_members": {
+ "name": "project_members",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_members_project_id_projects_id_fk": {
+ "name": "project_members_project_id_projects_id_fk",
+ "tableFrom": "project_members",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "project_members_user_id_users_id_fk": {
+ "name": "project_members_user_id_users_id_fk",
+ "tableFrom": "project_members",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "projects": {
+ "name": "projects",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'OPEN'"
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "client_name": {
+ "name": "client_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "project_manager": {
+ "name": "project_manager",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "netsuite_job_id": {
+ "name": "netsuite_job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "projects_organization_id_organizations_id_fk": {
+ "name": "projects_organization_id_organizations_id_fk",
+ "tableFrom": "projects",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "push_tokens": {
+ "name": "push_tokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "push_tokens_user_id_users_id_fk": {
+ "name": "push_tokens_user_id_users_id_fk",
+ "tableFrom": "push_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "schedule_baselines": {
+ "name": "schedule_baselines",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "snapshot_data": {
+ "name": "snapshot_data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "schedule_baselines_project_id_projects_id_fk": {
+ "name": "schedule_baselines_project_id_projects_id_fk",
+ "tableFrom": "schedule_baselines",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "schedule_tasks": {
+ "name": "schedule_tasks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "start_date": {
+ "name": "start_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workdays": {
+ "name": "workdays",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "end_date_calculated": {
+ "name": "end_date_calculated",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "phase": {
+ "name": "phase",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'PENDING'"
+ },
+ "is_critical_path": {
+ "name": "is_critical_path",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_milestone": {
+ "name": "is_milestone",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "percent_complete": {
+ "name": "percent_complete",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "assigned_to": {
+ "name": "assigned_to",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "schedule_tasks_project_id_projects_id_fk": {
+ "name": "schedule_tasks_project_id_projects_id_fk",
+ "tableFrom": "schedule_tasks",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "slab_memories": {
+ "name": "slab_memories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "memory_type": {
+ "name": "memory_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tags": {
+ "name": "tags",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "importance": {
+ "name": "importance",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0.7
+ },
+ "pinned": {
+ "name": "pinned",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "access_count": {
+ "name": "access_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_accessed_at": {
+ "name": "last_accessed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "slab_memories_user_id_users_id_fk": {
+ "name": "slab_memories_user_id_users_id_fk",
+ "tableFrom": "slab_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "task_dependencies": {
+ "name": "task_dependencies",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "predecessor_id": {
+ "name": "predecessor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "successor_id": {
+ "name": "successor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'FS'"
+ },
+ "lag_days": {
+ "name": "lag_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "task_dependencies_predecessor_id_schedule_tasks_id_fk": {
+ "name": "task_dependencies_predecessor_id_schedule_tasks_id_fk",
+ "tableFrom": "task_dependencies",
+ "tableTo": "schedule_tasks",
+ "columnsFrom": [
+ "predecessor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "task_dependencies_successor_id_schedule_tasks_id_fk": {
+ "name": "task_dependencies_successor_id_schedule_tasks_id_fk",
+ "tableFrom": "task_dependencies",
+ "tableTo": "schedule_tasks",
+ "columnsFrom": [
+ "successor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "team_members": {
+ "name": "team_members",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_members_team_id_teams_id_fk": {
+ "name": "team_members_team_id_teams_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_members_user_id_users_id_fk": {
+ "name": "team_members_user_id_users_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "teams": {
+ "name": "teams",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "teams_organization_id_organizations_id_fk": {
+ "name": "teams_organization_id_organizations_id_fk",
+ "tableFrom": "teams",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'office'"
+ },
+ "google_email": {
+ "name": "google_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "last_login_at": {
+ "name": "last_login_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vendors": {
+ "name": "vendors",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'Subcontractor'"
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "netsuite_id": {
+ "name": "netsuite_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "vendors_organization_id_organizations_id_fk": {
+ "name": "vendors_organization_id_organizations_id_fk",
+ "tableFrom": "vendors",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "workday_exceptions": {
+ "name": "workday_exceptions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "start_date": {
+ "name": "start_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "end_date": {
+ "name": "end_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'non_working'"
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'company_holiday'"
+ },
+ "recurrence": {
+ "name": "recurrence",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'one_time'"
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workday_exceptions_project_id_projects_id_fk": {
+ "name": "workday_exceptions_project_id_projects_id_fk",
+ "tableFrom": "workday_exceptions",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "credit_memos": {
+ "name": "credit_memos",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "netsuite_id": {
+ "name": "netsuite_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "customer_id": {
+ "name": "customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memo_number": {
+ "name": "memo_number",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'draft'"
+ },
+ "issue_date": {
+ "name": "issue_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "total": {
+ "name": "total",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "amount_applied": {
+ "name": "amount_applied",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "amount_remaining": {
+ "name": "amount_remaining",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "memo": {
+ "name": "memo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "line_items": {
+ "name": "line_items",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "credit_memos_customer_id_customers_id_fk": {
+ "name": "credit_memos_customer_id_customers_id_fk",
+ "tableFrom": "credit_memos",
+ "tableTo": "customers",
+ "columnsFrom": [
+ "customer_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "credit_memos_project_id_projects_id_fk": {
+ "name": "credit_memos_project_id_projects_id_fk",
+ "tableFrom": "credit_memos",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "invoices": {
+ "name": "invoices",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "netsuite_id": {
+ "name": "netsuite_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "customer_id": {
+ "name": "customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoice_number": {
+ "name": "invoice_number",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'draft'"
+ },
+ "issue_date": {
+ "name": "issue_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "due_date": {
+ "name": "due_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subtotal": {
+ "name": "subtotal",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "tax": {
+ "name": "tax",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "total": {
+ "name": "total",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "amount_paid": {
+ "name": "amount_paid",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "amount_due": {
+ "name": "amount_due",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "memo": {
+ "name": "memo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "line_items": {
+ "name": "line_items",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invoices_customer_id_customers_id_fk": {
+ "name": "invoices_customer_id_customers_id_fk",
+ "tableFrom": "invoices",
+ "tableTo": "customers",
+ "columnsFrom": [
+ "customer_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "invoices_project_id_projects_id_fk": {
+ "name": "invoices_project_id_projects_id_fk",
+ "tableFrom": "invoices",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "netsuite_auth": {
+ "name": "netsuite_auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "access_token_encrypted": {
+ "name": "access_token_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token_encrypted": {
+ "name": "refresh_token_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_in": {
+ "name": "expires_in",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "issued_at": {
+ "name": "issued_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "netsuite_sync_log": {
+ "name": "netsuite_sync_log",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sync_type": {
+ "name": "sync_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "record_type": {
+ "name": "record_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "direction": {
+ "name": "direction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "records_processed": {
+ "name": "records_processed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "records_failed": {
+ "name": "records_failed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "error_summary": {
+ "name": "error_summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "netsuite_sync_metadata": {
+ "name": "netsuite_sync_metadata",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "local_table": {
+ "name": "local_table",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "local_record_id": {
+ "name": "local_record_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "netsuite_record_type": {
+ "name": "netsuite_record_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "netsuite_internal_id": {
+ "name": "netsuite_internal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_synced_at": {
+ "name": "last_synced_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_modified_local": {
+ "name": "last_modified_local",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_modified_remote": {
+ "name": "last_modified_remote",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'synced'"
+ },
+ "conflict_data": {
+ "name": "conflict_data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "retry_count": {
+ "name": "retry_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "payments": {
+ "name": "payments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "netsuite_id": {
+ "name": "netsuite_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "customer_id": {
+ "name": "customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vendor_id": {
+ "name": "vendor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "payment_type": {
+ "name": "payment_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "amount": {
+ "name": "amount",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "payment_date": {
+ "name": "payment_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "payment_method": {
+ "name": "payment_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reference_number": {
+ "name": "reference_number",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memo": {
+ "name": "memo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "payments_customer_id_customers_id_fk": {
+ "name": "payments_customer_id_customers_id_fk",
+ "tableFrom": "payments",
+ "tableTo": "customers",
+ "columnsFrom": [
+ "customer_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "payments_vendor_id_vendors_id_fk": {
+ "name": "payments_vendor_id_vendors_id_fk",
+ "tableFrom": "payments",
+ "tableTo": "vendors",
+ "columnsFrom": [
+ "vendor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "payments_project_id_projects_id_fk": {
+ "name": "payments_project_id_projects_id_fk",
+ "tableFrom": "payments",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "vendor_bills": {
+ "name": "vendor_bills",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "netsuite_id": {
+ "name": "netsuite_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vendor_id": {
+ "name": "vendor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bill_number": {
+ "name": "bill_number",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "bill_date": {
+ "name": "bill_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "due_date": {
+ "name": "due_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subtotal": {
+ "name": "subtotal",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "tax": {
+ "name": "tax",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "total": {
+ "name": "total",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "amount_paid": {
+ "name": "amount_paid",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "amount_due": {
+ "name": "amount_due",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "memo": {
+ "name": "memo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "line_items": {
+ "name": "line_items",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "vendor_bills_vendor_id_vendors_id_fk": {
+ "name": "vendor_bills_vendor_id_vendors_id_fk",
+ "tableFrom": "vendor_bills",
+ "tableTo": "vendors",
+ "columnsFrom": [
+ "vendor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "vendor_bills_project_id_projects_id_fk": {
+ "name": "vendor_bills_project_id_projects_id_fk",
+ "tableFrom": "vendor_bills",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_config": {
+ "name": "plugin_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_encrypted": {
+ "name": "is_encrypted",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "plugin_config_plugin_id_plugins_id_fk": {
+ "name": "plugin_config_plugin_id_plugins_id_fk",
+ "tableFrom": "plugin_config",
+ "tableTo": "plugins",
+ "columnsFrom": [
+ "plugin_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_events": {
+ "name": "plugin_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "plugin_events_plugin_id_plugins_id_fk": {
+ "name": "plugin_events_plugin_id_plugins_id_fk",
+ "tableFrom": "plugin_events",
+ "tableTo": "plugins",
+ "columnsFrom": [
+ "plugin_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "plugin_events_user_id_users_id_fk": {
+ "name": "plugin_events_user_id_users_id_fk",
+ "tableFrom": "plugin_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugins": {
+ "name": "plugins",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "capabilities": {
+ "name": "capabilities",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "required_env_vars": {
+ "name": "required_env_vars",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'disabled'"
+ },
+ "status_reason": {
+ "name": "status_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enabled_by": {
+ "name": "enabled_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "installed_at": {
+ "name": "installed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "plugins_enabled_by_users_id_fk": {
+ "name": "plugins_enabled_by_users_id_fk",
+ "tableFrom": "plugins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "enabled_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "agent_items": {
+ "name": "agent_items",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "conversation_id": {
+ "name": "conversation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "done": {
+ "name": "done",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_items_user_id_users_id_fk": {
+ "name": "agent_items_user_id_users_id_fk",
+ "tableFrom": "agent_items",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "agent_config": {
+ "name": "agent_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_id": {
+ "name": "model_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_name": {
+ "name": "model_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "prompt_cost": {
+ "name": "prompt_cost",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "completion_cost": {
+ "name": "completion_cost",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "context_length": {
+ "name": "context_length",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "max_cost_per_million": {
+ "name": "max_cost_per_million",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "allow_user_selection": {
+ "name": "allow_user_selection",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_config_updated_by_users_id_fk": {
+ "name": "agent_config_updated_by_users_id_fk",
+ "tableFrom": "agent_config",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "agent_usage": {
+ "name": "agent_usage",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "conversation_id": {
+ "name": "conversation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_id": {
+ "name": "model_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "prompt_tokens": {
+ "name": "prompt_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "completion_tokens": {
+ "name": "completion_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "total_tokens": {
+ "name": "total_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "estimated_cost": {
+ "name": "estimated_cost",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_usage_conversation_id_agent_conversations_id_fk": {
+ "name": "agent_usage_conversation_id_agent_conversations_id_fk",
+ "tableFrom": "agent_usage",
+ "tableTo": "agent_conversations",
+ "columnsFrom": [
+ "conversation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_usage_user_id_users_id_fk": {
+ "name": "agent_usage_user_id_users_id_fk",
+ "tableFrom": "agent_usage",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "anthropic_oauth_tokens": {
+ "name": "anthropic_oauth_tokens",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "anthropic_oauth_tokens_user_id_users_id_fk": {
+ "name": "anthropic_oauth_tokens_user_id_users_id_fk",
+ "tableFrom": "anthropic_oauth_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_model_preference": {
+ "name": "user_model_preference",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_id": {
+ "name": "model_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "prompt_cost": {
+ "name": "prompt_cost",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "completion_cost": {
+ "name": "completion_cost",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_model_preference_user_id_users_id_fk": {
+ "name": "user_model_preference_user_id_users_id_fk",
+ "tableFrom": "user_model_preference",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_provider_config": {
+ "name": "user_provider_config",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider_type": {
+ "name": "provider_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "api_key": {
+ "name": "api_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_url": {
+ "name": "base_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model_overrides": {
+ "name": "model_overrides",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_provider_config_user_id_users_id_fk": {
+ "name": "user_provider_config_user_id_users_id_fk",
+ "tableFrom": "user_provider_config",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "custom_themes": {
+ "name": "custom_themes",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "theme_data": {
+ "name": "theme_data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "custom_themes_user_id_users_id_fk": {
+ "name": "custom_themes_user_id_users_id_fk",
+ "tableFrom": "custom_themes",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_theme_preference": {
+ "name": "user_theme_preference",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "active_theme_id": {
+ "name": "active_theme_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_theme_preference_user_id_users_id_fk": {
+ "name": "user_theme_preference_user_id_users_id_fk",
+ "tableFrom": "user_theme_preference",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "google_auth": {
+ "name": "google_auth",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_account_key_encrypted": {
+ "name": "service_account_key_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_domain": {
+ "name": "workspace_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "shared_drive_id": {
+ "name": "shared_drive_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "shared_drive_name": {
+ "name": "shared_drive_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "google_auth_organization_id_organizations_id_fk": {
+ "name": "google_auth_organization_id_organizations_id_fk",
+ "tableFrom": "google_auth",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "google_auth_connected_by_users_id_fk": {
+ "name": "google_auth_connected_by_users_id_fk",
+ "tableFrom": "google_auth",
+ "tableTo": "users",
+ "columnsFrom": [
+ "connected_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "google_starred_files": {
+ "name": "google_starred_files",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "google_file_id": {
+ "name": "google_file_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "google_starred_files_user_id_users_id_fk": {
+ "name": "google_starred_files_user_id_users_id_fk",
+ "tableFrom": "google_starred_files",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "custom_dashboards": {
+ "name": "custom_dashboards",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "spec_data": {
+ "name": "spec_data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "queries": {
+ "name": "queries",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "render_prompt": {
+ "name": "render_prompt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "custom_dashboards_user_id_users_id_fk": {
+ "name": "custom_dashboards_user_id_users_id_fk",
+ "tableFrom": "custom_dashboards",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "mcp_api_keys": {
+ "name": "mcp_api_keys",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key_prefix": {
+ "name": "key_prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key_hash": {
+ "name": "key_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_api_keys_user_id_users_id_fk": {
+ "name": "mcp_api_keys_user_id_users_id_fk",
+ "tableFrom": "mcp_api_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "mcp_servers": {
+ "name": "mcp_servers",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "headers": {
+ "name": "headers",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_enabled": {
+ "name": "is_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_org_id_organizations_id_fk": {
+ "name": "mcp_servers_org_id_organizations_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "org_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_servers_created_by_users_id_fk": {
+ "name": "mcp_servers_created_by_users_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "mcp_usage": {
+ "name": "mcp_usage",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "api_key_id": {
+ "name": "api_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "success": {
+ "name": "success",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_usage_api_key_id_mcp_api_keys_id_fk": {
+ "name": "mcp_usage_api_key_id_mcp_api_keys_id_fk",
+ "tableFrom": "mcp_usage",
+ "tableTo": "mcp_api_keys",
+ "columnsFrom": [
+ "api_key_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_usage_user_id_users_id_fk": {
+ "name": "mcp_usage_user_id_users_id_fk",
+ "tableFrom": "mcp_usage",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "channel_categories": {
+ "name": "channel_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "collapsed_by_default": {
+ "name": "collapsed_by_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_categories_organization_id_organizations_id_fk": {
+ "name": "channel_categories_organization_id_organizations_id_fk",
+ "tableFrom": "channel_categories",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "channel_members": {
+ "name": "channel_members",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'member'"
+ },
+ "notify_level": {
+ "name": "notify_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'all'"
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_members_channel_id_channels_id_fk": {
+ "name": "channel_members_channel_id_channels_id_fk",
+ "tableFrom": "channel_members",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_members_user_id_users_id_fk": {
+ "name": "channel_members_user_id_users_id_fk",
+ "tableFrom": "channel_members",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "channel_read_state": {
+ "name": "channel_read_state",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_read_message_id": {
+ "name": "last_read_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "unread_count": {
+ "name": "unread_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_read_state_user_id_users_id_fk": {
+ "name": "channel_read_state_user_id_users_id_fk",
+ "tableFrom": "channel_read_state",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_read_state_channel_id_channels_id_fk": {
+ "name": "channel_read_state_channel_id_channels_id_fk",
+ "tableFrom": "channel_read_state",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "channels": {
+ "name": "channels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'text'"
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_private": {
+ "name": "is_private",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channels_organization_id_organizations_id_fk": {
+ "name": "channels_organization_id_organizations_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "organizations",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channels_project_id_projects_id_fk": {
+ "name": "channels_project_id_projects_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channels_category_id_channel_categories_id_fk": {
+ "name": "channels_category_id_channel_categories_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "channel_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_created_by_users_id_fk": {
+ "name": "channels_created_by_users_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "message_attachments": {
+ "name": "message_attachments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "message_id": {
+ "name": "message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_size": {
+ "name": "file_size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "r2_path": {
+ "name": "r2_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "width": {
+ "name": "width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "message_attachments_message_id_messages_id_fk": {
+ "name": "message_attachments_message_id_messages_id_fk",
+ "tableFrom": "message_attachments",
+ "tableTo": "messages",
+ "columnsFrom": [
+ "message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "message_mentions": {
+ "name": "message_mentions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "message_id": {
+ "name": "message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "mention_type": {
+ "name": "mention_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "message_mentions_message_id_messages_id_fk": {
+ "name": "message_mentions_message_id_messages_id_fk",
+ "tableFrom": "message_mentions",
+ "tableTo": "messages",
+ "columnsFrom": [
+ "message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "message_reactions": {
+ "name": "message_reactions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "message_id": {
+ "name": "message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emoji": {
+ "name": "emoji",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "message_reactions_message_id_messages_id_fk": {
+ "name": "message_reactions_message_id_messages_id_fk",
+ "tableFrom": "message_reactions",
+ "tableTo": "messages",
+ "columnsFrom": [
+ "message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "message_reactions_user_id_users_id_fk": {
+ "name": "message_reactions_user_id_users_id_fk",
+ "tableFrom": "message_reactions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "messages": {
+ "name": "messages",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_html": {
+ "name": "content_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "edited_at": {
+ "name": "edited_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deleted_by": {
+ "name": "deleted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_pinned": {
+ "name": "is_pinned",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "reply_count": {
+ "name": "reply_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_reply_at": {
+ "name": "last_reply_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "messages_channel_id_channels_id_fk": {
+ "name": "messages_channel_id_channels_id_fk",
+ "tableFrom": "messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "messages_user_id_users_id_fk": {
+ "name": "messages_user_id_users_id_fk",
+ "tableFrom": "messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "messages_deleted_by_users_id_fk": {
+ "name": "messages_deleted_by_users_id_fk",
+ "tableFrom": "messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "deleted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "typing_sessions": {
+ "name": "typing_sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "typing_sessions_channel_id_channels_id_fk": {
+ "name": "typing_sessions_channel_id_channels_id_fk",
+ "tableFrom": "typing_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "typing_sessions_user_id_users_id_fk": {
+ "name": "typing_sessions_user_id_users_id_fk",
+ "tableFrom": "typing_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_presence": {
+ "name": "user_presence",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'offline'"
+ },
+ "status_message": {
+ "name": "status_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_seen_at": {
+ "name": "last_seen_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_presence_user_id_users_id_fk": {
+ "name": "user_presence_user_id_users_id_fk",
+ "tableFrom": "user_presence",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "local_sync_metadata": {
+ "name": "local_sync_metadata",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "record_id": {
+ "name": "record_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "vector_clock": {
+ "name": "vector_clock",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_modified_at": {
+ "name": "last_modified_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending_sync'"
+ },
+ "conflict_data": {
+ "name": "conflict_data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "local_sync_metadata_table_record_idx": {
+ "name": "local_sync_metadata_table_record_idx",
+ "columns": [
+ "table_name",
+ "record_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "mutation_queue": {
+ "name": "mutation_queue",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "operation": {
+ "name": "operation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "record_id": {
+ "name": "record_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "vector_clock": {
+ "name": "vector_clock",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "retry_count": {
+ "name": "retry_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "process_after": {
+ "name": "process_after",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "mutation_queue_status_created_idx": {
+ "name": "mutation_queue_status_created_idx",
+ "columns": [
+ "status",
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sync_checkpoint": {
+ "name": "sync_checkpoint",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_sync_cursor": {
+ "name": "last_sync_cursor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "local_vector_clock": {
+ "name": "local_vector_clock",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "synced_at": {
+ "name": "synced_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "sync_checkpoint_table_name_unique": {
+ "name": "sync_checkpoint_table_name_unique",
+ "columns": [
+ "table_name"
+ ],
+ "isUnique": true
+ },
+ "sync_checkpoint_table_name_idx": {
+ "name": "sync_checkpoint_table_name_idx",
+ "columns": [
+ "table_name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "sync_tombstone": {
+ "name": "sync_tombstone",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "record_id": {
+ "name": "record_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "vector_clock": {
+ "name": "vector_clock",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "synced": {
+ "name": "synced",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ }
+ },
+ "indexes": {
+ "sync_tombstone_table_record_idx": {
+ "name": "sync_tombstone_table_record_idx",
+ "columns": [
+ "table_name",
+ "record_id"
+ ],
+ "isUnique": false
+ },
+ "sync_tombstone_synced_idx": {
+ "name": "sync_tombstone_synced_idx",
+ "columns": [
+ "synced"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 1038741..0230cf7 100755
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -204,6 +204,13 @@
"when": 1771295883108,
"tag": "0028_small_old_lace",
"breakpoints": true
+ },
+ {
+ "idx": 29,
+ "version": "6",
+ "when": 1771303716734,
+ "tag": "0029_fantastic_mach_iv",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/packages/agent-core/src/client.ts b/packages/agent-core/src/client.ts
index e315945..abfb93b 100644
--- a/packages/agent-core/src/client.ts
+++ b/packages/agent-core/src/client.ts
@@ -3,11 +3,41 @@ import type { ProviderConfig } from "./types"
export function createClient(provider: ProviderConfig): Anthropic {
switch (provider.type) {
- case "anthropic":
+ case "anthropic": {
+ // OAuth tokens use Bearer auth instead of x-api-key
+ if (provider.apiKey?.startsWith("sk-ant-oat")) {
+ const oauthToken = provider.apiKey
+ const wrappedFetch: typeof globalThis.fetch = (input, init) => {
+ const url =
+ typeof input === "string"
+ ? input
+ : input instanceof URL
+ ? input.toString()
+ : input.url
+ const betaUrl = url.includes("?")
+ ? `${url}&beta=true`
+ : `${url}?beta=true`
+ const existingHeaders =
+ (init?.headers as Record | undefined) ?? {}
+ // Remove x-api-key if SDK sets it, add Bearer
+ const { "x-api-key": _dropped, ...rest } = existingHeaders
+ return globalThis.fetch(betaUrl, {
+ ...init,
+ headers: {
+ ...rest,
+ authorization: `Bearer ${oauthToken}`,
+ "anthropic-beta":
+ "oauth-2025-04-20,interleaved-thinking-2025-05-14",
+ },
+ })
+ }
+ return new Anthropic({ apiKey: "unused", fetch: wrappedFetch })
+ }
return new Anthropic({
apiKey: provider.apiKey,
...(provider.baseUrl ? { baseURL: provider.baseUrl } : {}),
})
+ }
case "openrouter":
return new Anthropic({
apiKey: provider.apiKey ?? "",
diff --git a/packages/agent-core/src/index.ts b/packages/agent-core/src/index.ts
index 819a96e..696b7fa 100644
--- a/packages/agent-core/src/index.ts
+++ b/packages/agent-core/src/index.ts
@@ -19,3 +19,9 @@ export type {
DataSource,
SSEData,
} from "./types"
+export {
+ generatePKCE,
+ buildAuthUrl,
+ exchangeCode,
+ refreshAccessToken,
+} from "./oauth"
diff --git a/packages/agent-core/src/loop.ts b/packages/agent-core/src/loop.ts
index 9cfb173..d62a38a 100644
--- a/packages/agent-core/src/loop.ts
+++ b/packages/agent-core/src/loop.ts
@@ -16,6 +16,7 @@ interface AgentOptions {
readonly tools?: readonly ToolDef[]
readonly mcpClientManager?: McpClientManager
readonly maxTurns?: number
+ readonly isOAuth?: boolean
}
export async function* runAgent(
@@ -66,6 +67,11 @@ export async function* runAgent(
}
}
+ // OAuth endpoint requires mcp_ prefix on tool names
+ const effectiveTools: Tool[] = opts.isOAuth
+ ? apiTools.map((t) => ({ ...t, name: `mcp_${t.name}` }))
+ : apiTools
+
let turn = 0
while (turn < maxTurns) {
@@ -77,7 +83,7 @@ export async function* runAgent(
max_tokens: 8192,
system: opts.systemPrompt,
messages,
- tools: apiTools,
+ tools: effectiveTools,
})
// Stream text deltas and tool_use starts to the caller
@@ -85,9 +91,13 @@ export async function* runAgent(
if (event.type === "content_block_start") {
const block = event.content_block
if (block.type === "tool_use") {
+ const displayName =
+ opts.isOAuth && block.name.startsWith("mcp_")
+ ? block.name.slice(4)
+ : block.name
yield {
type: "tool_use",
- name: block.name,
+ name: displayName,
toolCallId: block.id,
input: {},
}
@@ -141,12 +151,18 @@ export async function* runAgent(
for (const block of message.content) {
if (block.type !== "tool_use") continue
- const runFn = toolMap.get(block.name)
+ // Strip mcp_ prefix from OAuth tool calls for local dispatch
+ const toolName =
+ opts.isOAuth && block.name.startsWith("mcp_")
+ ? block.name.slice(4)
+ : block.name
+
+ const runFn = toolMap.get(toolName)
// Route: direct tool -> MCP manager -> unknown
if (!runFn && !mcpManager) {
const errorResult = JSON.stringify({
- error: `Unknown tool: ${block.name}`,
+ error: `Unknown tool: ${toolName}`,
})
yield {
type: "tool_result",
@@ -168,12 +184,12 @@ export async function* runAgent(
result = await runFn(block.input)
} else if (mcpManager) {
result = await mcpManager.callTool(
- block.name,
+ toolName,
block.input
)
} else {
result = JSON.stringify({
- error: `Unknown tool: ${block.name}`,
+ error: `Unknown tool: ${toolName}`,
})
}
let parsed: unknown
diff --git a/packages/agent-core/src/oauth.ts b/packages/agent-core/src/oauth.ts
new file mode 100644
index 0000000..2d6a145
--- /dev/null
+++ b/packages/agent-core/src/oauth.ts
@@ -0,0 +1,118 @@
+// Anthropic OAuth constants (same as Claude Code / pi-ai)
+const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
+const AUTHORIZE_URL = "https://claude.ai/oauth/authorize"
+const TOKEN_URL = "https://console.anthropic.com/v1/oauth/token"
+const REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback"
+const SCOPES = "org:create_api_key user:profile user:inference"
+
+interface OAuthTokenResponse {
+ readonly access_token: string
+ readonly refresh_token: string
+ readonly expires_in: number
+ readonly token_type: string
+}
+
+function base64url(buffer: ArrayBuffer): string {
+ const bytes = new Uint8Array(buffer)
+ let binary = ""
+ for (const byte of bytes) {
+ binary += String.fromCharCode(byte)
+ }
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
+}
+
+export async function generatePKCE(): Promise<{
+ verifier: string
+ challenge: string
+}> {
+ // 32 random bytes -> 43 base64url chars, well within 43-128 range
+ const verifierBytes = crypto.getRandomValues(new Uint8Array(32))
+ const verifier = base64url(verifierBytes.buffer)
+
+ const encoder = new TextEncoder()
+ const data = encoder.encode(verifier)
+ const hash = await crypto.subtle.digest("SHA-256", data)
+ const challenge = base64url(hash)
+
+ return { verifier, challenge }
+}
+
+export function buildAuthUrl(challenge: string): string {
+ const params = new URLSearchParams({
+ response_type: "code",
+ client_id: CLIENT_ID,
+ redirect_uri: REDIRECT_URI,
+ scope: SCOPES,
+ code_challenge: challenge,
+ code_challenge_method: "S256",
+ })
+ return `${AUTHORIZE_URL}?${params.toString()}`
+}
+
+function parseTokenResponse(raw: unknown): {
+ access: string
+ refresh: string
+ expiresAt: number
+} {
+ if (
+ typeof raw !== "object" ||
+ raw === null ||
+ !("access_token" in raw) ||
+ !("refresh_token" in raw) ||
+ !("expires_in" in raw)
+ ) {
+ throw new Error("Unexpected token response shape")
+ }
+
+ const resp = raw as OAuthTokenResponse
+ return {
+ access: resp.access_token,
+ refresh: resp.refresh_token,
+ expiresAt: Date.now() + resp.expires_in * 1000,
+ }
+}
+
+async function postToken(body: Record): Promise<{
+ access: string
+ refresh: string
+ expiresAt: number
+}> {
+ const res = await fetch(TOKEN_URL, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ })
+
+ if (!res.ok) {
+ const text = await res.text()
+ throw new Error(`Token request failed (${res.status}): ${text}`)
+ }
+
+ const json: unknown = await res.json()
+ return parseTokenResponse(json)
+}
+
+export async function exchangeCode(
+ code: string,
+ state: string,
+ verifier: string,
+): Promise<{ access: string; refresh: string; expiresAt: number }> {
+ return postToken({
+ grant_type: "authorization_code",
+ client_id: CLIENT_ID,
+ code,
+ state,
+ redirect_uri: REDIRECT_URI,
+ code_verifier: verifier,
+ })
+}
+
+export async function refreshAccessToken(
+ refreshToken: string,
+): Promise<{ access: string; refresh: string; expiresAt: number }> {
+ return postToken({
+ grant_type: "refresh_token",
+ client_id: CLIENT_ID,
+ refresh_token: refreshToken,
+ })
+}
diff --git a/packages/agent-server/src/stream.ts b/packages/agent-server/src/stream.ts
index 56a1169..1dfe3b1 100644
--- a/packages/agent-server/src/stream.ts
+++ b/packages/agent-server/src/stream.ts
@@ -188,12 +188,16 @@ export async function createAgentStream(
: undefined,
})
+ const isOAuth =
+ provider.apiKey?.startsWith("sk-ant-oat") ?? false
+
const agentStream = runAgent({
provider,
model: context.model,
systemPrompt,
messages,
mcpClientManager: manager,
+ isOAuth,
})
// Wrap to disconnect MCP after stream ends
diff --git a/src/app/actions/anthropic-oauth.ts b/src/app/actions/anthropic-oauth.ts
new file mode 100644
index 0000000..5d02d41
--- /dev/null
+++ b/src/app/actions/anthropic-oauth.ts
@@ -0,0 +1,238 @@
+"use server"
+
+import { getCloudflareContext } from "@opennextjs/cloudflare"
+import { eq } from "drizzle-orm"
+import { revalidatePath } from "next/cache"
+import { getDb } from "@/db"
+import {
+ anthropicOauthTokens,
+ userProviderConfig,
+} from "@/db/schema-ai-config"
+import { getCurrentUser } from "@/lib/auth"
+import { encrypt, decrypt } from "@/lib/crypto"
+import { isDemoUser } from "@/lib/demo"
+import {
+ exchangeCode as exchangeOAuthCode_,
+ refreshAccessToken as refreshToken_,
+} from "agent-core"
+
+export async function exchangeOAuthCode(
+ code: string,
+ state: string,
+ verifier: string
+): Promise<{ success: true } | { success: false; error: string }> {
+ try {
+ const user = await getCurrentUser()
+ if (!user) {
+ return { success: false, error: "Unauthorized" }
+ }
+
+ if (isDemoUser(user.id)) {
+ return { success: false, error: "DEMO_READ_ONLY" }
+ }
+
+ const tokens = await exchangeOAuthCode_(code, state, verifier)
+
+ const { env } = await getCloudflareContext()
+ const encryptionKey = (
+ env as unknown as Record
+ ).PROVIDER_KEY_ENCRYPTION_KEY
+
+ if (!encryptionKey) {
+ return {
+ success: false,
+ error:
+ "Encryption key not configured (PROVIDER_KEY_ENCRYPTION_KEY)",
+ }
+ }
+
+ const encryptedAccess = await encrypt(
+ tokens.access,
+ encryptionKey,
+ user.id
+ )
+ const encryptedRefresh = await encrypt(
+ tokens.refresh,
+ encryptionKey,
+ user.id
+ )
+
+ const db = getDb(env.DB)
+ const now = new Date().toISOString()
+ const expiresAt = new Date(tokens.expiresAt).toISOString()
+
+ await db
+ .insert(anthropicOauthTokens)
+ .values({
+ userId: user.id,
+ accessToken: encryptedAccess,
+ refreshToken: encryptedRefresh,
+ expiresAt,
+ updatedAt: now,
+ })
+ .onConflictDoUpdate({
+ target: anthropicOauthTokens.userId,
+ set: {
+ accessToken: encryptedAccess,
+ refreshToken: encryptedRefresh,
+ expiresAt,
+ updatedAt: now,
+ },
+ })
+ .run()
+
+ await db
+ .insert(userProviderConfig)
+ .values({
+ userId: user.id,
+ providerType: "anthropic-oauth",
+ apiKey: null,
+ baseUrl: null,
+ modelOverrides: null,
+ isActive: 1,
+ updatedAt: now,
+ })
+ .onConflictDoUpdate({
+ target: userProviderConfig.userId,
+ set: {
+ providerType: "anthropic-oauth",
+ apiKey: null,
+ baseUrl: null,
+ isActive: 1,
+ updatedAt: now,
+ },
+ })
+ .run()
+
+ revalidatePath("/dashboard")
+ return { success: true }
+ } catch (err) {
+ return {
+ success: false,
+ error:
+ err instanceof Error
+ ? err.message
+ : "Failed to exchange OAuth code",
+ }
+ }
+}
+
+// Returns a valid access token for the given userId, refreshing if needed.
+// Returns null if no token exists or on any error.
+export async function getOAuthAccessToken(
+ userId: string
+): Promise {
+ try {
+ const { env } = await getCloudflareContext()
+ const db = getDb(env.DB)
+
+ const row = await db
+ .select()
+ .from(anthropicOauthTokens)
+ .where(eq(anthropicOauthTokens.userId, userId))
+ .get()
+
+ if (!row) return null
+
+ const encryptionKey = (
+ env as unknown as Record
+ ).PROVIDER_KEY_ENCRYPTION_KEY
+
+ if (!encryptionKey) return null
+
+ const isExpired =
+ Date.now() >
+ new Date(row.expiresAt).getTime() - 5 * 60 * 1000
+
+ if (!isExpired) {
+ return await decrypt(row.accessToken, encryptionKey, userId)
+ }
+
+ // Token expired — refresh
+ const refreshToken = await decrypt(
+ row.refreshToken,
+ encryptionKey,
+ userId
+ )
+ const fresh = await refreshToken_(refreshToken)
+
+ const encryptedAccess = await encrypt(
+ fresh.access,
+ encryptionKey,
+ userId
+ )
+ const encryptedRefresh = await encrypt(
+ fresh.refresh,
+ encryptionKey,
+ userId
+ )
+ const now = new Date().toISOString()
+ const expiresAt = new Date(fresh.expiresAt).toISOString()
+
+ await db
+ .update(anthropicOauthTokens)
+ .set({
+ accessToken: encryptedAccess,
+ refreshToken: encryptedRefresh,
+ expiresAt,
+ updatedAt: now,
+ })
+ .where(eq(anthropicOauthTokens.userId, userId))
+ .run()
+
+ return fresh.access
+ } catch {
+ return null
+ }
+}
+
+export async function disconnectOAuth(): Promise<{
+ success: true
+}> {
+ const user = await getCurrentUser()
+ if (!user) {
+ // Still return success shape — nothing to disconnect
+ return { success: true }
+ }
+
+ const { env } = await getCloudflareContext()
+ const db = getDb(env.DB)
+
+ await db
+ .delete(anthropicOauthTokens)
+ .where(eq(anthropicOauthTokens.userId, user.id))
+ .run()
+
+ await db
+ .delete(userProviderConfig)
+ .where(eq(userProviderConfig.userId, user.id))
+ .run()
+
+ revalidatePath("/dashboard")
+ return { success: true }
+}
+
+export async function getOAuthStatus(): Promise<{
+ connected: boolean
+ expiresAt?: string
+}> {
+ try {
+ const user = await getCurrentUser()
+ if (!user) return { connected: false }
+
+ const { env } = await getCloudflareContext()
+ const db = getDb(env.DB)
+
+ const row = await db
+ .select()
+ .from(anthropicOauthTokens)
+ .where(eq(anthropicOauthTokens.userId, user.id))
+ .get()
+
+ if (!row) return { connected: false }
+
+ return { connected: true, expiresAt: row.expiresAt }
+ } catch {
+ return { connected: false }
+ }
+}
diff --git a/src/app/actions/provider-config.ts b/src/app/actions/provider-config.ts
index bf3db17..bbf50bc 100644
--- a/src/app/actions/provider-config.ts
+++ b/src/app/actions/provider-config.ts
@@ -112,20 +112,21 @@ export async function getProviderConfigForJwt(
env as unknown as Record
).PROVIDER_KEY_ENCRYPTION_KEY
- if (!encryptionKey) {
- return null
- }
-
let decryptedApiKey: string | null = null
if (config.apiKey) {
- try {
- decryptedApiKey = await decrypt(
- config.apiKey,
- encryptionKey,
- userId
- )
- } catch {
+ if (!encryptionKey) {
+ // Can't decrypt, but still return the config without a key
decryptedApiKey = null
+ } else {
+ try {
+ decryptedApiKey = await decrypt(
+ config.apiKey,
+ encryptionKey,
+ userId
+ )
+ } catch {
+ decryptedApiKey = null
+ }
}
}
@@ -189,20 +190,19 @@ export async function setUserProviderConfig(
}
}
- const encryptionKey = (
- env as unknown as Record
- ).PROVIDER_KEY_ENCRYPTION_KEY
-
- if (!encryptionKey) {
- return {
- success: false,
- error:
- "Encryption key not configured (PROVIDER_KEY_ENCRYPTION_KEY)",
- }
- }
-
let encryptedApiKey: string | null = null
if (apiKey) {
+ const encryptionKey = (
+ env as unknown as Record
+ ).PROVIDER_KEY_ENCRYPTION_KEY
+
+ if (!encryptionKey) {
+ return {
+ success: false,
+ error:
+ "Encryption key not configured (PROVIDER_KEY_ENCRYPTION_KEY)",
+ }
+ }
encryptedApiKey = await encrypt(
apiKey,
encryptionKey,
@@ -376,20 +376,19 @@ export async function setOrgProviderConfig(
const { env } = await getCloudflareContext()
const db = getDb(env.DB)
- const encryptionKey = (
- env as unknown as Record
- ).PROVIDER_KEY_ENCRYPTION_KEY
-
- if (!encryptionKey) {
- return {
- success: false,
- error:
- "Encryption key not configured (PROVIDER_KEY_ENCRYPTION_KEY)",
- }
- }
-
let encryptedApiKey: string | null = null
if (apiKey) {
+ const encryptionKey = (
+ env as unknown as Record
+ ).PROVIDER_KEY_ENCRYPTION_KEY
+
+ if (!encryptionKey) {
+ return {
+ success: false,
+ error:
+ "Encryption key not configured (PROVIDER_KEY_ENCRYPTION_KEY)",
+ }
+ }
encryptedApiKey = await encrypt(
apiKey,
encryptionKey,
diff --git a/src/app/api/agent/route.ts b/src/app/api/agent/route.ts
index e41ef73..2a28ab4 100644
--- a/src/app/api/agent/route.ts
+++ b/src/app/api/agent/route.ts
@@ -6,6 +6,7 @@
import { getCurrentUser } from "@/lib/auth"
import { getProviderConfigForJwt } from "@/app/actions/provider-config"
+import { getOAuthAccessToken } from "@/app/actions/anthropic-oauth"
import { generateAgentToken } from "@/lib/agent/api-auth"
import { getCloudflareContext } from "@opennextjs/cloudflare"
import { getDb } from "@/db"
@@ -107,7 +108,7 @@ export async function POST(
)
}
- const provider: ProviderConfig = providerConfig
+ let provider: ProviderConfig = providerConfig
? {
type: mapProviderType(providerConfig.type),
apiKey: providerConfig.apiKey ?? undefined,
@@ -117,6 +118,26 @@ export async function POST(
}
: { type: "anthropic" }
+ // Resolve OAuth access token if needed
+ if (providerConfig?.type === "anthropic-oauth") {
+ const accessToken = await getOAuthAccessToken(user.id)
+ if (!accessToken) {
+ return new Response(
+ JSON.stringify({
+ error: "Anthropic OAuth not connected or token expired",
+ }),
+ {
+ status: 401,
+ headers: { "Content-Type": "application/json" },
+ }
+ )
+ }
+ provider = {
+ type: "anthropic",
+ apiKey: accessToken,
+ }
+ }
+
// Generate JWT for bridge route auth
const { env } = await getCloudflareContext()
const envRecord = env as unknown as Record<
@@ -261,12 +282,16 @@ export async function POST(
: undefined,
})
+ const isOAuth =
+ provider.apiKey?.startsWith("sk-ant-oat") ?? false
+
const stream = runAgent({
provider,
model,
systemPrompt,
messages: msgs,
mcpClientManager: manager,
+ isOAuth,
})
// Wrap stream to disconnect MCP after completion
diff --git a/src/components/settings/ai-model-tab.tsx b/src/components/settings/ai-model-tab.tsx
index a6c0211..25cfef3 100755
--- a/src/components/settings/ai-model-tab.tsx
+++ b/src/components/settings/ai-model-tab.tsx
@@ -3,6 +3,7 @@
import * as React from "react"
import {
Check,
+ ExternalLink,
Loader2,
Search,
Eye,
@@ -47,6 +48,15 @@ import {
setUserProviderConfig,
clearUserProviderConfig,
} from "@/app/actions/provider-config"
+import {
+ exchangeOAuthCode,
+ disconnectOAuth,
+ getOAuthStatus,
+} from "@/app/actions/anthropic-oauth"
+import {
+ generatePKCE,
+ buildAuthUrl,
+} from "@/lib/anthropic-oauth-client"
import { Slider } from "@/components/ui/slider"
import { Switch } from "@/components/ui/switch"
import { cn } from "@/lib/utils"
@@ -224,6 +234,11 @@ function outputCostPerMillion(
// Provider Configuration Section
// ============================================================================
+type OAuthState =
+ | { step: "idle" }
+ | { step: "connecting"; verifier: string }
+ | { step: "connected"; expiresAt?: string }
+
function ProviderConfigSection({
onProviderChanged,
}: {
@@ -239,16 +254,25 @@ function ProviderConfigSection({
const [hasStoredKey, setHasStoredKey] =
React.useState(false)
- // load current config from D1
+ // OAuth state
+ const [oauth, setOAuth] = React.useState({
+ step: "idle",
+ })
+ const [oauthCode, setOAuthCode] = React.useState("")
+
+ // load current config + OAuth status from D1
React.useEffect(() => {
- getUserProviderConfig()
- .then((result) => {
+ Promise.all([
+ getUserProviderConfig(),
+ getOAuthStatus(),
+ ])
+ .then(([configResult, oauthStatus]) => {
if (
- "success" in result &&
- result.success &&
- result.data
+ "success" in configResult &&
+ configResult.success &&
+ configResult.data
) {
- const d = result.data
+ const d = configResult.data
const type = (
PROVIDER_TYPES.includes(
d.providerType as ProviderType
@@ -260,6 +284,12 @@ function ProviderConfigSection({
setBaseUrl(d.baseUrl ?? "")
setHasStoredKey(d.hasApiKey)
}
+ if (oauthStatus.connected) {
+ setOAuth({
+ step: "connected",
+ expiresAt: oauthStatus.expiresAt,
+ })
+ }
})
.catch(() => {})
.finally(() => setLoading(false))
@@ -275,6 +305,10 @@ function ProviderConfigSection({
setActiveType(type)
setApiKey("")
setShowKey(false)
+ setOAuthCode("")
+ if (type !== "anthropic-oauth") {
+ setOAuth({ step: "idle" })
+ }
const newInfo = PROVIDERS.find(
(p) => p.type === type
)
@@ -282,6 +316,54 @@ function ProviderConfigSection({
setHasStoredKey(false)
}
+ const handleOAuthConnect = async (): Promise => {
+ const { verifier, challenge } = await generatePKCE()
+ const url = buildAuthUrl(challenge)
+ setOAuth({ step: "connecting", verifier })
+ window.open(url, "_blank", "noopener")
+ }
+
+ const handleOAuthSubmit = async (): Promise => {
+ if (oauth.step !== "connecting") return
+ const trimmed = oauthCode.trim()
+ if (!trimmed) return
+
+ // Parse "code#state" or just "code"
+ const hashIdx = trimmed.indexOf("#")
+ const code =
+ hashIdx >= 0 ? trimmed.slice(0, hashIdx) : trimmed
+ const state =
+ hashIdx >= 0 ? trimmed.slice(hashIdx + 1) : ""
+
+ setSaving(true)
+ const result = await exchangeOAuthCode(
+ code,
+ state,
+ oauth.verifier
+ )
+ setSaving(false)
+
+ if (result.success) {
+ toast.success("Connected to Anthropic")
+ setOAuth({ step: "connected" })
+ setOAuthCode("")
+ setProviderType("anthropic-oauth")
+ onProviderChanged()
+ } else {
+ toast.error(result.error ?? "OAuth failed")
+ }
+ }
+
+ const handleOAuthDisconnect =
+ async (): Promise => {
+ setSaving(true)
+ await disconnectOAuth()
+ setSaving(false)
+ setOAuth({ step: "idle" })
+ toast.success("Disconnected")
+ onProviderChanged()
+ }
+
const handleSave = async (): Promise => {
setSaving(true)
const result = await setUserProviderConfig(
@@ -375,91 +457,184 @@ function ProviderConfigSection({
{info.description}
- {/* credential inputs */}
- {(info.needsApiKey || info.needsBaseUrl) && (
+ {/* OAuth flow for anthropic-oauth */}
+ {activeType === "anthropic-oauth" && (
- {info.needsApiKey && (
-
-
-
-
- setApiKey(e.target.value)
- }
- placeholder={
- hasStoredKey
- ? "Key saved (enter new to replace)"
- : activeType === "openrouter"
- ? "sk-or-..."
- : activeType === "anthropic-key"
- ? "sk-ant-..."
- : "API key"
- }
- className="h-8 pr-10 text-xs"
- />
-
-
-
+ {oauth.step === "connected" && (
+
+
+
+
+ Connected
+
+ {oauth.expiresAt && (
+
+ expires{" "}
+ {new Date(
+ oauth.expiresAt
+ ).toLocaleDateString()}
+
+ )}
+
)}
- {info.needsBaseUrl && (
-
-
-
- setBaseUrl(e.target.value)
- }
- placeholder={
- info.defaultBaseUrl ?? "https://..."
- }
- className="h-8 text-xs"
- />
+ {oauth.step === "idle" && (
+
+ )}
+
+ {oauth.step === "connecting" && (
+
+
+ Authorize in the browser tab that opened,
+ then paste the code below.
+
+
+
+ setOAuthCode(e.target.value)
+ }
+ placeholder="Paste authorization code here"
+ className="h-8 text-xs font-mono"
+ autoFocus
+ />
+
+
+
)}
)}
- {/* actions */}
-
-
- {activeType !== "anthropic-oauth" && (
+ {/* credential inputs (non-OAuth providers) */}
+ {activeType !== "anthropic-oauth" &&
+ (info.needsApiKey || info.needsBaseUrl) && (
+
+ {info.needsApiKey && (
+
+
+
+
+ setApiKey(e.target.value)
+ }
+ placeholder={
+ hasStoredKey
+ ? "Key saved (enter new to replace)"
+ : activeType === "openrouter"
+ ? "sk-or-..."
+ : activeType === "anthropic-key"
+ ? "sk-ant-..."
+ : "API key"
+ }
+ className="h-8 pr-10 text-xs"
+ />
+
+
+
+
+
+ )}
+
+ {info.needsBaseUrl && (
+
+
+
+ setBaseUrl(e.target.value)
+ }
+ placeholder={
+ info.defaultBaseUrl ?? "https://..."
+ }
+ className="h-8 text-xs"
+ />
+
+ )}
+
+ )}
+
+ {/* actions (non-OAuth providers) */}
+ {activeType !== "anthropic-oauth" && (
+
+
Reset to default
- )}
-
+
+ )}
)
}
diff --git a/src/db/schema-ai-config.ts b/src/db/schema-ai-config.ts
index aaec925..2c7d5fe 100755
--- a/src/db/schema-ai-config.ts
+++ b/src/db/schema-ai-config.ts
@@ -79,6 +79,21 @@ export const agentUsage = sqliteTable("agent_usage", {
createdAt: text("created_at").notNull(),
})
+// per-user Anthropic OAuth tokens (separate from provider config
+// because OAuth needs refresh token + expiry tracking)
+export const anthropicOauthTokens = sqliteTable(
+ "anthropic_oauth_tokens",
+ {
+ userId: text("user_id")
+ .primaryKey()
+ .references(() => users.id, { onDelete: "cascade" }),
+ accessToken: text("access_token").notNull(),
+ refreshToken: text("refresh_token").notNull(),
+ expiresAt: text("expires_at").notNull(),
+ updatedAt: text("updated_at").notNull(),
+ }
+)
+
export type AgentConfig = typeof agentConfig.$inferSelect
export type NewAgentConfig = typeof agentConfig.$inferInsert
export type UserProviderConfig = typeof userProviderConfig.$inferSelect
@@ -89,3 +104,7 @@ export type UserModelPreference =
typeof userModelPreference.$inferSelect
export type NewUserModelPreference =
typeof userModelPreference.$inferInsert
+export type AnthropicOauthToken =
+ typeof anthropicOauthTokens.$inferSelect
+export type NewAnthropicOauthToken =
+ typeof anthropicOauthTokens.$inferInsert
diff --git a/src/lib/anthropic-oauth-client.ts b/src/lib/anthropic-oauth-client.ts
new file mode 100644
index 0000000..610d36b
--- /dev/null
+++ b/src/lib/anthropic-oauth-client.ts
@@ -0,0 +1,52 @@
+// Browser-safe PKCE + auth URL generation for Anthropic OAuth.
+// Duplicated from agent-core/oauth.ts to avoid pulling in
+// server-only MCP deps via the barrel export.
+
+const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
+const AUTHORIZE_URL = "https://claude.ai/oauth/authorize"
+const REDIRECT_URI =
+ "https://console.anthropic.com/oauth/code/callback"
+const SCOPES = "org:create_api_key user:profile user:inference"
+
+function base64url(buffer: ArrayBuffer): string {
+ const bytes = new Uint8Array(buffer)
+ let binary = ""
+ for (const byte of bytes) {
+ binary += String.fromCharCode(byte)
+ }
+ return btoa(binary)
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_")
+ .replace(/=/g, "")
+}
+
+export async function generatePKCE(): Promise<{
+ verifier: string
+ challenge: string
+}> {
+ const verifierBytes = crypto.getRandomValues(
+ new Uint8Array(32)
+ )
+ const verifier = base64url(verifierBytes.buffer)
+
+ const encoder = new TextEncoder()
+ const hash = await crypto.subtle.digest(
+ "SHA-256",
+ encoder.encode(verifier)
+ )
+ const challenge = base64url(hash)
+
+ return { verifier, challenge }
+}
+
+export function buildAuthUrl(challenge: string): string {
+ const params = new URLSearchParams({
+ response_type: "code",
+ client_id: CLIENT_ID,
+ redirect_uri: REDIRECT_URI,
+ scope: SCOPES,
+ code_challenge: challenge,
+ code_challenge_method: "S256",
+ })
+ return `${AUTHORIZE_URL}?${params.toString()}`
+}