Fix: core cli

This commit is contained in:
Harshith Mullapudi 2025-07-23 12:17:14 +05:30
parent a60dc20bf2
commit 2e08470a03
18 changed files with 5002 additions and 349 deletions

View File

@ -135,7 +135,11 @@ function App() {
// `themeAction` is the action name that's used to change the theme in the session storage. // `themeAction` is the action name that's used to change the theme in the session storage.
export default function AppWithProviders() { export default function AppWithProviders() {
return ( return (
<ThemeProvider specifiedTheme={Theme.LIGHT} themeAction="/action/set-theme"> <ThemeProvider
specifiedTheme={Theme.LIGHT}
disableTransitionOnThemeChange={true}
themeAction="/action/set-theme"
>
<App /> <App />
</ThemeProvider> </ThemeProvider>
); );

View File

@ -3,7 +3,7 @@ import { env } from "~/env.server";
export const impersonationSessionStorage = createCookieSessionStorage({ export const impersonationSessionStorage = createCookieSessionStorage({
cookie: { cookie: {
name: "__impersonate", // use any name you want here name: "__impersonate_core", // use any name you want here
sameSite: "lax", // this helps with CSRF sameSite: "lax", // this helps with CSRF
path: "/", // remember to add this so the cookie will work in all routes path: "/", // remember to add this so the cookie will work in all routes
httpOnly: true, // for security reasons, make this cookie http only httpOnly: true, // for security reasons, make this cookie http only

View File

@ -9,7 +9,7 @@ export const sessionStorage = createCookieSessionStorage<{
[SESSION_KEY]: AuthUser; [SESSION_KEY]: AuthUser;
}>({ }>({
cookie: { cookie: {
name: "__session__core", // use any name you want here name: "__session", // use any name you want here
sameSite: "lax", // this helps with CSRF sameSite: "lax", // this helps with CSRF
path: "/", // remember to add this so the cookie will work in all routes path: "/", // remember to add this so the cookie will work in all routes
httpOnly: true, // for security reasons, make this cookie http only httpOnly: true, // for security reasons, make this cookie http only

View File

@ -7,12 +7,14 @@ import { type HistoryStep } from "../utils/types";
import { import {
createConversationHistoryForAgent, createConversationHistoryForAgent,
deletePersonalAccessToken, deletePersonalAccessToken,
getCreditsForUser,
getPreviousExecutionHistory, getPreviousExecutionHistory,
init, init,
type RunChatPayload, type RunChatPayload,
updateConversationHistoryMessage, updateConversationHistoryMessage,
updateConversationStatus, updateConversationStatus,
updateExecutionStep, updateExecutionStep,
updateUserCredits,
} from "../utils/utils"; } from "../utils/utils";
const chatQueue = queue({ const chatQueue = queue({
@ -30,6 +32,8 @@ export const chat = task({
queue: chatQueue, queue: chatQueue,
init, init,
run: async (payload: RunChatPayload, { init }) => { run: async (payload: RunChatPayload, { init }) => {
const usageCredits = await getCreditsForUser(init?.userId as string);
await updateConversationStatus("running", payload.conversationId); await updateConversationStatus("running", payload.conversationId);
try { try {
@ -119,13 +123,7 @@ export const chat = task({
payload.conversationId, payload.conversationId,
); );
// await addToMemory( usageCredits && (await updateUserCredits(usageCredits, creditForChat));
// init.conversation.id,
// message,
// agentUserMessage,
// init.preferences,
// init.userName,
// );
if (init?.tokenId) { if (init?.tokenId) {
await deletePersonalAccessToken(init.tokenId); await deletePersonalAccessToken(init.tokenId);

View File

@ -6,6 +6,7 @@ import {
type Prisma, type Prisma,
PrismaClient, PrismaClient,
UserType, UserType,
type UserUsage,
type Workspace, type Workspace,
} from "@prisma/client"; } from "@prisma/client";
@ -56,7 +57,11 @@ function encryptToken(value: string) {
} }
const nonce = nodeCrypto.randomBytes(12); const nonce = nodeCrypto.randomBytes(12);
const cipher = nodeCrypto.createCipheriv("aes-256-gcm", encryptionKey, nonce); const cipher = nodeCrypto.createCipheriv(
"aes-256-gcm",
encryptionKey,
nonce as any,
);
let encrypted = cipher.update(value, "utf8", "hex"); let encrypted = cipher.update(value, "utf8", "hex");
encrypted += cipher.final("hex"); encrypted += cipher.final("hex");
@ -557,3 +562,28 @@ export async function webSearch(args: WebSearchArgs): Promise<WebSearchResult> {
); );
} }
} }
export const getCreditsForUser = async (
userId: string,
): Promise<UserUsage | null> => {
return await prisma.userUsage.findUnique({
where: {
userId,
},
});
};
export const updateUserCredits = async (
userUsage: UserUsage,
usedCredits: number,
) => {
return await prisma.userUsage.update({
where: {
id: userUsage.id,
},
data: {
availableCredits: userUsage.availableCredits - usedCredits,
usedCredits: userUsage.usedCredits + usedCredits,
},
});
};

View File

@ -115,7 +115,7 @@
"react-virtualized": "^9.22.6", "react-virtualized": "^9.22.6",
"remix-auth": "^4.2.0", "remix-auth": "^4.2.0",
"remix-auth-oauth2": "^3.4.1", "remix-auth-oauth2": "^3.4.1",
"remix-themes": "^1.3.1", "remix-themes": "^2.0.4",
"remix-typedjson": "0.3.1", "remix-typedjson": "0.3.1",
"remix-utils": "^7.7.0", "remix-utils": "^7.7.0",
"sdk": "link:@modelcontextprotocol/sdk", "sdk": "link:@modelcontextprotocol/sdk",

View File

@ -46,6 +46,110 @@ model AuthorizationCode {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
model OAuthAuthorizationCode {
id String @id @default(cuid())
code String @unique
// OAuth2 specific fields
clientId String
userId String
redirectUri String
scope String?
state String?
codeChallenge String?
codeChallengeMethod String?
expiresAt DateTime
used Boolean @default(false)
// Relations
client OAuthClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model OAuthClient {
id String @id @default(cuid())
clientId String @unique
clientSecret String
name String
description String?
// Redirect URIs (comma-separated for simplicity)
redirectUris String
// Allowed scopes (comma-separated)
allowedScopes String @default("read")
// Grant types allowed
grantTypes String @default("authorization_code")
// PKCE support
requirePkce Boolean @default(false)
// Client metadata
logoUrl String?
homepageUrl String?
// GitHub-style features
isActive Boolean @default(true)
// Workspace relationship (like GitHub orgs)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
// Created by user (for audit trail)
createdBy User @relation(fields: [createdById], references: [id])
createdById String
// Relations
oauthAuthorizationCodes OAuthAuthorizationCode[]
accessTokens OAuthAccessToken[]
refreshTokens OAuthRefreshToken[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model OAuthAccessToken {
id String @id @default(cuid())
token String @unique
clientId String
userId String
scope String?
expiresAt DateTime
revoked Boolean @default(false)
// Relations
client OAuthClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model OAuthRefreshToken {
id String @id @default(cuid())
token String @unique
clientId String
userId String
scope String?
expiresAt DateTime
revoked Boolean @default(false)
// Relations
client OAuthClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Conversation { model Conversation {
id String @id @default(uuid()) id String @id @default(uuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@ -319,6 +423,26 @@ model User {
Conversation Conversation[] Conversation Conversation[]
ConversationHistory ConversationHistory[] ConversationHistory ConversationHistory[]
IngestionRule IngestionRule[] IngestionRule IngestionRule[]
// OAuth2 relations
oauthAuthorizationCodes OAuthAuthorizationCode[]
oauthAccessTokens OAuthAccessToken[]
oauthRefreshTokens OAuthRefreshToken[]
oauthClientsCreated OAuthClient[]
UserUsage UserUsage?
}
model UserUsage {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deleted DateTime?
availableCredits Int @default(0)
usedCredits Int @default(0)
user User @relation(fields: [userId], references: [id])
userId String @unique
} }
model WebhookConfiguration { model WebhookConfiguration {
@ -375,6 +499,7 @@ model Workspace {
WebhookConfiguration WebhookConfiguration[] WebhookConfiguration WebhookConfiguration[]
Conversation Conversation[] Conversation Conversation[]
IngestionRule IngestionRule[] IngestionRule IngestionRule[]
OAuthClient OAuthClient[]
} }
enum AuthenticationMethod { enum AuthenticationMethod {

View File

@ -8,7 +8,7 @@
], ],
"scripts": { "scripts": {
"build": "dotenv -- turbo run build", "build": "dotenv -- turbo run build",
"dev": "dotenv -- turbo run dev", "dev": "dotenv -- turbo run dev --filter=!core-extension --filter=!@redplanethq/core",
"lint": "dotenv -- turbo run lint", "lint": "dotenv -- turbo run lint",
"format": "dotenv -- prettier --write \"**/*.{ts,tsx,md}\"", "format": "dotenv -- prettier --write \"**/*.{ts,tsx,md}\"",
"check-types": "dotenv -- turbo run check-types", "check-types": "dotenv -- turbo run check-types",

View File

@ -1,6 +1,6 @@
{ {
"name": "@redplanethq/core", "name": "@redplanethq/core",
"version": "0.1.4", "version": "0.1.6",
"description": "A Command-Line Interface for Core", "description": "A Command-Line Interface for Core",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
@ -105,6 +105,7 @@
"minimatch": "^10.0.1", "minimatch": "^10.0.1",
"mlly": "^1.7.1", "mlly": "^1.7.1",
"nypm": "^0.5.4", "nypm": "^0.5.4",
"nanoid": "3.3.8",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
"open": "^10.0.3", "open": "^10.0.3",
"knex": "3.1.0", "knex": "3.1.0",

View File

@ -9,7 +9,7 @@ import { handleDockerLogin } from "../utils/docker-login.js";
import { deployTriggerTasks } from "../utils/trigger-deploy.js"; import { deployTriggerTasks } from "../utils/trigger-deploy.js";
import path from "path"; import path from "path";
import * as fs from "fs"; import * as fs from "fs";
import { initTriggerDatabase } from "../utils/database-init.js"; import { createTriggerConfigJson, initTriggerDatabase } from "../utils/database-init.js";
export async function initCommand() { export async function initCommand() {
// Display the CORE brain logo // Display the CORE brain logo
@ -66,6 +66,7 @@ export async function initCommand() {
} }
} catch (error: any) { } catch (error: any) {
s1.stop(error.message); s1.stop(error.message);
outro("❌ Setup failed: " + error.message);
process.exit(1); process.exit(1);
} }
@ -77,7 +78,8 @@ export async function initCommand() {
showOutput: true, showOutput: true,
}); });
} catch (error: any) { } catch (error: any) {
throw error; outro("❌ Setup failed: " + error.message);
process.exit(1);
} }
// Step 4: Check if postgres is running // Step 4: Check if postgres is running
@ -98,7 +100,7 @@ export async function initCommand() {
if (retries >= maxRetries) { if (retries >= maxRetries) {
s3.stop("L PostgreSQL not accessible on localhost:5432"); s3.stop("L PostgreSQL not accessible on localhost:5432");
outro("Please check your Docker setup and try again"); outro("Please check your Docker setup and try again");
process.exit(1); process.exit(1);
} }
@ -118,6 +120,7 @@ export async function initCommand() {
} }
} catch (error: any) { } catch (error: any) {
s4.stop(error.message); s4.stop(error.message);
outro("❌ Setup failed: " + error.message);
process.exit(1); process.exit(1);
} }
@ -129,7 +132,8 @@ export async function initCommand() {
showOutput: true, showOutput: true,
}); });
} catch (error: any) { } catch (error: any) {
throw error; outro("❌ Setup failed: " + error.message);
process.exit(1);
} }
// Step 7: Check if Trigger.dev configuration already exists // Step 7: Check if Trigger.dev configuration already exists
@ -142,10 +146,12 @@ export async function initCommand() {
); );
} else { } else {
// Step 8: Show login instructions // Step 8: Show login instructions
outro("🎉 Docker containers are now running!"); note("🎉 Docker containers are now running!");
const { prodSecretKey, projectRefId } = await initTriggerDatabase(triggerDir);
const { prodSecretKey, projectRefId, personalToken } = await initTriggerDatabase(triggerDir);
await createTriggerConfigJson(personalToken as string);
console.log(prodSecretKey, projectRefId);
const openaiApiKey = await text({ const openaiApiKey = await text({
message: "Enter your OpenAI API Key:", message: "Enter your OpenAI API Key:",
validate: (value) => { validate: (value) => {
@ -167,24 +173,20 @@ export async function initCommand() {
s6.stop("✅ Updated .env with Trigger.dev configuration"); s6.stop("✅ Updated .env with Trigger.dev configuration");
} catch (error: any) { } catch (error: any) {
s6.stop("❌ Failed to update .env file"); s6.stop("❌ Failed to update .env file");
throw error; outro("❌ Setup failed: " + error.message);
process.exit(1);
} }
// Step 12: Restart root docker-compose with new configuration // Step 12: Restart root docker-compose with new configuration
try { try {
await executeCommandInteractive("docker compose down", {
cwd: rootDir,
message: "Stopping Core services...",
showOutput: true,
});
await executeCommandInteractive("docker compose up -d", { await executeCommandInteractive("docker compose up -d", {
cwd: rootDir, cwd: rootDir,
message: "Starting Core services with new Trigger.dev configuration...", message: "Starting Core services with new Trigger.dev configuration...",
showOutput: true, showOutput: true,
}); });
} catch (error: any) { } catch (error: any) {
throw error; outro("❌ Setup failed: " + error.message);
process.exit(1);
} }
} }
@ -196,7 +198,6 @@ export async function initCommand() {
await deployTriggerTasks(rootDir); await deployTriggerTasks(rootDir);
// Step 15: Final instructions // Step 15: Final instructions
outro("🎉 Setup Complete!");
note( note(
[ [
"Your services are now running:", "Your services are now running:",
@ -212,6 +213,8 @@ export async function initCommand() {
].join("\n"), ].join("\n"),
"🚀 Services Running" "🚀 Services Running"
); );
outro("🎉 Setup Complete!");
process.exit(0);
} catch (error: any) { } catch (error: any) {
outro(`❌ Setup failed: ${error.message}`); outro(`❌ Setup failed: ${error.message}`);
process.exit(1); process.exit(1);

View File

@ -6,21 +6,19 @@ import dotenv from "dotenv";
import dotenvExpand from "dotenv-expand"; import dotenvExpand from "dotenv-expand";
import path from "node:path"; import path from "node:path";
import { log } from "@clack/prompts"; import { log } from "@clack/prompts";
import { customAlphabet } from "nanoid";
// Generate a new token similar to the original: "tr_pat_" + 40 lowercase alphanumeric chars import $xdgAppPaths from "xdg-app-paths";
function generatePersonalToken(count: number) { import { mkdirSync, writeFileSync } from "node:fs";
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
let token = "tr_pat_"; export const xdgAppPaths = $xdgAppPaths as unknown as typeof $xdgAppPaths.default;
for (let i = 0; i < count; i++) {
token += chars.charAt(Math.floor(Math.random() * chars.length)); const tokenGenerator = customAlphabet("123456789abcdefghijkmnopqrstuvwxyz", 40);
}
return token;
}
// Generate tokens internally // Generate tokens internally
const TRIGGER_TOKEN = nodeCrypto.randomBytes(32).toString("hex"); let ENCRYPTION_KEY: string;
const COMMON_ID = "9ea0412ea8ef441ca03c7952d011ab56"; const COMMON_ID = "9ea0412ea8ef441ca03c7952d011ab56";
const key = generatePersonalToken(20); const key = tokenGenerator(20);
export async function createOrg(knex: KnexT) { export async function createOrg(knex: KnexT) {
try { try {
@ -90,17 +88,26 @@ export async function createPersonalToken(knex: KnexT) {
log.step("Creating CLI personal access token..."); log.step("Creating CLI personal access token...");
// Generate a new token similar to the original: "tr_pat_" + 40 lowercase alphanumeric chars // Generate a new token similar to the original: "tr_pat_" + 40 lowercase alphanumeric chars
const personalToken = generatePersonalToken(40); const personalToken = `tr_pat_${tokenGenerator(40)}`;
await knex("PersonalAccessToken").insert({ await knex("PersonalAccessToken").insert({
id, id,
name: "cli", name: "cli",
userId: COMMON_ID, userId: COMMON_ID,
updatedAt: new Date(), updatedAt: new Date(),
obfuscatedToken: personalToken, obfuscatedToken: obfuscateToken(personalToken),
hashedToken: hashToken(personalToken), hashedToken: hashToken(personalToken),
encryptedToken: {}, encryptedToken: encryptToken(personalToken),
}); });
log.success("CLI personal access token created."); log.success("CLI personal access token created.");
return personalToken;
}
function obfuscateToken(token: string) {
const withoutPrefix = token.replace("tr_pat_", "");
const obfuscated = `${withoutPrefix.slice(0, 4)}${"•".repeat(18)}${withoutPrefix.slice(-4)}`;
return `tr_pat_${obfuscated}`;
} }
export async function createProject(knex: KnexT) { export async function createProject(knex: KnexT) {
@ -179,9 +186,9 @@ export async function createProject(knex: KnexT) {
} }
} }
export function encryptToken(value: string) { function encryptToken(value: string) {
const nonce = nodeCrypto.randomBytes(12); const nonce = nodeCrypto.randomBytes(12);
const cipher = nodeCrypto.createCipheriv("aes-256-gcm", TRIGGER_TOKEN, nonce); const cipher = nodeCrypto.createCipheriv("aes-256-gcm", ENCRYPTION_KEY, nonce);
let encrypted = cipher.update(value, "utf8", "hex"); let encrypted = cipher.update(value, "utf8", "hex");
encrypted += cipher.final("hex"); encrypted += cipher.final("hex");
@ -203,11 +210,46 @@ export function hashToken(token: string): string {
// Main initialization function // Main initialization function
export async function initTriggerDatabase(triggerDir: string) { export async function initTriggerDatabase(triggerDir: string) {
log.step("Waiting for Trigger.dev to be ready on http://localhost:8030/login...");
await new Promise((resolve) => setTimeout(resolve, 5000));
// Check if Trigger.dev is up and /login returns 200 before proceeding
const MAX_RETRIES = 30;
const RETRY_DELAY_MS = 2000;
let loginOk = false;
for (let i = 0; i < MAX_RETRIES; i++) {
try {
const res = await fetch("http://localhost:8030/login");
if (res.status === 200) {
loginOk = true;
log.step("Trigger.dev is up and /login returned 200.");
break;
}
} catch (e) {
// ignore, will retry
}
if (i < MAX_RETRIES - 1) {
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
}
}
if (!loginOk) {
log.error("Trigger.dev did not respond with 200 on /login after waiting.");
throw new Error("Trigger.dev is not ready at http://localhost:8030/login");
}
const envPath = path.join(triggerDir, ".env"); const envPath = path.join(triggerDir, ".env");
log.step(`Loading environment variables from ${envPath}...`); log.step(`Loading environment variables from ${envPath}...`);
const envVarsExpand = const envVarsExpand =
dotenvExpand.expand(dotenv.config({ path: envPath, processEnv: {} })).parsed || {}; dotenvExpand.expand(dotenv.config({ path: envPath, processEnv: {} })).parsed || {};
// Set the encryption key from the .env file
ENCRYPTION_KEY = envVarsExpand.ENCRYPTION_KEY as string;
if (!ENCRYPTION_KEY) {
throw new Error("ENCRYPTION_KEY not found in trigger/.env file");
}
const knex = Knex({ const knex = Knex({
client: "pg", // Use PostgreSQL as the database client client: "pg", // Use PostgreSQL as the database client
connection: envVarsExpand.DIRECT_URL?.replace("host.docker.internal", "localhost"), // Database connection URL from environment variable connection: envVarsExpand.DIRECT_URL?.replace("host.docker.internal", "localhost"), // Database connection URL from environment variable
@ -220,19 +262,65 @@ export async function initTriggerDatabase(triggerDir: string) {
await createOrg(knex); await createOrg(knex);
// Create personal access token // Create personal access token
await createPersonalToken(knex); const personalToken = await createPersonalToken(knex);
// Create project and return details // Create project and return details
const projectDetails = await createProject(knex); const projectDetails = await createProject(knex);
log.success("Trigger.dev database initialized successfully."); log.success("Trigger.dev database initialized successfully.");
log.step("Setting things up...");
await new Promise((resolve) => setTimeout(resolve, 5000));
return { return {
prodSecretKey: projectDetails.prodSecret, prodSecretKey: projectDetails.prodSecret,
projectRefId: projectDetails.projectRef, projectRefId: projectDetails.projectRef,
personalToken,
}; };
} catch (error) { } catch (error) {
log.error(`Initialization failed: ${error}`); log.error(`Initialization failed: ${error}`);
throw new Error(`Initialization failed: ${error}`); throw new Error(`Initialization failed: ${error}`);
} }
} }
function getGlobalConfigFolderPath() {
const configDir = xdgAppPaths("trigger").config();
return configDir;
}
const CONFIG_FILE = "config.json";
function getAuthConfigFilePath() {
return path.join(getGlobalConfigFolderPath(), CONFIG_FILE);
}
/**
* Creates the Trigger.dev CLI config.json file in ~/Library/Preferences/trigger/config.json
* with the given personal access token. If the config already exists, it will be deleted first.
*
* @param {string} personalToken - The personal access token to store in the config.
*/
export async function createTriggerConfigJson(personalToken: string) {
const configPath = getAuthConfigFilePath();
// If config.json exists, delete it
mkdirSync(path.dirname(configPath), {
recursive: true,
});
const config = {
version: 2,
currentProfile: "default",
profiles: {
default: {
accessToken: personalToken,
apiUrl: "http://localhost:8030",
},
},
};
writeFileSync(path.join(configPath), JSON.stringify(config, undefined, 2), {
encoding: "utf-8",
});
}

View File

@ -27,7 +27,7 @@ export function executeCommandInteractive(command: string, options: CommandOptio
cwd: options.cwd, cwd: options.cwd,
stdio: options.showOutput ? ["ignore", "pipe", "pipe"] : "ignore", stdio: options.showOutput ? ["ignore", "pipe", "pipe"] : "ignore",
detached: false, detached: false,
env: options.env ? { ...process.env, ...options.env } : { ...process.env }, env: options.env ? { ...process.env, ...options.env } : {},
}); });
let output = ""; let output = "";

View File

@ -2,6 +2,7 @@ import { note, log } from "@clack/prompts";
import { executeCommandInteractive } from "./docker-interactive.js"; import { executeCommandInteractive } from "./docker-interactive.js";
import { getDockerCompatibleEnvVars } from "./env-docker.js"; import { getDockerCompatibleEnvVars } from "./env-docker.js";
import path from "path"; import path from "path";
import { createTriggerConfigJson } from "./database-init.js";
export async function deployTriggerTasks(rootDir: string): Promise<void> { export async function deployTriggerTasks(rootDir: string): Promise<void> {
const webappDir = path.join(rootDir, "apps", "webapp"); const webappDir = path.join(rootDir, "apps", "webapp");
@ -63,4 +64,4 @@ export async function deployTriggerTasks(rootDir: string): Promise<void> {
"Manual Deployment" "Manual Deployment"
); );
} }
} }

View File

@ -0,0 +1,18 @@
-- CreateTable
CREATE TABLE "UserUsage" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deleted" TIMESTAMP(3),
"availableCredits" INTEGER NOT NULL DEFAULT 0,
"usedCredits" INTEGER NOT NULL DEFAULT 0,
"userId" TEXT NOT NULL,
CONSTRAINT "UserUsage_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "UserUsage_userId_key" ON "UserUsage"("userId");
-- AddForeignKey
ALTER TABLE "UserUsage" ADD CONSTRAINT "UserUsage_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -50,17 +50,17 @@ model OAuthAuthorizationCode {
id String @id @default(cuid()) id String @id @default(cuid())
code String @unique code String @unique
// OAuth2 specific fields // OAuth2 specific fields
clientId String clientId String
userId String userId String
redirectUri String redirectUri String
scope String? scope String?
state String? state String?
codeChallenge String? codeChallenge String?
codeChallengeMethod String? codeChallengeMethod String?
expiresAt DateTime expiresAt DateTime
used Boolean @default(false) used Boolean @default(false)
// Relations // Relations
client OAuthClient @relation(fields: [clientId], references: [id], onDelete: Cascade) client OAuthClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
@ -73,43 +73,43 @@ model OAuthAuthorizationCode {
model OAuthClient { model OAuthClient {
id String @id @default(cuid()) id String @id @default(cuid())
clientId String @unique clientId String @unique
clientSecret String clientSecret String
name String name String
description String? description String?
// Redirect URIs (comma-separated for simplicity) // Redirect URIs (comma-separated for simplicity)
redirectUris String redirectUris String
// Allowed scopes (comma-separated) // Allowed scopes (comma-separated)
allowedScopes String @default("read") allowedScopes String @default("read")
// Grant types allowed // Grant types allowed
grantTypes String @default("authorization_code") grantTypes String @default("authorization_code")
// PKCE support // PKCE support
requirePkce Boolean @default(false) requirePkce Boolean @default(false)
// Client metadata // Client metadata
logoUrl String? logoUrl String?
homepageUrl String? homepageUrl String?
// GitHub-style features // GitHub-style features
isActive Boolean @default(true) isActive Boolean @default(true)
// Workspace relationship (like GitHub orgs) // Workspace relationship (like GitHub orgs)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String workspaceId String
// Created by user (for audit trail) // Created by user (for audit trail)
createdBy User @relation(fields: [createdById], references: [id]) createdBy User @relation(fields: [createdById], references: [id])
createdById String createdById String
// Relations // Relations
oauthAuthorizationCodes OAuthAuthorizationCode[] oauthAuthorizationCodes OAuthAuthorizationCode[]
accessTokens OAuthAccessToken[] accessTokens OAuthAccessToken[]
refreshTokens OAuthRefreshToken[] refreshTokens OAuthRefreshToken[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
@ -423,12 +423,26 @@ model User {
Conversation Conversation[] Conversation Conversation[]
ConversationHistory ConversationHistory[] ConversationHistory ConversationHistory[]
IngestionRule IngestionRule[] IngestionRule IngestionRule[]
// OAuth2 relations // OAuth2 relations
oauthAuthorizationCodes OAuthAuthorizationCode[] oauthAuthorizationCodes OAuthAuthorizationCode[]
oauthAccessTokens OAuthAccessToken[] oauthAccessTokens OAuthAccessToken[]
oauthRefreshTokens OAuthRefreshToken[] oauthRefreshTokens OAuthRefreshToken[]
oauthClientsCreated OAuthClient[] oauthClientsCreated OAuthClient[]
UserUsage UserUsage?
}
model UserUsage {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deleted DateTime?
availableCredits Int @default(0)
usedCredits Int @default(0)
user User @relation(fields: [userId], references: [id])
userId String @unique
} }
model WebhookConfiguration { model WebhookConfiguration {

View File

@ -23,7 +23,6 @@ export function createMCPTransportBridge(
// Forward messages from client to server // Forward messages from client to server
clientTransport.onmessage = (message: any, extra: any) => { clientTransport.onmessage = (message: any, extra: any) => {
console.log(message);
log("[Client→Server]", message.method || message.id); log("[Client→Server]", message.method || message.id);
onMessage?.("client-to-server", message); onMessage?.("client-to-server", message);
@ -41,8 +40,6 @@ export function createMCPTransportBridge(
// Forward messages from server to client // Forward messages from server to client
serverTransport.onmessage = (message: any, extra: any) => { serverTransport.onmessage = (message: any, extra: any) => {
console.log(message);
console.log(JSON.stringify(message), JSON.stringify(extra));
log("[Server→Client]", message.method || message.id); log("[Server→Client]", message.method || message.id);
onMessage?.("server-to-client", message); onMessage?.("server-to-client", message);

View File

@ -39,8 +39,6 @@ export class RemixMCPTransport implements Transport {
return; return;
} }
console.log(message, "message");
if (Object.keys(message).length === 0) { if (Object.keys(message).length === 0) {
this.send({}); this.send({});
} else { } else {

4922
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff