Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .server-changes/seed-local-cli-pat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
area: webapp
type: improvement
---

The db seed now mints (and prints) a Personal Access Token for the seeded
`local@trigger.dev` user. This lets the CLI authenticate against a local
instance via `TRIGGER_ACCESS_TOKEN` without the browser magic-link flow, which
matters for headless/agent onboarding. Idempotent: re-seeding decrypts and
reprints the existing `local-dev-cli` token instead of creating new ones.
46 changes: 46 additions & 0 deletions apps/webapp/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { createOrganization } from "./app/models/organization.server";
import { createProject } from "./app/models/project.server";
import type { Organization, Prisma, User } from "@trigger.dev/database";
import { AuthenticationMethod } from "@trigger.dev/database";
import { encryptToken, decryptToken, hashToken } from "./app/utils/tokens.server";
import { env } from "./app/env.server";
import { randomBytes } from "node:crypto";

async function seed() {
console.log("🌱 Starting seed...");
Expand Down Expand Up @@ -86,11 +89,54 @@ async function seed() {
console.log(`User: ${user.email}`);
console.log(`Organization: ${organization.title} (${organization.slug})`);
console.log(`Projects: ${referenceProjects.map((p) => p.name).join(", ")}`);

// The PAT is an admin credential. Only mint and print it when seeding a local
// instance, so a stray non-local `db:seed` can't leak it to stdout/logs.
const localHostnames = new Set(["localhost", "127.0.0.1", "[::1]"]);
const isLocalInstance =
env.NODE_ENV !== "production" && localHostnames.has(new URL(env.APP_ORIGIN).hostname);
if (isLocalInstance) {
const localPat = await ensureLocalCliPat(user);
console.log(`\n🔑 CLI access token for ${user.email} (name: ${localPat.name}):`);
console.log(` ${localPat.token}`);
console.log(` Point the CLI at this local instance without a browser login:`);
console.log(` export TRIGGER_ACCESS_TOKEN=${localPat.token}`);
console.log(` export TRIGGER_API_URL=${env.APP_ORIGIN}`);
}
console.log("\n⚠️ Note: in your triggerdotdev/references clone, set TRIGGER_PROJECT_REF in:");
console.log(` - projects/d3-chat/.env: TRIGGER_PROJECT_REF=proj_cdmymsrobxmcgjqzhdkq`);
console.log(` - projects/realtime-streams/.env: TRIGGER_PROJECT_REF=proj_klxlzjnzxmbgiwuuwhvb`);
}

// Mints (or reuses) a Personal Access Token for the seeded local user so the
// CLI can authenticate against this instance without the browser magic-link
// flow. Idempotent: on re-seed we decrypt and reprint the existing token
// rather than piling up new ones. The token is created inline (rather than via
// personalAccessToken.server) so the seed doesn't pull the RBAC/service module
// graph into its import chain.
async function ensureLocalCliPat(user: User) {
const name = "local-dev-cli";
const existing = await prisma.personalAccessToken.findFirst({
where: { userId: user.id, name, revokedAt: null },
});
if (existing) {
const enc = existing.encryptedToken as { nonce: string; ciphertext: string; tag: string };
return { name, token: decryptToken(enc.nonce, enc.ciphertext, enc.tag, env.ENCRYPTION_KEY) };
}
const token = `tr_pat_${randomBytes(20).toString("hex")}`;
const body = token.slice("tr_pat_".length);
await prisma.personalAccessToken.create({
data: {
name,
userId: user.id,
encryptedToken: encryptToken(token, env.ENCRYPTION_KEY),
hashedToken: hashToken(token),
obfuscatedToken: `tr_pat_${body.slice(0, 4)}${"•".repeat(18)}${body.slice(-4)}`,
},
});
return { name, token };
}

async function createBatchLimitOrgs(user: User) {
const org1 = await findOrCreateOrganization("batch-limit-org-1", user, {
batchQueueConcurrencyConfig: { processingConcurrency: 1 },
Expand Down