mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 18:38:27 +00:00
Fix: core cli
This commit is contained in:
parent
a60dc20bf2
commit
2e08470a03
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -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 = "";
|
||||||
|
|||||||
@ -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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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
4922
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user